diff --git a/.github/workflows/apicompatibility.yml b/.github/workflows/apicompatibility.yml index 5a948df11..1077523a6 100644 --- a/.github/workflows/apicompatibility.yml +++ b/.github/workflows/apicompatibility.yml @@ -17,6 +17,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-dotnet@v1 with: + fetch-depth: 0 # fetching all dotnet-version: '7.0.x' include-prerelease: true diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 52e0bc9f2..a8a9213fb 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -20,6 +20,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 # fetching all - uses: actions/setup-dotnet@v1 with: diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index a9ff3f3f3..72729d83a 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -23,6 +23,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 # fetching all - uses: actions/setup-dotnet@v1 with: diff --git a/OpenTelemetry.proj b/OpenTelemetry.proj index 6247d1d6c..2cc1c23a6 100644 --- a/OpenTelemetry.proj +++ b/OpenTelemetry.proj @@ -2,15 +2,8 @@ - - - - - - - diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index b15e5d2b0..4430f261c 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -29,6 +29,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E build\docker-compose.net6.0.yml = build\docker-compose.net6.0.yml build\docker-compose.netcoreapp3.1.yml = build\docker-compose.netcoreapp3.1.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 build\OpenTelemetry.prod.loose.ruleset = build\OpenTelemetry.prod.loose.ruleset build\OpenTelemetry.prod.ruleset = build\OpenTelemetry.prod.ruleset @@ -72,10 +73,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Cons EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Zipkin.Tests", "test\OpenTelemetry.Exporter.Zipkin.Tests\OpenTelemetry.Exporter.Zipkin.Tests.csproj", "{1D778D2E-9523-450E-A6E0-A36897C7E78E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.AspNet", "src\OpenTelemetry.Instrumentation.AspNet\OpenTelemetry.Instrumentation.AspNet.csproj", "{B9EEACDD-CAFA-4B75-A18D-898E7DE21B17}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.AspNet.Tests", "test\OpenTelemetry.Instrumentation.AspNet.Tests\OpenTelemetry.Instrumentation.AspNet.Tests.csproj", "{55CBAADE-7040-46D6-A845-F207B4F0E281}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests", "test\OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests\OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj", "{7C4026CA-6434-4762-8B77-D657EAEE1325}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{F1D0972B-38CF-49C2-9F4B-4C5DE02FB71D}" @@ -126,8 +123,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentati EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.ZPages.Tests", "test\OpenTelemetry.Exporter.ZPages.Tests\OpenTelemetry.Exporter.ZPages.Tests.csproj", "{98F9556B-116F-49B5-9211-BB1D418446FF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.AspNet", "examples\AspNet\Examples.AspNet.csproj", "{9A4E3A68-904B-4835-A3C8-F664B73098DB}" -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}" @@ -198,12 +193,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "reporting-exceptions", "doc EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "customizing-the-sdk", "docs\trace\customizing-the-sdk\customizing-the-sdk.csproj", "{64E3D8BB-93AB-4571-93F7-ED8D64DFFD06}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus", "src\OpenTelemetry.Exporter.Prometheus\OpenTelemetry.Exporter.Prometheus.csproj", "{52158A12-E7EF-45A1-859F-06F9B17410CB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule", "src\OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule\OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj", "{F38E511B-1877-4E8A-8051-7879FC7DF8A4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests", "test\OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests\OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj", "{4D7201BC-7124-4401-AD65-FAB58A053D45}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "learning-more-instruments", "docs\metrics\learning-more-instruments\learning-more-instruments.csproj", "{E7F491CC-C37E-4A56-9CA7-8F77F59E0614}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started", "docs\metrics\getting-started\getting-started.csproj", "{EA60B549-F712-4ABE-8E44-FCA83B78C06E}" @@ -216,8 +205,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "customizing-the-sdk", "docs EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Tests.Stress", "test\OpenTelemetry.Tests.Stress\OpenTelemetry.Tests.Stress.csproj", "{2770158A-D220-414B-ABC6-179371323579}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.Tests", "test\OpenTelemetry.Exporter.Prometheus.Tests\OpenTelemetry.Exporter.Prometheus.Tests.csproj", "{380EE686-91F1-45B3-AEEB-755F0E5B068F}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs", "src\OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs\OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs.csproj", "{6E1A5FA3-E024-4972-9EDC-11E36C5A0D6F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp.AspNetCore.6.0", "test\TestApp.AspNetCore.6.0\TestApp.AspNetCore.6.0.csproj", "{0076C657-564F-4787-9FFF-52D9D55166E8}" @@ -234,6 +221,26 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Pr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "correlation", "docs\logs\correlation\correlation.csproj", "{9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Tests.Stress.Logs", "test\OpenTelemetry.Tests.Stress.Logs\OpenTelemetry.Tests.Stress.Logs.csproj", "{4298057B-24E0-47B3-BB76-C17E81AF6B39}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.LoggingExtensions", "examples\LoggingExtensions\Examples.LoggingExtensions.csproj", "{F5EFF065-7AF5-4D7D-8038-CC419ABD8777}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Serilog", "src\OpenTelemetry.Extensions.Serilog\OpenTelemetry.Extensions.Serilog.csproj", "{0D85558E-15B9-4251-BDBD-9CB7933B57E2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Serilog.Tests", "test\OpenTelemetry.Extensions.Serilog.Tests\OpenTelemetry.Extensions.Serilog.Tests.csproj", "{6A2C122A-C1CD-4B6B-AE09-2ABB7D3C50CE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.EventSource", "src\OpenTelemetry.Extensions.EventSource\OpenTelemetry.Extensions.EventSource.csproj", "{7AFB4975-9680-4668-9F5E-C3F0CA41E982}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.EventSource.Tests", "test\OpenTelemetry.Extensions.EventSource.Tests\OpenTelemetry.Extensions.EventSource.Tests.csproj", "{304FCFFF-97DE-484B-8D8C-612C644426E5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.AspNetCore", "src\OpenTelemetry.Exporter.Prometheus.AspNetCore\OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj", "{921CF401-4C2F-4C6D-A750-0B5DC457C1F1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.HttpListener", "src\OpenTelemetry.Exporter.Prometheus.HttpListener\OpenTelemetry.Exporter.Prometheus.HttpListener.csproj", "{6B0232B7-5F29-4FB5-B383-1AA02DFE1089}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests", "test\OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests\OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj", "{FBD12B0B-6731-4DD4-9C13-86F34593E974}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus.HttpListener.Tests", "test\OpenTelemetry.Exporter.Prometheus.HttpListener.Tests\OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj", "{4EF4364F-6E64-43CE-BED1-E6FE01024899}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -304,14 +311,6 @@ Global {1D778D2E-9523-450E-A6E0-A36897C7E78E}.Debug|Any CPU.Build.0 = Debug|Any CPU {1D778D2E-9523-450E-A6E0-A36897C7E78E}.Release|Any CPU.ActiveCfg = Release|Any CPU {1D778D2E-9523-450E-A6E0-A36897C7E78E}.Release|Any CPU.Build.0 = Release|Any CPU - {B9EEACDD-CAFA-4B75-A18D-898E7DE21B17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9EEACDD-CAFA-4B75-A18D-898E7DE21B17}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9EEACDD-CAFA-4B75-A18D-898E7DE21B17}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9EEACDD-CAFA-4B75-A18D-898E7DE21B17}.Release|Any CPU.Build.0 = Release|Any CPU - {55CBAADE-7040-46D6-A845-F207B4F0E281}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {55CBAADE-7040-46D6-A845-F207B4F0E281}.Debug|Any CPU.Build.0 = Debug|Any CPU - {55CBAADE-7040-46D6-A845-F207B4F0E281}.Release|Any CPU.ActiveCfg = Release|Any CPU - {55CBAADE-7040-46D6-A845-F207B4F0E281}.Release|Any CPU.Build.0 = Release|Any CPU {7C4026CA-6434-4762-8B77-D657EAEE1325}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7C4026CA-6434-4762-8B77-D657EAEE1325}.Debug|Any CPU.Build.0 = Debug|Any CPU {7C4026CA-6434-4762-8B77-D657EAEE1325}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -324,10 +323,6 @@ Global {98F9556B-116F-49B5-9211-BB1D418446FF}.Debug|Any CPU.Build.0 = Debug|Any CPU {98F9556B-116F-49B5-9211-BB1D418446FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {98F9556B-116F-49B5-9211-BB1D418446FF}.Release|Any CPU.Build.0 = Release|Any CPU - {9A4E3A68-904B-4835-A3C8-F664B73098DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9A4E3A68-904B-4835-A3C8-F664B73098DB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9A4E3A68-904B-4835-A3C8-F664B73098DB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9A4E3A68-904B-4835-A3C8-F664B73098DB}.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 @@ -408,18 +403,6 @@ Global {64E3D8BB-93AB-4571-93F7-ED8D64DFFD06}.Debug|Any CPU.Build.0 = Debug|Any CPU {64E3D8BB-93AB-4571-93F7-ED8D64DFFD06}.Release|Any CPU.ActiveCfg = Release|Any CPU {64E3D8BB-93AB-4571-93F7-ED8D64DFFD06}.Release|Any CPU.Build.0 = Release|Any CPU - {52158A12-E7EF-45A1-859F-06F9B17410CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {52158A12-E7EF-45A1-859F-06F9B17410CB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {52158A12-E7EF-45A1-859F-06F9B17410CB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {52158A12-E7EF-45A1-859F-06F9B17410CB}.Release|Any CPU.Build.0 = Release|Any CPU - {F38E511B-1877-4E8A-8051-7879FC7DF8A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F38E511B-1877-4E8A-8051-7879FC7DF8A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F38E511B-1877-4E8A-8051-7879FC7DF8A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F38E511B-1877-4E8A-8051-7879FC7DF8A4}.Release|Any CPU.Build.0 = Release|Any CPU - {4D7201BC-7124-4401-AD65-FAB58A053D45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D7201BC-7124-4401-AD65-FAB58A053D45}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D7201BC-7124-4401-AD65-FAB58A053D45}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D7201BC-7124-4401-AD65-FAB58A053D45}.Release|Any CPU.Build.0 = Release|Any CPU {E7F491CC-C37E-4A56-9CA7-8F77F59E0614}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E7F491CC-C37E-4A56-9CA7-8F77F59E0614}.Debug|Any CPU.Build.0 = Debug|Any CPU {E7F491CC-C37E-4A56-9CA7-8F77F59E0614}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -444,10 +427,6 @@ Global {2770158A-D220-414B-ABC6-179371323579}.Debug|Any CPU.Build.0 = Debug|Any CPU {2770158A-D220-414B-ABC6-179371323579}.Release|Any CPU.ActiveCfg = Release|Any CPU {2770158A-D220-414B-ABC6-179371323579}.Release|Any CPU.Build.0 = Release|Any CPU - {380EE686-91F1-45B3-AEEB-755F0E5B068F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {380EE686-91F1-45B3-AEEB-755F0E5B068F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {380EE686-91F1-45B3-AEEB-755F0E5B068F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {380EE686-91F1-45B3-AEEB-755F0E5B068F}.Release|Any CPU.Build.0 = Release|Any CPU {6E1A5FA3-E024-4972-9EDC-11E36C5A0D6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6E1A5FA3-E024-4972-9EDC-11E36C5A0D6F}.Debug|Any CPU.Build.0 = Debug|Any CPU {6E1A5FA3-E024-4972-9EDC-11E36C5A0D6F}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -480,6 +459,46 @@ Global {9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5}.Release|Any CPU.Build.0 = Release|Any CPU + {4298057B-24E0-47B3-BB76-C17E81AF6B39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4298057B-24E0-47B3-BB76-C17E81AF6B39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4298057B-24E0-47B3-BB76-C17E81AF6B39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4298057B-24E0-47B3-BB76-C17E81AF6B39}.Release|Any CPU.Build.0 = Release|Any CPU + {F5EFF065-7AF5-4D7D-8038-CC419ABD8777}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5EFF065-7AF5-4D7D-8038-CC419ABD8777}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5EFF065-7AF5-4D7D-8038-CC419ABD8777}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5EFF065-7AF5-4D7D-8038-CC419ABD8777}.Release|Any CPU.Build.0 = Release|Any CPU + {0D85558E-15B9-4251-BDBD-9CB7933B57E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D85558E-15B9-4251-BDBD-9CB7933B57E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D85558E-15B9-4251-BDBD-9CB7933B57E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D85558E-15B9-4251-BDBD-9CB7933B57E2}.Release|Any CPU.Build.0 = Release|Any CPU + {6A2C122A-C1CD-4B6B-AE09-2ABB7D3C50CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A2C122A-C1CD-4B6B-AE09-2ABB7D3C50CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A2C122A-C1CD-4B6B-AE09-2ABB7D3C50CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A2C122A-C1CD-4B6B-AE09-2ABB7D3C50CE}.Release|Any CPU.Build.0 = Release|Any CPU + {7AFB4975-9680-4668-9F5E-C3F0CA41E982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AFB4975-9680-4668-9F5E-C3F0CA41E982}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AFB4975-9680-4668-9F5E-C3F0CA41E982}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AFB4975-9680-4668-9F5E-C3F0CA41E982}.Release|Any CPU.Build.0 = Release|Any CPU + {304FCFFF-97DE-484B-8D8C-612C644426E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {304FCFFF-97DE-484B-8D8C-612C644426E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {304FCFFF-97DE-484B-8D8C-612C644426E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {304FCFFF-97DE-484B-8D8C-612C644426E5}.Release|Any CPU.Build.0 = Release|Any CPU + {921CF401-4C2F-4C6D-A750-0B5DC457C1F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {921CF401-4C2F-4C6D-A750-0B5DC457C1F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {921CF401-4C2F-4C6D-A750-0B5DC457C1F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {921CF401-4C2F-4C6D-A750-0B5DC457C1F1}.Release|Any CPU.Build.0 = Release|Any CPU + {6B0232B7-5F29-4FB5-B383-1AA02DFE1089}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B0232B7-5F29-4FB5-B383-1AA02DFE1089}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B0232B7-5F29-4FB5-B383-1AA02DFE1089}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B0232B7-5F29-4FB5-B383-1AA02DFE1089}.Release|Any CPU.Build.0 = Release|Any CPU + {FBD12B0B-6731-4DD4-9C13-86F34593E974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBD12B0B-6731-4DD4-9C13-86F34593E974}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBD12B0B-6731-4DD4-9C13-86F34593E974}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBD12B0B-6731-4DD4-9C13-86F34593E974}.Release|Any CPU.Build.0 = Release|Any CPU + {4EF4364F-6E64-43CE-BED1-E6FE01024899}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EF4364F-6E64-43CE-BED1-E6FE01024899}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EF4364F-6E64-43CE-BED1-E6FE01024899}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EF4364F-6E64-43CE-BED1-E6FE01024899}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -489,7 +508,6 @@ 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} - {9A4E3A68-904B-4835-A3C8-F664B73098DB} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E} {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} @@ -517,6 +535,7 @@ Global {41B784AA-3301-4126-AF9F-1D59BD04B0BF} = {3277B1C0-BDFE-4460-9B0D-D9A661FB48DB} {6C7A1595-36D6-4229-BBB5-5A6B5791791D} = {3862190B-E2C5-418E-AFDC-DB281FB5C705} {9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5} = {3862190B-E2C5-418E-AFDC-DB281FB5C705} + {F5EFF065-7AF5-4D7D-8038-CC419ABD8777} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521} diff --git a/README.md b/README.md index b7ab73708..442df7a4c 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,6 @@ Here are the most commonly used components: Here are the [instrumentation libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library): -* [ASP.NET](./src/OpenTelemetry.Instrumentation.AspNet/README.md) * [ASP.NET Core](./src/OpenTelemetry.Instrumentation.AspNetCore/README.md) * [Grpc.Net.Client](./src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md) * [HTTP clients](./src/OpenTelemetry.Instrumentation.Http/README.md) @@ -67,7 +66,8 @@ libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/ma * [Jaeger](./src/OpenTelemetry.Exporter.Jaeger/README.md) * [OTLP](./src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md) (OpenTelemetry Protocol) -* [Prometheus](./src/OpenTelemetry.Exporter.Prometheus/README.md) +* [Prometheus HttpListener](./src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md) +* [Prometheus AspNetCore](./src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md) * [Zipkin](./src/OpenTelemetry.Exporter.Zipkin/README.md) See the [OpenTelemetry registry](https://opentelemetry.io/registry/?s=net) for @@ -111,13 +111,13 @@ If you have trouble accessing the doc, please get in touch on * [Alan West](https://github.com/alanwest), New Relic * [Cijo Thomas](https://github.com/cijothomas), Microsoft * [Mikel Blanchard](https://github.com/CodeBlanch), Microsoft +* [Utkarsh Umesan Pillai](https://github.com/utpilla), Microsoft [Approvers](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver) ([@open-telemetry/dotnet-approvers](https://github.com/orgs/open-telemetry/teams/dotnet-approvers)): * [Reiley Yang](https://github.com/reyang), Microsoft * [Robert Pająk](https://github.com/pellared), Splunk -* [Utkarsh Umesan Pillai](https://github.com/utpilla), Microsoft [Emeritus Maintainer/Approver/Triager](https://github.com/open-telemetry/community/blob/main/community-membership.md#emeritus-maintainerapprovertriager): diff --git a/build/Common.nonprod.props b/build/Common.nonprod.props index 45fbfd950..4810547d7 100644 --- a/build/Common.nonprod.props +++ b/build/Common.nonprod.props @@ -37,17 +37,15 @@ [2.43.0,3.0) [2.43.0, 3.0) [2.44.0,3.0) - [5.2.7,6.0) - [5.2.7,6.0) - [3.2.7,4.0) [3.1.6,5.0) [6.0.0,) [6.0.0,) [16.10.0] - [12.0.2,13.0) [4.14.5,5.0) [6.1.0,7.0) + [1.0.0-rc.2,2.0) [6.2.3] + 6.0.5 [2.4.3,3.0) [2.4.1,3.0) diff --git a/build/Common.prod.props b/build/Common.prod.props index c97f40a0e..915644b77 100644 --- a/build/Common.prod.props +++ b/build/Common.prod.props @@ -35,6 +35,7 @@ $(Build_ArtifactStagingDirectory) true + $(RepoRoot)\build\GlobalAttrExclusions.txt diff --git a/build/Common.props b/build/Common.props index f1c8471b4..46569eea8 100644 --- a/build/Common.props +++ b/build/Common.props @@ -22,7 +22,7 @@ Please sort alphabetically. Refer to https://docs.microsoft.com/nuget/concepts/package-versioning for semver syntax. --> - [2.3.0,3.0) + [4.1.0,5.0) [3.19.4,4.0) [2.44.0,3.0) [2.43.0,3.0) @@ -40,6 +40,7 @@ [1.0.0,2.0) [0.12.1,0.13) 1.3.0 + [2.8.0,3.0) [1.2.0-beta.354,2.0) 1.4.0 7.0.0-preview.4.22229.4 diff --git a/build/GlobalAttrExclusions.txt b/build/GlobalAttrExclusions.txt new file mode 100644 index 000000000..eb04262f5 --- /dev/null +++ b/build/GlobalAttrExclusions.txt @@ -0,0 +1,4 @@ +// These attributes should be excluded from ApiCompat checks. + +T:System.Runtime.CompilerServices.CompilerGeneratedAttribute +T:System.Runtime.CompilerServices.NullableContextAttribute \ No newline at end of file diff --git a/docs/logs/customizing-the-sdk/Program.cs b/docs/logs/customizing-the-sdk/Program.cs index edaf04e99..b6b3afd07 100644 --- a/docs/logs/customizing-the-sdk/Program.cs +++ b/docs/logs/customizing-the-sdk/Program.cs @@ -30,9 +30,7 @@ public class Program builder.AddOpenTelemetry(options => { options.IncludeScopes = true; - options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService( - serviceName: "MyService", - serviceVersion: "1.0.0")); + options.ConfigureResource(r => r.AddService(serviceName: "MyService", serviceVersion: "1.0.0")); options.AddConsoleExporter(); }); }); diff --git a/docs/logs/customizing-the-sdk/README.md b/docs/logs/customizing-the-sdk/README.md index e9cc9e020..8b1d9bcb9 100644 --- a/docs/logs/customizing-the-sdk/README.md +++ b/docs/logs/customizing-the-sdk/README.md @@ -52,26 +52,25 @@ var loggerFactory = LoggerFactory.Create(builder => For more information on Processors, please review [Extending the SDK](../extending-the-sdk/README.md#processor) -### SetResourceBuilder +### ConfigureResource [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 SDK](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions#telemetry-sdk). -The `SetResourceBuilder` method on `OpenTelemetryLoggerOptions` can be used to -set a single `ResourceBuilder`. If `SetResourceBuilder` is called multiple -times, only the last is kept. It is not possible to change the resource builder +The `ConfigureResource` method on `OpenTelemetryLoggerOptions` can be used to +configure the `ResourceBuilder`. It is not possible to change the resources *after* creating the `LoggerFactory`. -The snippet below shows configuring a custom `ResourceBuilder` to the provider. +The snippet below shows configuring the `ResourceBuilder` of the provider. ```csharp var loggerFactory = LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => { - options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService( + options.ConfigureResource(r => r.AddService( serviceName: "MyService", serviceVersion: "1.0.0" )); diff --git a/docs/metrics/customizing-the-sdk/README.md b/docs/metrics/customizing-the-sdk/README.md index e2b024eee..eeeb64ef8 100644 --- a/docs/metrics/customizing-the-sdk/README.md +++ b/docs/metrics/customizing-the-sdk/README.md @@ -35,9 +35,10 @@ In a typical application, a single `MeterProvider` is created at application startup and disposed at application shutdown. It is important to ensure that the provider is not disposed too early. Actual mechanism depends on the application type. For example, in a typical ASP.NET application, `MeterProvider` is created -in `Application_Start`, and disposed in `Application_End` (both methods part of -Global.asax.cs file) as shown [here](../../../examples/AspNet/Global.asax.cs). In -a typical ASP.NET Core application, `MeterProvider` lifetime is managed by +in `Application_Start`, and disposed in `Application_End` (both methods are a +part of the Global.asax.cs file) as shown +[here](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/examples/AspNet/Global.asax.cs). +In a typical ASP.NET Core application, `MeterProvider` lifetime is managed by leveraging the built-in Dependency Injection container as shown [here](../../../examples/AspNetCore/Program.cs). @@ -422,7 +423,8 @@ Refer to the individual exporter docs to learn how to use them: * [In-memory](../../../src/OpenTelemetry.Exporter.InMemory/README.md) * [OTLP](../../../src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md) (OpenTelemetry Protocol) -* [Prometheus](../../../src/OpenTelemetry.Exporter.Prometheus/README.md) +* [Prometheus HttpListener](../../../src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md) +* [Prometheus AspNetCore](../../../src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md) ### Resource diff --git a/docs/metrics/extending-the-sdk/README.md b/docs/metrics/extending-the-sdk/README.md index 7671bb420..0777c8f8f 100644 --- a/docs/metrics/extending-the-sdk/README.md +++ b/docs/metrics/extending-the-sdk/README.md @@ -12,7 +12,8 @@ OpenTelemetry .NET SDK has provided the following built-in metric exporters: * [InMemory](../../../src/OpenTelemetry.Exporter.InMemory/README.md) * [Console](../../../src/OpenTelemetry.Exporter.Console/README.md) * [OpenTelemetryProtocol](../../../src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md) -* [Prometheus](../../../src/OpenTelemetry.Exporter.Prometheus/README.md) +* [Prometheus HttpListener](../../../src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md) +* [Prometheus AspNetCore](../../../src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md) Custom exporters can be implemented to send telemetry data to places which are not covered by the built-in exporters: diff --git a/docs/metrics/getting-started-prometheus-grafana/Program.cs b/docs/metrics/getting-started-prometheus-grafana/Program.cs index 2e176c39f..32bfa1c65 100644 --- a/docs/metrics/getting-started-prometheus-grafana/Program.cs +++ b/docs/metrics/getting-started-prometheus-grafana/Program.cs @@ -31,7 +31,7 @@ public class Program { using var meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter("MyCompany.MyProduct.MyLibrary") - .AddPrometheusExporter(options => { options.StartHttpListener = true; }) + .AddPrometheusHttpListener() .Build(); Console.WriteLine("Press any key to exit"); diff --git a/docs/metrics/getting-started-prometheus-grafana/README.md b/docs/metrics/getting-started-prometheus-grafana/README.md index e58245a6b..1e2f2e35e 100644 --- a/docs/metrics/getting-started-prometheus-grafana/README.md +++ b/docs/metrics/getting-started-prometheus-grafana/README.md @@ -23,10 +23,10 @@ dotnet run ``` Add a reference to [Prometheus -Exporter](../../../src/OpenTelemetry.Exporter.Prometheus/README.md): +Exporter Http Listener](../../../src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md): ```sh -dotnet add package --prerelease OpenTelemetry.Exporter.Prometheus +dotnet add package --prerelease OpenTelemetry.Exporter.Prometheus.HttpListener ``` Now, we are going to make some small tweaks to the example in the @@ -46,12 +46,13 @@ And replace the below line: with ```csharp -.AddPrometheusExporter(options => { options.StartHttpListener = true; }) +.AddPrometheusHttpListener() ``` -With `AddPrometheusExporter()`, OpenTelemetry `PrometheusExporter` will export +`PrometheusHttpListener` is a wrapper that contains `PrometheusExporter`. With +`AddPrometheusHttpListener()`, OpenTelemetry `PrometheusExporter` will export data via the endpoint defined by -[PrometheusExporterOptions.HttpListenerPrefixes](../../../src/OpenTelemetry.Exporter.Prometheus/README.md#httplistenerprefixes), +[PrometheusHttpListenerOptions.UriPrefixes](../../../src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md#uriprefixes), which is `http://localhost:9464/` by default. ```mermaid @@ -60,7 +61,7 @@ graph LR subgraph SDK MeterProvider MetricReader[BaseExportingMetricReader] - PrometheusExporter["PrometheusExporter
(http://localhost:9464/)"] + PrometheusHttpListener["PrometheusHttpListener
(http://localhost:9464/)"] end subgraph API @@ -69,7 +70,7 @@ end Instrument --> | Measurements | MeterProvider -MeterProvider --> | Metrics | MetricReader --> | Pull | PrometheusExporter +MeterProvider --> | Metrics | MetricReader --> | Pull | PrometheusHttpListener ``` Also, for our learning purpose, use a while-loop to keep increasing the counter @@ -99,7 +100,7 @@ web browser: ![Browser UI](https://user-images.githubusercontent.com/17327289/151633547-736c6d91-62d2-4e66-a53f-2e16c44bfabc.png) -Now, we understand how we can configure `PrometheusExporter` to export metrics. +Now, we understand how we can configure `PrometheusHttpListener` to export metrics. Next, we are going to learn about how to use Prometheus to collect the metrics. ## Collect metrics using Prometheus @@ -156,7 +157,7 @@ values we have set in `otel.yml`. Congratulations! Now we know how to configure Prometheus server and deploy OpenTelemetry -`PrometheusExporter` to export our metrics. Next, we are going to explore a tool +`PrometheusHttpListener` to export our metrics. Next, we are going to explore a tool called Grafana, which has powerful visualizations for the metrics. ## Explore metrics using Grafana @@ -201,7 +202,7 @@ subgraph Prometheus PrometheusDatabase end -PrometheusExporter["PrometheusExporter
(listening at #quot;http://localhost:9464/#quot;)"] -->|HTTP GET| PrometheusScraper{{"Prometheus scraper
(polling #quot;http://localhost:9464/metrics#quot; every 10 seconds)"}} +PrometheusHttpListener["PrometheusHttpListener
(listening at #quot;http://localhost:9464/#quot;)"] -->|HTTP GET| PrometheusScraper{{"Prometheus scraper
(polling #quot;http://localhost:9464/metrics#quot; every 10 seconds)"}} PrometheusScraper --> PrometheusDatabase[("Prometheus TSDB (time series database)")] PrometheusDatabase -->|http://localhost:9090/graph| PrometheusUI["Browser
(Prometheus Dashboard)"] PrometheusDatabase -->|http://localhost:9090/api/| Grafana[Grafana Server] diff --git a/docs/metrics/getting-started-prometheus-grafana/getting-started-prometheus-grafana.csproj b/docs/metrics/getting-started-prometheus-grafana/getting-started-prometheus-grafana.csproj index 4913a024a..8d59ff99c 100644 --- a/docs/metrics/getting-started-prometheus-grafana/getting-started-prometheus-grafana.csproj +++ b/docs/metrics/getting-started-prometheus-grafana/getting-started-prometheus-grafana.csproj @@ -1,5 +1,5 @@ - + diff --git a/docs/trace/customizing-the-sdk/README.md b/docs/trace/customizing-the-sdk/README.md index 9587e9c13..1bac8017d 100644 --- a/docs/trace/customizing-the-sdk/README.md +++ b/docs/trace/customizing-the-sdk/README.md @@ -15,9 +15,9 @@ processors, etc. Naturally, almost all the customizations must be done on the Building a `TracerProvider` is done using `TracerProviderBuilder` which must be obtained by calling `Sdk.CreateTracerProviderBuilder()`. `TracerProviderBuilder` -exposes various methods which configures the provider it is going to build. These -includes methods like `SetSampler`, `AddProcessor` etc, and are explained in -subsequent sections of this document. Once configuration is done, calling +exposes various methods which configures the provider it is going to build. +These includes methods like `SetSampler`, `AddProcessor` etc, and are explained +in subsequent sections of this document. Once configuration is done, calling `Build()` on the `TracerProviderBuilder` builds the `TracerProvider` instance. Once built, changes to its configuration is not allowed, with the exception of adding more processors. In most cases, a single `TracerProvider` is created at @@ -38,9 +38,10 @@ In a typical application, a single `TracerProvider` is created at application startup and disposed at application shutdown. It is important to ensure that the provider is not disposed too early. Actual mechanism depends on the application type. For example, in a typical ASP.NET application, `TracerProvider` is created -in `Application_Start`, and disposed in `Application_End` (both methods part of -Global.asax.cs file) as shown [here](../../../examples/AspNet/Global.asax.cs). In -a typical ASP.NET Core application, `TracerProvider` lifetime is managed by +in `Application_Start`, and disposed in `Application_End` (both methods are a +part of the Global.asax.cs file) as shown +[here](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/examples/AspNet/Global.asax.cs). +In a typical ASP.NET Core application, `TracerProvider` lifetime is managed by leveraging the built-in Dependency Injection container as shown [here](../../../examples/AspNetCore/Program.cs). @@ -98,13 +99,12 @@ using var tracerProvider = Sdk.CreateTracerProviderBuilder() See [Program.cs](./Program.cs) for complete example. -**Note** -A common mistake while configuring `TracerProvider` is forgetting to add -all `ActivitySources` to the provider. It is recommended to leverage the +**Note** A common mistake while configuring `TracerProvider` is forgetting to +add all `ActivitySources` to the provider. It is recommended to leverage the wild card subscription model where it makes sense. For example, if your -application is expecting to enable tracing from a number of libraries -from a company "Abc", the you can use `AddSource("Abc.*")` to enable -all sources whose name starts with "Abc.". +application is expecting to enable tracing from a number of libraries from a +company "Abc", the you can use `AddSource("Abc.*")` to enable all sources whose +name starts with "Abc.". ### Instrumentation @@ -124,5 +124,5 @@ all sources whose name starts with "Abc.". ## Context Propagation -// TODO: OpenTelemetry Sdk contents about Context. -// TODO: Links to built-in instrumentations doing Propagation. +// TODO: OpenTelemetry Sdk contents about Context. // TODO: Links to built-in +instrumentations doing Propagation. diff --git a/docs/trace/extending-the-sdk/MyFilteringProcessor.cs b/docs/trace/extending-the-sdk/MyFilteringProcessor.cs index 22cf898f3..ed74678de 100644 --- a/docs/trace/extending-the-sdk/MyFilteringProcessor.cs +++ b/docs/trace/extending-the-sdk/MyFilteringProcessor.cs @@ -17,16 +17,28 @@ using System; using System.Diagnostics; using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; -internal class MyFilteringProcessor : BaseProcessor +/// +/// A custom processor for filtering instances. +/// +/// +/// Note: is used as the base class because +/// the SDK needs to understand that MyFilteringProcessor wraps an inner +/// processor. Without that understanding some features such as would be unavailable because the SDK needs to push state +/// about the parent to all processors in the +/// chain. +/// +internal sealed class MyFilteringProcessor : CompositeProcessor { private readonly Func filter; - private readonly BaseProcessor processor; public MyFilteringProcessor(BaseProcessor processor, Func filter) + : base(new[] { processor }) { this.filter = filter ?? throw new ArgumentNullException(nameof(filter)); - this.processor = processor ?? throw new ArgumentNullException(nameof(processor)); } public override void OnEnd(Activity activity) @@ -35,7 +47,7 @@ internal class MyFilteringProcessor : BaseProcessor // only if the Filter returns true. if (this.filter(activity)) { - this.processor.OnEnd(activity); + base.OnEnd(activity); } } } diff --git a/docs/trace/extending-the-sdk/README.md b/docs/trace/extending-the-sdk/README.md index 09058fc41..b95f8225c 100644 --- a/docs/trace/extending-the-sdk/README.md +++ b/docs/trace/extending-the-sdk/README.md @@ -102,7 +102,6 @@ The [OpenTelemetry .NET Github repo](../../../README.md#getting-started) ships the following instrumentation libraries. The individual docs for them describes the library they instrument, and steps for enabling them. -* [ASP.NET](../../../src/OpenTelemetry.Instrumentation.AspNet/README.md) * [ASP.NET Core](../../../src/OpenTelemetry.Instrumentation.AspNetCore/README.md) * [gRPC @@ -175,7 +174,10 @@ Writing an instrumentation library typically involves 3 steps. method, it should call the `AddInstrumentation` method, and `AddSource` method to enable its ActivitySource for the provider. An example instrumentation using this approach is [SqlClient - instrumentation](../../../src/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs) + instrumentation](../../../src/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs). + **CAUTION**: The instrumentation libraries requiring state management + are usually hard to auto-instrument. Therefore, they take the risk of not + being supported by [OpenTelemetry .NET Automatic Instrumentation](https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation). 2. If the instrumentation library does not requires any state management tied to that of `TracerProvider`, then providing `TracerProviderBuilder` @@ -200,8 +202,7 @@ activities does not by default runs through the sampler, and will have their `Kind` set to internal and they'll have empty ActivitySource name associated with it. -Some common examples of such libraries include -[ASP.NET](../../../src/OpenTelemetry.Instrumentation.AspNet/README.md), [ASP.NET +Some common examples of such libraries include [ASP.NET Core](../../../src/OpenTelemetry.Instrumentation.AspNetCore/README.md), [HTTP client .NET Core](../../../src/OpenTelemetry.Instrumentation.Http/README.md) . Instrumentation libraries for these are already provided in this repo. The diff --git a/examples/AspNet/App_Start/WebApiConfig.cs b/examples/AspNet/App_Start/WebApiConfig.cs deleted file mode 100644 index 243637d65..000000000 --- a/examples/AspNet/App_Start/WebApiConfig.cs +++ /dev/null @@ -1,40 +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.Net.Http.Formatting; -using System.Web.Http; - -namespace Examples.AspNet -{ - public static class WebApiConfig - { - public static void Register(HttpConfiguration config) - { - // Web API configuration and services - - // Web API routes - config.MapHttpAttributeRoutes(); - - config.Routes.MapHttpRoute( - name: "DefaultApi", - routeTemplate: "api/{controller}/{id}", - defaults: new { id = RouteParameter.Optional }); - - config.Formatters.Clear(); - config.Formatters.Add(new JsonMediaTypeFormatter()); - } - } -} diff --git a/examples/AspNet/Controllers/HomeController.cs b/examples/AspNet/Controllers/HomeController.cs deleted file mode 100644 index f23880589..000000000 --- a/examples/AspNet/Controllers/HomeController.cs +++ /dev/null @@ -1,37 +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.Web.Mvc; - -namespace Examples.AspNet.Controllers -{ - public class HomeController : Controller - { - // For testing traditional routing. Ex: https://localhost:XXXX/ - public ActionResult Index() - { - return this.View(); - } - - [Route("about_attr_route/{customerId}")] // For testing attribute routing. Ex: https://localhost:XXXX/about_attr_route - public ActionResult About(int? customerId) - { - this.ViewBag.Message = $"Your application description page for customer {customerId}."; - - return this.View(); - } - } -} diff --git a/examples/AspNet/Controllers/WeatherForecastController.cs b/examples/AspNet/Controllers/WeatherForecastController.cs deleted file mode 100644 index 092bdf071..000000000 --- a/examples/AspNet/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,217 +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; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Security.Cryptography; -using System.Threading.Tasks; -using System.Web.Http; -using Examples.AspNet.Models; -using OpenTelemetry; - -namespace Examples.AspNet.Controllers -{ - public class WeatherForecastController : ApiController - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching", - }; - - [HttpGet] // For testing traditional routing. Ex: https://localhost:XXXX/api/weatherforecast - public async Task> Get() - { - // Build some dependency spans. - - await RequestGoogleHomPageViaHttpClient().ConfigureAwait(false); - - await this.RequestInvalidViaHttpClient().ConfigureAwait(false); - - await this.RequestValidThatReturnsFailedViaHttpClient().ConfigureAwait(false); - - await this.RequestValidThatSpawnsSubSpansViaHttpClient().ConfigureAwait(false); - - return GetWeatherForecast(); - } - - [Route("subroute/{customerId}")] // For testing attribute routing. Ex: https://localhost:XXXX/subroute/10 - [HttpGet] - public async Task> Get(int customerId) - { - if (customerId < 0) - { - throw new ArgumentException(); - } - - // Making http calls here to serve as an example of - // how dependency calls will be captured and treated - // automatically as child of incoming request. - - RequestGoogleHomPageViaHttpWebRequestLegacySync(); - - await RequestGoogleHomPageViaHttpWebRequestLegacyAsync().ConfigureAwait(false); - - RequestGoogleHomPageViaHttpWebRequestLegacyAsyncResult(); - - return GetWeatherForecast(); - } - - /// - /// For testing large async operation which causes IIS to jump threads and results in lost AsyncLocals. - /// - [Route("data")] - [HttpGet] - public async Task GetData() - { - Baggage.SetBaggage("key1", "value1"); - - using var rng = RandomNumberGenerator.Create(); - - var requestData = new byte[1024 * 1024 * 100]; - rng.GetBytes(requestData); - - using var client = new HttpClient(); - - using var request = new HttpRequestMessage(HttpMethod.Post, this.Url.Content("~/data")); - - request.Content = new ByteArrayContent(requestData); - - using var response = await client.SendAsync(request).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - - var responseData = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); - - return responseData.SequenceEqual(responseData) ? "match" : "mismatch"; - } - - [Route("data")] - [HttpPost] - public async Task PostData() - { - string value1 = Baggage.GetBaggage("key1"); - if (string.IsNullOrEmpty(value1)) - { - throw new InvalidOperationException("Key1 was not found on Baggage."); - } - - var stream = await this.Request.Content.ReadAsStreamAsync().ConfigureAwait(false); - - var result = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StreamContent(stream), - }; - - result.Content.Headers.ContentType = this.Request.Content.Headers.ContentType; - - return result; - } - - private static IEnumerable GetWeatherForecast() - { - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)], - }) - .ToArray(); - } - - // Test successful dependency collection via HttpClient. - private static async Task RequestGoogleHomPageViaHttpClient() - { - using var request = new HttpClient(); - - using var response = await request.GetAsync("http://www.google.com").ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - } - - // Test dependency collection via legacy HttpWebRequest sync. - private static void RequestGoogleHomPageViaHttpWebRequestLegacySync() - { - var request = WebRequest.Create("http://www.google.com/?sync"); - - using var response = request.GetResponse(); - } - - // Test dependency collection via legacy HttpWebRequest async. - private static async Task RequestGoogleHomPageViaHttpWebRequestLegacyAsync() - { - var request = (HttpWebRequest)WebRequest.Create($"http://www.google.com/?async"); - - using var response = await request.GetResponseAsync().ConfigureAwait(false); - } - - // Test dependency collection via legacy HttpWebRequest IAsyncResult. - private static void RequestGoogleHomPageViaHttpWebRequestLegacyAsyncResult() - { - var request = (HttpWebRequest)WebRequest.Create($"http://www.google.com/?async"); - - var asyncResult = request.BeginGetResponse(null, null); - - using var response = request.EndGetResponse(asyncResult); - } - - // Test exception dependency collection via HttpClient. - private async Task RequestInvalidViaHttpClient() - { - try - { - using var request = new HttpClient(); - - // This request is not available over SSL and will throw a handshake exception. - - using var response = await request.GetAsync(this.Url.Content("~/subroute/10").Replace("http", "https")).ConfigureAwait(false); - - Debug.Fail("Unreachable"); - } - catch - { - } - } - - // Test exception dependency collection via HttpClient. - private async Task RequestValidThatReturnsFailedViaHttpClient() - { - using var request = new HttpClient(); - - // This request will return a 500 error because customerId should be >= 0; - - using var response = await request.GetAsync(this.Url.Content("~/subroute/-1")).ConfigureAwait(false); - - Debug.Assert(response.StatusCode == HttpStatusCode.InternalServerError, "response.StatusCode is InternalServerError"); - } - - // Test successful dependency collection via HttpClient. - private async Task RequestValidThatSpawnsSubSpansViaHttpClient() - { - using var request = new HttpClient(); - - // This request will return successfully and cause a bunch of sub-spans; - - using var response = await request.GetAsync(this.Url.Content("~/subroute/10")).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); - } - } -} diff --git a/examples/AspNet/Examples.AspNet.csproj b/examples/AspNet/Examples.AspNet.csproj deleted file mode 100644 index fe1cd5f22..000000000 --- a/examples/AspNet/Examples.AspNet.csproj +++ /dev/null @@ -1,157 +0,0 @@ - - - - PackageReference - Debug - AnyCPU - - - 2.0 - {9A4E3A68-904B-4835-A3C8-F664B73098DB} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - OpenTelemetry.Exporter.AspNet - OpenTelemetry.Exporter.AspNet - v4.8 - true - - - - - - - - false - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - true - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - Global.asax - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - - - - {99f8a331-05e9-45a5-89ba-4c54e825e5b2} - OpenTelemetry.Api - - - {b9eeacdd-cafa-4b75-a18d-898e7de21b17} - OpenTelemetry.Instrumentation.AspNet - - - {8d47e3cf-9ae3-42fe-9084-feb72d9ad769} - OpenTelemetry.Exporter.Jaeger - - - {412c64d1-43d6-4e4c-8ad8-e20e63b415bd} - OpenTelemetry.Instrumentation.Http - - - {ae3e3df5-4083-4c6e-a840-8271b0acde7e} - OpenTelemetry - - - {1afff251-3b0c-47ca-be94-937083732c0a} - OpenTelemetry.Exporter.Console - - - {7edae7fa-b44e-42ca-80fa-7df2faa2c5dd} - OpenTelemetry.Exporter.Zipkin - - - {a38ac295-2745-4b85-8b6b-dca864cedd5b} - OpenTelemetry.Exporter.OpenTelemetryProtocol - - - {52158a12-e7ef-45a1-859f-06f9b17410cb} - OpenTelemetry.Exporter.Prometheus - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - - - - $(BuildDependsOn) - SkipBuildWithoutVisualStudio - - - - - - True - True - 0 - / - http://localhost:56171/ - False - False - - - False - - - - - diff --git a/examples/AspNet/Global.asax b/examples/AspNet/Global.asax deleted file mode 100644 index e6d835ed4..000000000 --- a/examples/AspNet/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="Examples.AspNet.WebApiApplication" Language="C#" %> diff --git a/examples/AspNet/Global.asax.cs b/examples/AspNet/Global.asax.cs deleted file mode 100644 index 260dda7f9..000000000 --- a/examples/AspNet/Global.asax.cs +++ /dev/null @@ -1,111 +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; -using System.Configuration; -using System.Web; -using System.Web.Http; -using System.Web.Mvc; -using System.Web.Routing; -using OpenTelemetry; -using OpenTelemetry.Exporter; -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; - -namespace Examples.AspNet -{ -#pragma warning disable SA1649 // File name should match first type name - public class WebApiApplication : HttpApplication -#pragma warning restore SA1649 // File name should match first type name - { - private IDisposable tracerProvider; - private IDisposable meterProvider; - - protected void Application_Start() - { - var builder = Sdk.CreateTracerProviderBuilder() - .AddAspNetInstrumentation() - .AddHttpClientInstrumentation(); - - switch (ConfigurationManager.AppSettings["UseExporter"].ToLowerInvariant()) - { - case "jaeger": - builder.AddJaegerExporter(jaegerOptions => - { - jaegerOptions.AgentHost = ConfigurationManager.AppSettings["JaegerHost"]; - jaegerOptions.AgentPort = int.Parse(ConfigurationManager.AppSettings["JaegerPort"]); - }); - break; - case "zipkin": - builder.AddZipkinExporter(zipkinOptions => - { - zipkinOptions.Endpoint = new Uri(ConfigurationManager.AppSettings["ZipkinEndpoint"]); - }); - break; - case "otlp": - builder.AddOtlpExporter(otlpOptions => - { - otlpOptions.Endpoint = new Uri(ConfigurationManager.AppSettings["OtlpEndpoint"]); - }); - break; - default: - builder.AddConsoleExporter(options => options.Targets = ConsoleExporterOutputTargets.Debug); - break; - } - - this.tracerProvider = builder.Build(); - - // Metrics - // Note: Tracerprovider is needed for metrics to work - // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2994 - - var meterBuilder = Sdk.CreateMeterProviderBuilder() - .AddAspNetInstrumentation(); - - switch (ConfigurationManager.AppSettings["UseMetricsExporter"].ToLowerInvariant()) - { - case "otlp": - meterBuilder.AddOtlpExporter(otlpOptions => - { - otlpOptions.Endpoint = new Uri(ConfigurationManager.AppSettings["OtlpEndpoint"]); - }); - break; - case "prometheus": - meterBuilder.AddPrometheusExporter(); - break; - default: - meterBuilder.AddConsoleExporter((exporterOptions, metricReaderOptions) => - { - exporterOptions.Targets = ConsoleExporterOutputTargets.Debug; - }); - break; - } - - this.meterProvider = meterBuilder.Build(); - - GlobalConfiguration.Configure(WebApiConfig.Register); - - AreaRegistration.RegisterAllAreas(); - RouteConfig.RegisterRoutes(RouteTable.Routes); - } - - protected void Application_End() - { - this.tracerProvider?.Dispose(); - this.meterProvider?.Dispose(); - } - } -} diff --git a/examples/AspNet/Properties/AssemblyInfo.cs b/examples/AspNet/Properties/AssemblyInfo.cs deleted file mode 100644 index 7dd41a41a..000000000 --- a/examples/AspNet/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,50 +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.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Examples.AspNet")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Examples.AspNet")] -[assembly: AssemblyCopyright("Copyright @ 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9a4e3a68-904b-4835-a3c8-f664b73098db")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/examples/AspNet/SuppressInstrumentationHttpModule.cs b/examples/AspNet/SuppressInstrumentationHttpModule.cs deleted file mode 100644 index fb8321091..000000000 --- a/examples/AspNet/SuppressInstrumentationHttpModule.cs +++ /dev/null @@ -1,58 +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; -using System.Web; -using OpenTelemetry; - -namespace Examples.AspNet -{ - /// - /// A demo which will suppress ASP.NET - /// instrumentation if a request contains "suppress=true" on the query - /// string. Suppressed spans will not be processed/exported by the - /// OpenTelemetry SDK. - /// - public class SuppressInstrumentationHttpModule : IHttpModule - { - private IDisposable suppressionScope; - - public void Init(HttpApplication context) - { - context.BeginRequest += this.Application_BeginRequest; - context.EndRequest += this.Application_EndRequest; - } - - public void Dispose() - { - } - - private void Application_BeginRequest(object sender, EventArgs e) - { - var context = ((HttpApplication)sender).Context; - - if (context.Request.QueryString["suppress"] == "true") - { - this.suppressionScope = SuppressInstrumentationScope.Begin(); - } - } - - private void Application_EndRequest(object sender, EventArgs e) - { - this.suppressionScope?.Dispose(); - } - } -} diff --git a/examples/AspNet/Views/Home/About.cshtml b/examples/AspNet/Views/Home/About.cshtml deleted file mode 100644 index 4b2d9e844..000000000 --- a/examples/AspNet/Views/Home/About.cshtml +++ /dev/null @@ -1,7 +0,0 @@ -@{ - ViewBag.Title = "About"; -} -

@ViewBag.Title.

-

@ViewBag.Message

- -

Use this area to provide additional information.

diff --git a/examples/AspNet/Views/Home/Index.cshtml b/examples/AspNet/Views/Home/Index.cshtml deleted file mode 100644 index b89b4453d..000000000 --- a/examples/AspNet/Views/Home/Index.cshtml +++ /dev/null @@ -1,31 +0,0 @@ -@{ - ViewBag.Title = "Home Page"; -} - -
-

ASP.NET

-

ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.

-

Learn more »

-
- -
-
-

Getting started

-

- ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that - enables a clean separation of concerns and gives you full control over markup - for enjoyable, agile development. -

-

Learn more »

-
-
-

Get more libraries

-

NuGet is a free Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects.

-

Learn more »

-
-
-

Web Hosting

-

You can easily find a web hosting company that offers the right mix of features and price for your applications.

-

Learn more »

-
-
diff --git a/examples/AspNet/Views/Shared/_Layout.cshtml b/examples/AspNet/Views/Shared/_Layout.cshtml deleted file mode 100644 index 160995d96..000000000 --- a/examples/AspNet/Views/Shared/_Layout.cshtml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - @ViewBag.Title - My ASP.NET Application - - - -
- @RenderBody() -
-
-

© @DateTime.Now.Year - My ASP.NET Application

-
-
- - @RenderSection("scripts", required: false) - - diff --git a/examples/AspNet/Views/Web.config b/examples/AspNet/Views/Web.config deleted file mode 100644 index 6e67a6c67..000000000 --- a/examples/AspNet/Views/Web.config +++ /dev/null @@ -1,42 +0,0 @@ - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/AspNet/Views/_ViewStart.cshtml b/examples/AspNet/Views/_ViewStart.cshtml deleted file mode 100644 index 2de62418c..000000000 --- a/examples/AspNet/Views/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "~/Views/Shared/_Layout.cshtml"; -} diff --git a/examples/AspNet/Web.Debug.config b/examples/AspNet/Web.Debug.config deleted file mode 100644 index 104f153f9..000000000 --- a/examples/AspNet/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - diff --git a/examples/AspNet/Web.Release.config b/examples/AspNet/Web.Release.config deleted file mode 100644 index 63c58428a..000000000 --- a/examples/AspNet/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - diff --git a/examples/AspNet/Web.config b/examples/AspNet/Web.config deleted file mode 100644 index bd4f12f8b..000000000 --- a/examples/AspNet/Web.config +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/AspNetCore/Examples.AspNetCore.csproj b/examples/AspNetCore/Examples.AspNetCore.csproj index a6ce03755..a1f897d4d 100644 --- a/examples/AspNetCore/Examples.AspNetCore.csproj +++ b/examples/AspNetCore/Examples.AspNetCore.csproj @@ -8,6 +8,7 @@ + @@ -18,7 +19,7 @@ - + diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index c507e7df4..ed5a4fdef 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -24,27 +24,28 @@ using OpenTelemetry.Trace; var builder = WebApplication.CreateBuilder(args); -var serviceName = "AspNetCoreExampleService"; - // OpenTelemetry var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown"; // Switch between Zipkin/Jaeger/OTLP by setting UseExporter in appsettings.json. var tracingExporter = builder.Configuration.GetValue("UseTracingExporter").ToLowerInvariant(); -var resourceBuilder = tracingExporter switch +var serviceName = tracingExporter switch { - "jaeger" => ResourceBuilder.CreateDefault().AddService(builder.Configuration.GetValue("Jaeger:ServiceName"), serviceVersion: assemblyVersion, serviceInstanceId: Environment.MachineName), - "zipkin" => ResourceBuilder.CreateDefault().AddService(builder.Configuration.GetValue("Zipkin:ServiceName"), serviceVersion: assemblyVersion, serviceInstanceId: Environment.MachineName), - "otlp" => ResourceBuilder.CreateDefault().AddService(builder.Configuration.GetValue("Otlp:ServiceName"), serviceVersion: assemblyVersion, serviceInstanceId: Environment.MachineName), - _ => ResourceBuilder.CreateDefault().AddService(serviceName, serviceVersion: assemblyVersion, serviceInstanceId: Environment.MachineName), + "jaeger" => builder.Configuration.GetValue("Jaeger:ServiceName"), + "zipkin" => builder.Configuration.GetValue("Zipkin:ServiceName"), + "otlp" => builder.Configuration.GetValue("Otlp:ServiceName"), + _ => "AspNetCoreExampleService", }; +Action configureResource = r => r.AddService( + serviceName, serviceVersion: assemblyVersion, serviceInstanceId: Environment.MachineName); + // Traces builder.Services.AddOpenTelemetryTracing(options => { options - .SetResourceBuilder(resourceBuilder) + .ConfigureResource(configureResource) .SetSampler(new AlwaysOnSampler()) .AddHttpClientInstrumentation() .AddAspNetCoreInstrumentation(); @@ -88,7 +89,7 @@ builder.Logging.ClearProviders(); builder.Logging.AddOpenTelemetry(options => { - options.SetResourceBuilder(resourceBuilder); + options.ConfigureResource(configureResource); var logExporter = builder.Configuration.GetValue("UseLogExporter").ToLowerInvariant(); switch (logExporter) { @@ -112,13 +113,16 @@ builder.Services.Configure(opt => }); // Metrics + +var metricsExporter = builder.Configuration.GetValue("UseMetricsExporter").ToLowerInvariant(); + builder.Services.AddOpenTelemetryMetrics(options => { - options.SetResourceBuilder(resourceBuilder) + options.ConfigureResource(configureResource) + .AddRuntimeInstrumentation() .AddHttpClientInstrumentation() .AddAspNetCoreInstrumentation(); - var metricsExporter = builder.Configuration.GetValue("UseMetricsExporter").ToLowerInvariant(); switch (metricsExporter) { case "prometheus": @@ -159,9 +163,7 @@ app.UseAuthorization(); app.MapControllers(); -var metricsExporter = builder.Configuration.GetValue("UseMetricsExporter").ToLowerInvariant(); - -if (metricsExporter == "prometheus") +if (metricsExporter.Equals("prometheus", StringComparison.OrdinalIgnoreCase)) { app.UseOpenTelemetryPrometheusScrapingEndpoint(); } diff --git a/examples/Console/Examples.Console.csproj b/examples/Console/Examples.Console.csproj index 26556b21e..8239cd4f5 100644 --- a/examples/Console/Examples.Console.csproj +++ b/examples/Console/Examples.Console.csproj @@ -3,7 +3,6 @@ Exe net6.0 - false $(NoWarn),CS0618 @@ -34,7 +33,7 @@ - + diff --git a/examples/Console/TestConsoleExporter.cs b/examples/Console/TestConsoleExporter.cs index 9929beab3..031e6fc4e 100644 --- a/examples/Console/TestConsoleExporter.cs +++ b/examples/Console/TestConsoleExporter.cs @@ -39,7 +39,7 @@ namespace Examples.Console // and use Console exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("Samples.SampleClient", "Samples.SampleServer") - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("console-test")) + .ConfigureResource(res => res.AddService("console-test")) .AddProcessor(new MyProcessor()) // This must be added before ConsoleExporter .AddConsoleExporter() .Build(); diff --git a/examples/Console/TestHttpClient.cs b/examples/Console/TestHttpClient.cs index 734cae06b..ba122c5f0 100644 --- a/examples/Console/TestHttpClient.cs +++ b/examples/Console/TestHttpClient.cs @@ -34,7 +34,7 @@ namespace Examples.Console using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddHttpClientInstrumentation() - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("http-service-example")) + .ConfigureResource(r => r.AddService("http-service-example")) .AddSource("http-client-test") .AddConsoleExporter() .Build(); diff --git a/examples/Console/TestInMemoryExporter.cs b/examples/Console/TestInMemoryExporter.cs index f46ed608b..5cfbe3274 100644 --- a/examples/Console/TestInMemoryExporter.cs +++ b/examples/Console/TestInMemoryExporter.cs @@ -51,7 +51,7 @@ namespace Examples.Console // and use InMemory exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("Samples.SampleClient", "Samples.SampleServer") - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("inmemory-test")) + .ConfigureResource(r => r.AddService("inmemory-test")) .AddInMemoryExporter(exportedItems) .Build(); diff --git a/examples/Console/TestJaegerExporter.cs b/examples/Console/TestJaegerExporter.cs index 2c67e9bff..aca390988 100644 --- a/examples/Console/TestJaegerExporter.cs +++ b/examples/Console/TestJaegerExporter.cs @@ -56,7 +56,7 @@ namespace Examples.Console // Enable OpenTelemetry for the sources "Samples.SampleServer" and "Samples.SampleClient" // and use the Jaeger exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("jaeger-test")) + .ConfigureResource(r => r.AddService("jaeger-test")) .AddSource("Samples.SampleClient", "Samples.SampleServer") .AddJaegerExporter(o => { diff --git a/examples/Console/TestMetrics.cs b/examples/Console/TestMetrics.cs index 35fc3dd13..78ee9cbed 100644 --- a/examples/Console/TestMetrics.cs +++ b/examples/Console/TestMetrics.cs @@ -33,7 +33,7 @@ namespace Examples.Console using var meter = new Meter("TestMeter"); var providerBuilder = Sdk.CreateMeterProviderBuilder() - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("myservice")) + .ConfigureResource(r => r.AddService("myservice")) .AddMeter(meter.Name); // All instruments from this meter are enabled. if (options.UseExporter.Equals("otlp", StringComparison.OrdinalIgnoreCase)) diff --git a/examples/Console/TestOTelShimWithConsoleExporter.cs b/examples/Console/TestOTelShimWithConsoleExporter.cs index 984ac7ad3..31bb3c85f 100644 --- a/examples/Console/TestOTelShimWithConsoleExporter.cs +++ b/examples/Console/TestOTelShimWithConsoleExporter.cs @@ -28,7 +28,7 @@ namespace Examples.Console // and use a single pipeline with a custom MyProcessor, and Console exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("MyCompany.MyProduct.MyWebServer") - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyServiceName")) + .ConfigureResource(r => r.AddService("MyServiceName")) .AddConsoleExporter() .Build(); diff --git a/examples/Console/TestOpenTracingShim.cs b/examples/Console/TestOpenTracingShim.cs index f0e967992..7361c3aa4 100644 --- a/examples/Console/TestOpenTracingShim.cs +++ b/examples/Console/TestOpenTracingShim.cs @@ -31,7 +31,7 @@ namespace Examples.Console // and use Console exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("MyCompany.MyProduct.MyWebServer") - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyServiceName")) + .ConfigureResource(r => r.AddService("MyServiceName")) .AddConsoleExporter() .Build(); diff --git a/examples/Console/TestOtlpExporter.cs b/examples/Console/TestOtlpExporter.cs index 3936f1b0e..f5417e77c 100644 --- a/examples/Console/TestOtlpExporter.cs +++ b/examples/Console/TestOtlpExporter.cs @@ -71,7 +71,7 @@ namespace Examples.Console // and use OTLP exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("Samples.SampleClient", "Samples.SampleServer") - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("otlp-test")) + .ConfigureResource(r => r.AddService("otlp-test")) .AddOtlpExporter(opt => { // If endpoint was not specified, the proper one will be selected according to the protocol. diff --git a/examples/Console/TestPrometheusExporter.cs b/examples/Console/TestPrometheusExporter.cs index 935e25300..befb0e5d4 100644 --- a/examples/Console/TestPrometheusExporter.cs +++ b/examples/Console/TestPrometheusExporter.cs @@ -51,12 +51,8 @@ internal class TestPrometheusExporter using var meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter(MyMeter.Name) .AddMeter(MyMeter2.Name) - .AddPrometheusExporter(options => - { - options.StartHttpListener = true; - options.HttpListenerPrefixes = new string[] { $"http://localhost:{port}/" }; - options.ScrapeResponseCacheDurationMilliseconds = 0; - }) + .AddPrometheusHttpListener( + options => options.UriPrefixes = new string[] { $"http://localhost:{port}/" }) .Build(); var process = Process.GetCurrentProcess(); diff --git a/examples/Console/TestZipkinExporter.cs b/examples/Console/TestZipkinExporter.cs index 40c44551b..0f6bbce22 100644 --- a/examples/Console/TestZipkinExporter.cs +++ b/examples/Console/TestZipkinExporter.cs @@ -39,7 +39,7 @@ namespace Examples.Console // and use the Zipkin exporter. using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddSource("Samples.SampleClient", "Samples.SampleServer") - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("zipkin-test")) + .ConfigureResource(r => r.AddService("zipkin-test")) .AddZipkinExporter(o => { o.Endpoint = new Uri(zipkinUri); diff --git a/examples/GrpcService/Startup.cs b/examples/GrpcService/Startup.cs index a2d1adf7e..c7034011b 100644 --- a/examples/GrpcService/Startup.cs +++ b/examples/GrpcService/Startup.cs @@ -45,7 +45,7 @@ namespace Examples.GrpcService { case "jaeger": services.AddOpenTelemetryTracing((builder) => builder - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(this.Configuration.GetValue("Jaeger:ServiceName"))) + .ConfigureResource(r => r.AddService(this.Configuration.GetValue("Jaeger:ServiceName"))) .AddAspNetCoreInstrumentation() .AddJaegerExporter(jaegerOptions => { diff --git a/examples/AspNet/App_Start/RouteConfig.cs b/examples/LoggingExtensions/ExampleEventSource.cs similarity index 52% rename from examples/AspNet/App_Start/RouteConfig.cs rename to examples/LoggingExtensions/ExampleEventSource.cs index e1c6ae4df..7fa0b8cea 100644 --- a/examples/AspNet/App_Start/RouteConfig.cs +++ b/examples/LoggingExtensions/ExampleEventSource.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,23 +14,20 @@ // limitations under the License. // -using System.Web.Mvc; -using System.Web.Routing; +using System.Diagnostics.Tracing; -namespace Examples.AspNet +namespace Examples.LoggingExtensions; + +[EventSource(Name = EventSourceName)] +internal sealed class ExampleEventSource : EventSource { - public class RouteConfig + public const string EventSourceName = "OpenTelemetry-ExampleEventSource"; + + public static ExampleEventSource Log { get; } = new(); + + [Event(1, Message = "Example event written with '{0}' reason", Level = EventLevel.Informational)] + public void ExampleEvent(string reason) { - public static void RegisterRoutes(RouteCollection routes) - { - routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); - - routes.MapMvcAttributeRoutes(); - - routes.MapRoute( - name: "Default", - url: "{controller}/{action}/{id}", - defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }); - } + this.WriteEvent(1, reason); } } diff --git a/examples/LoggingExtensions/Examples.LoggingExtensions.csproj b/examples/LoggingExtensions/Examples.LoggingExtensions.csproj new file mode 100644 index 000000000..892a652ac --- /dev/null +++ b/examples/LoggingExtensions/Examples.LoggingExtensions.csproj @@ -0,0 +1,16 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + diff --git a/examples/LoggingExtensions/Program.cs b/examples/LoggingExtensions/Program.cs new file mode 100644 index 000000000..24ec26ead --- /dev/null +++ b/examples/LoggingExtensions/Program.cs @@ -0,0 +1,58 @@ +// +// 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.Tracing; +using Examples.LoggingExtensions; +using OpenTelemetry.Logs; +using OpenTelemetry.Resources; +using Serilog; + +var resourceBuilder = ResourceBuilder.CreateDefault().AddService("Examples.LoggingExtensions"); + +var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => +{ + options.IncludeFormattedMessage = true; + options + .SetResourceBuilder(resourceBuilder) + .AddConsoleExporter(); +}); + +// Creates an OpenTelemetryEventSourceLogEmitter for routing ExampleEventSource +// events into logs +using var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, // <- Events will be written to openTelemetryLoggerProvider + (name) => name == ExampleEventSource.EventSourceName ? EventLevel.Informational : null, + disposeProvider: false); // <- Do not dispose the provider with OpenTelemetryEventSourceLogEmitter since in this case it is shared with Serilog + +// Configure Serilog global logger +Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry( + openTelemetryLoggerProvider, // <- Register OpenTelemetry Serilog sink writing to openTelemetryLoggerProvider + disposeProvider: false) // <- Do not dispose the provider with Serilog since in this case it is shared with OpenTelemetryEventSourceLogEmitter + .CreateLogger(); + +ExampleEventSource.Log.ExampleEvent("Startup complete"); + +// Note: Serilog ForContext API is used to set "CategoryName" on log messages +ILogger programLogger = Log.Logger.ForContext(); + +programLogger.Information("Application started {Greeting} {Location}", "Hello", "World"); + +// Note: For Serilog this call flushes all logs +Log.CloseAndFlush(); + +// Manually dispose OpenTelemetryLoggerProvider since it is being shared +openTelemetryLoggerProvider.Dispose(); diff --git a/examples/LoggingExtensions/README.md b/examples/LoggingExtensions/README.md new file mode 100644 index 000000000..adbb1df5b --- /dev/null +++ b/examples/LoggingExtensions/README.md @@ -0,0 +1,10 @@ +# OpenTelemetry Logging Extensions Example + +This project contains examples of the `LogEmitter` API being used to extend +existing logging platforms to write into OpenTelemetry logs. + +* Serilog: Using OpenTelemetry.Extensions.Serilog + +## References + +* [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Api/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Api/.publicApi/net462/PublicAPI.Unshipped.txt index e69de29bb..7883a7d9c 100644 --- a/src/OpenTelemetry.Api/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Api/.publicApi/net462/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +static OpenTelemetry.Trace.ActivityExtensions.RecordException(this System.Diagnostics.Activity activity, System.Exception ex, in System.Diagnostics.TagList tags) -> void diff --git a/src/OpenTelemetry.Api/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Api/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index e69de29bb..7883a7d9c 100644 --- a/src/OpenTelemetry.Api/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Api/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +static OpenTelemetry.Trace.ActivityExtensions.RecordException(this System.Diagnostics.Activity activity, System.Exception ex, in System.Diagnostics.TagList tags) -> void diff --git a/src/OpenTelemetry.Api/CHANGELOG.md b/src/OpenTelemetry.Api/CHANGELOG.md index 26664dcb8..d17ef3eb0 100644 --- a/src/OpenTelemetry.Api/CHANGELOG.md +++ b/src/OpenTelemetry.Api/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 1.4.0-alpha.1 + +Released 2022-Aug-02 + +* Add `Activity.RecordException` overload accepting additional attributes to + add to the `ActivityEvent`. + [#3433](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3433) + ## 1.3.0 Released 2022-Jun-03 @@ -159,7 +167,7 @@ Released 2021-Jan-29 the `Status` (otel.status_code) tag (added on `Activity` using the `SetStatus` extension) will now be set as the `UNSET`, `OK`, or `ERROR` string representation instead of the `0`, `1`, or `2` integer representation. - ([#1579](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1579) & + ([#1579](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1579) [#1620](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1620)) * Metrics API/SDK support is in an experimental state and is not recommended for production use. All metric APIs have been marked with the `Obsolete` @@ -262,7 +270,7 @@ Released 2020-08-28 header ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048)) * Removed `DistributedContext` as it is no longer part of the spec - ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048))) + ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048)) * Renaming from `ot` to `otel` ([#1046](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1046)) * Added `RuntimeContext` API diff --git a/src/OpenTelemetry.Api/Internal/ActivityHelperExtensions.cs b/src/OpenTelemetry.Api/Internal/ActivityHelperExtensions.cs index 6cc238b8c..e3a2c3120 100644 --- a/src/OpenTelemetry.Api/Internal/ActivityHelperExtensions.cs +++ b/src/OpenTelemetry.Api/Internal/ActivityHelperExtensions.cs @@ -44,7 +44,7 @@ namespace OpenTelemetry.Trace ActivityStatusTagEnumerator state = default; - ActivityTagsEnumeratorFactory.Enumerate(activity, ref state); + ActivityTagsEnumeratorFactory.Enumerate(activity, ref state, null); if (!state.StatusCode.HasValue) { @@ -72,7 +72,7 @@ namespace OpenTelemetry.Trace ActivitySingleTagEnumerator state = new ActivitySingleTagEnumerator(tagName); - ActivityTagsEnumeratorFactory.Enumerate(activity, ref state); + ActivityTagsEnumeratorFactory.Enumerate(activity, ref state, null); return state.Value; } @@ -91,7 +91,7 @@ namespace OpenTelemetry.Trace ActivityFirstTagEnumerator state = new ActivityFirstTagEnumerator(tagName); - ActivityTagsEnumeratorFactory.Enumerate(activity, ref state); + ActivityTagsEnumeratorFactory.Enumerate(activity, ref state, null); if (state.Value == null) { @@ -109,14 +109,15 @@ namespace OpenTelemetry.Trace /// The struct implementation to use for the enumeration. /// Activity instance. /// Tag enumerator. + /// Maximum number of tags to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")] - public static void EnumerateTags(this Activity activity, ref T tagEnumerator) + public static void EnumerateTags(this Activity activity, ref T tagEnumerator, int? maxTags = null) where T : struct, IActivityEnumerator> { Debug.Assert(activity != null, "Activity should not be null"); - ActivityTagsEnumeratorFactory.Enumerate(activity, ref tagEnumerator); + ActivityTagsEnumeratorFactory.Enumerate(activity, ref tagEnumerator, maxTags); } /// @@ -125,14 +126,15 @@ namespace OpenTelemetry.Trace /// The struct implementation to use for the enumeration. /// Activity instance. /// Link enumerator. + /// Maximum number of links to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")] - public static void EnumerateLinks(this Activity activity, ref T linkEnumerator) + public static void EnumerateLinks(this Activity activity, ref T linkEnumerator, int? maxLinks = null) where T : struct, IActivityEnumerator { Debug.Assert(activity != null, "Activity should not be null"); - ActivityLinksEnumeratorFactory.Enumerate(activity, ref linkEnumerator); + ActivityLinksEnumeratorFactory.Enumerate(activity, ref linkEnumerator, maxLinks); } /// @@ -141,12 +143,13 @@ namespace OpenTelemetry.Trace /// The struct implementation to use for the enumeration. /// ActivityLink instance. /// Tag enumerator. + /// Maximum number of tags to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")] - public static void EnumerateTags(this ActivityLink activityLink, ref T tagEnumerator) + public static void EnumerateTags(this ActivityLink activityLink, ref T tagEnumerator, int? maxTags = null) where T : struct, IActivityEnumerator> { - ActivityTagsEnumeratorFactory.Enumerate(activityLink, ref tagEnumerator); + ActivityTagsEnumeratorFactory.Enumerate(activityLink, ref tagEnumerator, maxTags); } /// @@ -155,14 +158,15 @@ namespace OpenTelemetry.Trace /// The struct implementation to use for the enumeration. /// Activity instance. /// Event enumerator. + /// Maximum number of events to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")] - public static void EnumerateEvents(this Activity activity, ref T eventEnumerator) + public static void EnumerateEvents(this Activity activity, ref T eventEnumerator, int? maxEvents = null) where T : struct, IActivityEnumerator { Debug.Assert(activity != null, "Activity should not be null"); - ActivityEventsEnumeratorFactory.Enumerate(activity, ref eventEnumerator); + ActivityEventsEnumeratorFactory.Enumerate(activity, ref eventEnumerator, maxEvents); } /// @@ -171,12 +175,13 @@ namespace OpenTelemetry.Trace /// The struct implementation to use for the enumeration. /// ActivityEvent instance. /// Tag enumerator. + /// Maximum number of tags to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")] - public static void EnumerateTags(this ActivityEvent activityEvent, ref T tagEnumerator) + public static void EnumerateTags(this ActivityEvent activityEvent, ref T tagEnumerator, int? maxTags = null) where T : struct, IActivityEnumerator> { - ActivityTagsEnumeratorFactory.Enumerate(activityEvent, ref tagEnumerator); + ActivityTagsEnumeratorFactory.Enumerate(activityEvent, ref tagEnumerator, maxTags); } private struct ActivitySingleTagEnumerator : IActivityEnumerator> @@ -265,7 +270,7 @@ namespace OpenTelemetry.Trace private static readonly DictionaryEnumerator.ForEachDelegate ForEachTagValueCallbackRef = ForEachTagValueCallback; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Enumerate(Activity activity, ref TState state) + public static void Enumerate(Activity activity, ref TState state, int? maxTags) { var tagObjects = activity.TagObjects; @@ -274,6 +279,12 @@ namespace OpenTelemetry.Trace return; } + if (maxTags.HasValue) + { + SkipAllocationFreeEnumeration(tagObjects, ref state, maxTags.Value); + return; + } + ActivityTagObjectsEnumerator( tagObjects, ref state, @@ -281,7 +292,7 @@ namespace OpenTelemetry.Trace } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Enumerate(ActivityLink activityLink, ref TState state) + public static void Enumerate(ActivityLink activityLink, ref TState state, int? maxTags) { var tags = activityLink.Tags; @@ -290,6 +301,12 @@ namespace OpenTelemetry.Trace return; } + if (maxTags.HasValue) + { + SkipAllocationFreeEnumeration(tags, ref state, maxTags.Value); + return; + } + ActivityTagsCollectionEnumerator( tags, ref state, @@ -297,7 +314,7 @@ namespace OpenTelemetry.Trace } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Enumerate(ActivityEvent activityEvent, ref TState state) + public static void Enumerate(ActivityEvent activityEvent, ref TState state, int? maxTags) { var tags = activityEvent.Tags; @@ -306,12 +323,31 @@ namespace OpenTelemetry.Trace return; } + if (maxTags.HasValue) + { + SkipAllocationFreeEnumeration(tags, ref state, maxTags.Value); + return; + } + ActivityTagsCollectionEnumerator( tags, ref state, ForEachTagValueCallbackRef); } + // TODO: When a limit has been configured an allocation-free enumerator is not used. + // Need to either: + // 1) modify the dynamically generated code to only enumerate up to the max number of items, or + // 2) wait until .NET 7 is released and do this more easily with the new enumerator functions + private static void SkipAllocationFreeEnumeration(IEnumerable> tags, ref TState state, int maxTags) + { + var enumerator = tags.GetEnumerator(); + for (var i = 0; enumerator.MoveNext() && i < maxTags; ++i) + { + state.ForEach(enumerator.Current); + } + } + private static bool ForEachTagValueCallback(ref TState state, KeyValuePair item) => state.ForEach(item); } @@ -328,7 +364,7 @@ namespace OpenTelemetry.Trace private static readonly ListEnumerator.ForEachDelegate ForEachLinkCallbackRef = ForEachLinkCallback; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Enumerate(Activity activity, ref TState state) + public static void Enumerate(Activity activity, ref TState state, int? maxLinks) { var activityLinks = activity.Links; @@ -337,6 +373,21 @@ namespace OpenTelemetry.Trace return; } + // TODO: When a limit has been configured an allocation-free enumerator is not used. + // Need to either: + // 1) modify the dynamically generated code to only enumerate up to the max number of items, or + // 2) wait until .NET 7 is released and do this more easily with the new enumerator functions + if (maxLinks.HasValue) + { + var enumerator = activityLinks.GetEnumerator(); + for (var i = 0; enumerator.MoveNext() && i < maxLinks; ++i) + { + state.ForEach(enumerator.Current); + } + + return; + } + ActivityLinksEnumerator( activityLinks, ref state, @@ -359,7 +410,7 @@ namespace OpenTelemetry.Trace private static readonly ListEnumerator.ForEachDelegate ForEachEventCallbackRef = ForEachEventCallback; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Enumerate(Activity activity, ref TState state) + public static void Enumerate(Activity activity, ref TState state, int? maxEvents) { var activityEvents = activity.Events; @@ -368,6 +419,21 @@ namespace OpenTelemetry.Trace return; } + // TODO: When a limit has been configured an allocation-free enumerator is not used. + // Need to either: + // 1) modify the dynamically generated code to only enumerate up to the max number of items, or + // 2) wait until .NET 7 is released and do this more easily with the new enumerator functions + if (maxEvents.HasValue) + { + var enumerator = activityEvents.GetEnumerator(); + for (var i = 0; enumerator.MoveNext() && i < maxEvents; ++i) + { + state.ForEach(enumerator.Current); + } + + return; + } + ActivityEventsEnumerator( activityEvents, ref state, diff --git a/src/OpenTelemetry.Api/Internal/Guard.cs b/src/OpenTelemetry.Api/Internal/Guard.cs index 1b6348cd8..f5b4235f1 100644 --- a/src/OpenTelemetry.Api/Internal/Guard.cs +++ b/src/OpenTelemetry.Api/Internal/Guard.cs @@ -25,6 +25,9 @@ namespace System.Runtime.CompilerServices /// /// Allows capturing of the expressions passed to a method. /// + /// + /// Borrowed from: . + /// [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] #pragma warning disable SA1402 // File may only contain a single type #pragma warning disable SA1649 // File name should match first type name diff --git a/src/OpenTelemetry.Api/README.md b/src/OpenTelemetry.Api/README.md index ab11fd6d7..26da26442 100644 --- a/src/OpenTelemetry.Api/README.md +++ b/src/OpenTelemetry.Api/README.md @@ -153,8 +153,7 @@ required only for the following scenarios: [Propagators](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md), to inject and extract context data. Some of the most common libraries requiring this include - [HttpClient](../OpenTelemetry.Instrumentation.Http/README.md), - [ASP.NET](../OpenTelemetry.Instrumentation.AspNet/README.md), [ASP.NET + [HttpClient](../OpenTelemetry.Instrumentation.Http/README.md), [ASP.NET Core](../OpenTelemetry.Instrumentation.AspNetCore/README.md). This repo already provides instrumentation for these common libraries. If your library is not built on top of these, and want to leverage propagators, follow the @@ -394,8 +393,8 @@ OpenTelemetry defines a concept called [Status](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status) to be associated with `Activity`. Starting with [DiagnosticSource 6.0](https://www.nuget.org/packages/System.Diagnostics.DiagnosticSource/6.0.0), -`SetStatus` API on `Activity` can be used to set the status and description -as shown below: +`SetStatus` API on `Activity` can be used to set the status and description as +shown below: ```csharp activity?.SetStatus(ActivityStatusCode.Ok); @@ -467,16 +466,16 @@ runtime itself, as part of the package. This means, users can instrument their applications/libraries to emit metrics by simply using the `System.Diagnostics.DiagnosticSource` package. This package can be used in applications targeting any of the officially supported -versions of [.NET](https://dotnet.microsoft.com/download/dotnet) and -[.NET Framework](https://dotnet.microsoft.com/download/dotnet-framework) (an -older Windows-based .NET implementation). +versions of [.NET](https://dotnet.microsoft.com/download/dotnet) and [.NET +Framework](https://dotnet.microsoft.com/download/dotnet-framework) (an older +Windows-based .NET implementation). ## Instrumenting a library/application with .NET Metrics API ### Basic metric usage -1. Install the `System.Diagnostics.DiagnosticSource` package version - `6.0.0` or above to your application or library. +1. Install the `System.Diagnostics.DiagnosticSource` package version `6.0.0` or + above to your application or library. ```xml @@ -496,9 +495,8 @@ older Windows-based .NET implementation). The above requires import of the `System.Diagnostics.Metrics` namespace. - **Note:** - It is important to note that `Meter` instances are created by using its - constructor, and *not* by calling a `GetMeter` method on the + **Note:** It is important to note that `Meter` instances are created by + using its constructor, and *not* by calling a `GetMeter` method on the `MeterProvider`. This is an important distinction from the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#get-a-meter), where `Meter`s are obtained from `MeterProvider`. @@ -528,8 +526,7 @@ describes more kinds of instruments. This component uses an [EventSource](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventsource) -with the name "OpenTelemetry-Api" for its internal logging. -Please refer to [SDK +with the name "OpenTelemetry-Api" for its internal logging. Please refer to [SDK troubleshooting](../OpenTelemetry/README.md#troubleshooting) for instructions on seeing these internal logs. diff --git a/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs b/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs index a358b689d..fc3738311 100644 --- a/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs +++ b/src/OpenTelemetry.Api/Trace/ActivityExtensions.cs @@ -69,14 +69,26 @@ namespace OpenTelemetry.Trace } /// - /// Record Exception. + /// Adds an activity event containing information from the specified exception. /// /// Activity instance. /// Exception to be recorded. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RecordException(this Activity activity, Exception ex) { - if (ex == null) + activity?.RecordException(ex, default); + } + + /// + /// Adds an activity event containing information from the specified exception and additional tags. + /// + /// Activity instance. + /// Exception to be recorded. + /// Additional tags to record on the event. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RecordException(this Activity activity, Exception ex, in TagList tags) + { + if (ex == null || activity == null) { return; } @@ -92,7 +104,12 @@ namespace OpenTelemetry.Trace tagsCollection.Add(SemanticConventions.AttributeExceptionMessage, ex.Message); } - activity?.AddEvent(new ActivityEvent(SemanticConventions.AttributeExceptionEventName, default, tagsCollection)); + foreach (var tag in tags) + { + tagsCollection[tag.Key] = tag.Value; + } + + activity.AddEvent(new ActivityEvent(SemanticConventions.AttributeExceptionEventName, default, tagsCollection)); } } } diff --git a/src/OpenTelemetry.Exporter.Console/CHANGELOG.md b/src/OpenTelemetry.Exporter.Console/CHANGELOG.md index 227e032b5..fde2a3c42 100644 --- a/src/OpenTelemetry.Exporter.Console/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Console/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +## 1.4.0-alpha.1 + +Released 2022-Aug-02 + +* The `MetricReaderOptions` defaults can be overridden using + `OTEL_METRIC_EXPORT_INTERVAL` and `OTEL_METRIC_EXPORT_TIMEOUT` + environmental variables as defined in the + [specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.12.0/specification/sdk-environment-variables.md#periodic-exporting-metricreader). + ([#3424](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3424)) + ## 1.3.0 Released 2022-Jun-03 @@ -51,7 +61,7 @@ Released 2022-Mar-30 * Added StatusCode, StatusDescription support to `ConsoleActivityExporter`. ([#2929](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2929) - [#3061](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3061)) + [#3061](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3061)) * `AddConsoleExporter` extension method by default sets up exporter to export metrics every 10 seconds. diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs index 2e3731e1d..d538c59fb 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs @@ -42,7 +42,11 @@ namespace OpenTelemetry.Exporter this.WriteLine($"{"LogRecord.TraceFlags:",-RightPaddingLength}{logRecord.TraceFlags}"); } - this.WriteLine($"{"LogRecord.CategoryName:",-RightPaddingLength}{logRecord.CategoryName}"); + if (logRecord.CategoryName != null) + { + this.WriteLine($"{"LogRecord.CategoryName:",-RightPaddingLength}{logRecord.CategoryName}"); + } + this.WriteLine($"{"LogRecord.LogLevel:",-RightPaddingLength}{logRecord.LogLevel}"); if (logRecord.FormattedMessage != null) @@ -62,7 +66,7 @@ namespace OpenTelemetry.Exporter // Special casing {OriginalFormat} // See https://github.com/open-telemetry/opentelemetry-dotnet/pull/3182 // for explanation. - var valueToTransform = logRecord.StateValues[i].Key.Equals("{OriginalValue}") + var valueToTransform = logRecord.StateValues[i].Key.Equals("{OriginalFormat}") ? new KeyValuePair("OriginalFormat (a.k.a Body)", logRecord.StateValues[i].Value) : logRecord.StateValues[i]; @@ -76,13 +80,13 @@ namespace OpenTelemetry.Exporter if (logRecord.EventId != default) { this.WriteLine($"{"LogRecord.EventId:",-RightPaddingLength}{logRecord.EventId.Id}"); - if (string.IsNullOrEmpty(logRecord.EventId.Name)) + if (!string.IsNullOrEmpty(logRecord.EventId.Name)) { this.WriteLine($"{"LogRecord.EventName:",-RightPaddingLength}{logRecord.EventId.Name}"); } } - if (logRecord.Exception is { }) + if (logRecord.Exception != null) { this.WriteLine($"{"LogRecord.Exception:",-RightPaddingLength}{logRecord.Exception?.Message}"); } diff --git a/src/OpenTelemetry.Exporter.Console/OpenTelemetry.Exporter.Console.csproj b/src/OpenTelemetry.Exporter.Console/OpenTelemetry.Exporter.Console.csproj index 028440a58..d0f69330b 100644 --- a/src/OpenTelemetry.Exporter.Console/OpenTelemetry.Exporter.Console.csproj +++ b/src/OpenTelemetry.Exporter.Console/OpenTelemetry.Exporter.Console.csproj @@ -21,6 +21,7 @@ + diff --git a/src/OpenTelemetry.Exporter.Console/README.md b/src/OpenTelemetry.Exporter.Console/README.md index 309f7718d..440e5d22a 100644 --- a/src/OpenTelemetry.Exporter.Console/README.md +++ b/src/OpenTelemetry.Exporter.Console/README.md @@ -23,6 +23,30 @@ used: * [Metrics](../../docs/metrics/getting-started/Program.cs) * [Traces](../../docs/trace/getting-started/Program.cs) +## Configuration + +See the +[`TestConsoleExporter.cs`](../../examples/Console/TestConsoleExporter.cs) for +an example of how to use the exporter for exporting traces to a collection. + +You can configure the `ConsoleExporter` through `Options` types properties +and environment variables. +The `Options` type setters take precedence over the environment variables. + +## Environment Variables + +The following environment variables can be used to override the default +values of the `PeriodicExportingMetricReaderOptions` +(following the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.12.0/specification/sdk-environment-variables.md#periodic-exporting-metricreader). + +| Environment variable | `PeriodicExportingMetricReaderOptions` property | +| ------------------------------| ------------------------------------------------| +| `OTEL_METRIC_EXPORT_INTERVAL` | `ExportIntervalMilliseconds` | +| `OTEL_METRIC_EXPORT_TIMEOUT` | `ExportTimeoutMilliseconds` | + +`FormatException` is thrown in case of an invalid value for any of the +supported environment variables. + ## References * [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md b/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md index 17fa5bd8c..9cd73b34a 100644 --- a/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md @@ -2,6 +2,19 @@ ## Unreleased +## 1.4.0-alpha.1 + +Released 2022-Aug-02 + +* `InMemoryExporter` will now buffer scopes when exporting `LogRecord` + ([#3360](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3360)) + +* The `MetricReaderOptions` defaults can be overridden using + `OTEL_METRIC_EXPORT_INTERVAL` and `OTEL_METRIC_EXPORT_TIMEOUT` + environmental variables as defined in the + [specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.12.0/specification/sdk-environment-variables.md#periodic-exporting-metricreader). + ([#3424](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3424)) + ## 1.3.0 Released 2022-Jun-03 diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs index 8dab7b51d..9715119d6 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs @@ -14,7 +14,6 @@ // limitations under the License. // -using System; using System.Collections.Generic; namespace OpenTelemetry.Exporter @@ -23,19 +22,21 @@ namespace OpenTelemetry.Exporter where T : class { private readonly ICollection exportedItems; - private readonly Func, ExportResult> onExport; + private readonly ExportFunc onExport; public InMemoryExporter(ICollection exportedItems) { this.exportedItems = exportedItems; - this.onExport = (Batch batch) => this.DefaultExport(batch); + this.onExport = this.DefaultExport; } - internal InMemoryExporter(Func, ExportResult> exportFunc) + internal InMemoryExporter(ExportFunc exportFunc) { this.onExport = exportFunc; } + internal delegate ExportResult ExportFunc(in Batch batch); + public override ExportResult Export(in Batch batch) => this.onExport(batch); private ExportResult DefaultExport(in Batch batch) diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs index 9cbde894c..6e10e5bd6 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs @@ -27,7 +27,25 @@ namespace OpenTelemetry.Logs Guard.ThrowIfNull(loggerOptions); Guard.ThrowIfNull(exportedItems); - return loggerOptions.AddProcessor(new SimpleLogRecordExportProcessor(new InMemoryExporter(exportedItems))); + var logExporter = new InMemoryExporter( + exportFunc: (in Batch batch) => ExportLogRecord(in batch, exportedItems)); + + return loggerOptions.AddProcessor(new SimpleLogRecordExportProcessor(logExporter)); + } + + private static ExportResult ExportLogRecord(in Batch batch, ICollection exportedItems) + { + if (exportedItems == null) + { + return ExportResult.Failure; + } + + foreach (var log in batch) + { + exportedItems.Add(log.Copy()); + } + + return ExportResult.Success; } } } diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs index f817f5744..613067d5e 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs @@ -145,7 +145,7 @@ namespace OpenTelemetry.Metrics configureMetricReader?.Invoke(metricReaderOptions); var metricExporter = new InMemoryExporter( - exportFunc: metricBatch => ExportMetricSnapshot(metricBatch, exportedItems)); + exportFunc: (in Batch metricBatch) => ExportMetricSnapshot(in metricBatch, exportedItems)); var metricReader = PeriodicExportingMetricReaderHelper.CreatePeriodicExportingMetricReader( metricExporter, diff --git a/src/OpenTelemetry.Exporter.InMemory/README.md b/src/OpenTelemetry.Exporter.InMemory/README.md index d886ecb35..30ef04f1c 100644 --- a/src/OpenTelemetry.Exporter.InMemory/README.md +++ b/src/OpenTelemetry.Exporter.InMemory/README.md @@ -17,6 +17,24 @@ See the [`TestInMemoryExporter.cs`](../../examples/Console/TestInMemoryExporter.cs) for an example of how to use the exporter for exporting traces to a collection. +You can configure the `InMemoryExporter` through `Options` types properties +and environment variables. +The `Options` type setters take precedence over the environment variables. + +## Environment Variables + +The following environment variables can be used to override the default +values of the `PeriodicExportingMetricReaderOptions` +(following the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.12.0/specification/sdk-environment-variables.md#periodic-exporting-metricreader). + +| Environment variable | `PeriodicExportingMetricReaderOptions` property | +| ------------------------------| ------------------------------------------------| +| `OTEL_METRIC_EXPORT_INTERVAL` | `ExportIntervalMilliseconds` | +| `OTEL_METRIC_EXPORT_TIMEOUT` | `ExportTimeoutMilliseconds` | + +`FormatException` is thrown in case of an invalid value for any of the +supported environment variables. + ## References * [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md b/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md index 78402461d..b136e59c4 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.4.0-alpha.1 + +Released 2022-Aug-02 + ## 1.3.0 Released 2022-Jun-03 @@ -209,11 +213,11 @@ Released 2021-Jan-29 Simple exporter, and settings for batch exporting properties. * Jaeger will now set the `error` tag when `otel.status_code` is set to `ERROR`. - ([#1579](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1579) & + ([#1579](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1579) [#1620](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1620)) * Jaeger will no longer send the `otel.status_code` tag if the value is `UNSET`. - ([#1609](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1609) & + ([#1609](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1609) [#1620](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1620)) * Span Event.Name will now be populated as the `event` field on Jaeger Logs @@ -265,14 +269,14 @@ Released 2020-Sep-15 Released 2020-08-28 -* Changed `JaegerExporter` to use `BatchExportActivityProcessor` by default +* Changed `JaegerExporter` to use `BatchExportActivityProcessor` by default. ([#1125](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1125)) * Span links will now be sent as `FOLLOWS_FROM` reference type. Previously they were sent as `CHILD_OF`. ([#970](https://github.com/open-telemetry/opentelemetry-dotnet/pull/970)) * Fixed issue when span has both the `net.peer.name` and `net.peer.port` - attributes but did not include `net.peer.port` in the `peer.service` field - ([#1195](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1195)). + attributes but did not include `net.peer.port` in the `peer.service` field. + ([#1195](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1195)) * Renamed extension method from `UseJaegerExporter` to `AddJaegerExporter`. diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs/CHANGELOG.md index 8a0a3f260..22bad6453 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.4.0-alpha.1 + +Released 2022-Aug-02 + ## 1.3.0-rc.2 Released 2022-June-1 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index d90417bb3..a68b5b949 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,6 +2,22 @@ ## Unreleased +## 1.4.0-alpha.1 + +Released 2022-Aug-02 + +* Adds support for limiting the length and count of attributes exported from + the OTLP exporter. These + [Attribute Limits](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#attribute-limits) + are configured via the environment variables defined in the specification. + ([#3376](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3376)) + +* The `MetricReaderOptions` defaults can be overridden using + `OTEL_METRIC_EXPORT_INTERVAL` and `OTEL_METRIC_EXPORT_TIMEOUT` + environmental variables as defined in the + [specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.12.0/specification/sdk-environment-variables.md#periodic-exporting-metricreader). + ([#3424](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3424)) + ## 1.3.0 Released 2022-Jun-03 @@ -19,7 +35,7 @@ Released 2022-May-16 * Support `HttpProtobuf` protocol with logs & added `HttpClientFactory` option -([#3225](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3225)) + ([#3225](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3225)) * Removes net5.0 target and replaced with net6.0 as .NET 5.0 is going out of support. @@ -242,8 +258,8 @@ Released 2021-Apr-23 * Null values in string arrays are preserved according to [spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/common.md). - ([#1919](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1919)) and - ([#1945](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1945)). + ([#1919](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1919) + [#1945](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1945)) * When using OpenTelemetry.Extensions.Hosting you can now bind `OtlpExporterOptions` to `IConfiguration` using the `Configure` extension (ex: diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/EnvironmentVariableConfiguration.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/EnvironmentVariableConfiguration.cs new file mode 100644 index 000000000..dd6cbc7ee --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/EnvironmentVariableConfiguration.cs @@ -0,0 +1,46 @@ +// +// 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; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Configuration; + +internal class EnvironmentVariableConfiguration +{ + public static void InitializeDefaultConfigurationFromEnvironment(SdkConfiguration sdkConfiguration) + { + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#attribute-limits + SetIntConfigValue("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", value => sdkConfiguration.AttributeValueLengthLimit = value); + SetIntConfigValue("OTEL_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.AttributeCountLimit = value); + + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#span-limits + SetIntConfigValue("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", value => sdkConfiguration.SpanAttributeValueLengthLimit = value); + SetIntConfigValue("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.SpanAttributeCountLimit = value); + SetIntConfigValue("OTEL_SPAN_EVENT_COUNT_LIMIT", value => sdkConfiguration.SpanEventCountLimit = value); + SetIntConfigValue("OTEL_SPAN_LINK_COUNT_LIMIT", value => sdkConfiguration.SpanLinkCountLimit = value); + SetIntConfigValue("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.EventAttributeCountLimit = value); + SetIntConfigValue("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.LinkAttributeCountLimit = value); + } + + private static void SetIntConfigValue(string key, Action setter) + { + if (EnvironmentVariableHelper.LoadNumeric(key, out var result)) + { + setter(result); + } + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/SdkConfiguration.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/SdkConfiguration.cs new file mode 100644 index 000000000..a7772bcd5 --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/SdkConfiguration.cs @@ -0,0 +1,69 @@ +// +// 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. +// + +namespace OpenTelemetry.Configuration; + +internal class SdkConfiguration +{ + private int? spanAttributeValueLengthLimit; + private int? spanAttributeCountLimit; + private int? eventAttributeCountLimit; + private int? linkAttributeCountLimit; + + private SdkConfiguration() + { + EnvironmentVariableConfiguration.InitializeDefaultConfigurationFromEnvironment(this); + } + + public static SdkConfiguration Instance { get; private set; } = new SdkConfiguration(); + + public int? AttributeValueLengthLimit { get; set; } + + public int? AttributeCountLimit { get; set; } + + public int? SpanAttributeValueLengthLimit + { + get => this.spanAttributeValueLengthLimit ?? this.AttributeValueLengthLimit; + set => this.spanAttributeValueLengthLimit = value; + } + + public int? SpanAttributeCountLimit + { + get => this.spanAttributeCountLimit ?? this.AttributeCountLimit; + set => this.spanAttributeCountLimit = value; + } + + public int? SpanEventCountLimit { get; set; } + + public int? SpanLinkCountLimit { get; set; } + + public int? EventAttributeCountLimit + { + get => this.eventAttributeCountLimit ?? this.SpanAttributeCountLimit; + set => this.eventAttributeCountLimit = value; + } + + public int? LinkAttributeCountLimit + { + get => this.linkAttributeCountLimit ?? this.SpanAttributeCountLimit; + set => this.linkAttributeCountLimit = value; + } + + internal static void Reset() + { + Instance = new SdkConfiguration(); + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs index 6e62e19e8..f1e360215 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs @@ -24,6 +24,7 @@ using System.Reflection.Emit; using System.Runtime.CompilerServices; using Google.Protobuf; using Google.Protobuf.Collections; +using OpenTelemetry.Configuration; using OpenTelemetry.Internal; using OpenTelemetry.Trace; using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1; @@ -154,7 +155,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation }; TagEnumerationState otlpTags = default; - activity.EnumerateTags(ref otlpTags); + activity.EnumerateTags(ref otlpTags, SdkConfiguration.Instance.SpanAttributeCountLimit); if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer) { @@ -181,7 +182,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation otlpSpan.Status = activity.ToOtlpStatus(ref otlpTags); EventEnumerationState otlpEvents = default; - activity.EnumerateEvents(ref otlpEvents); + activity.EnumerateEvents(ref otlpEvents, SdkConfiguration.Instance.SpanEventCountLimit); if (otlpEvents.Created) { otlpSpan.Events.AddRange(otlpEvents.Events); @@ -189,13 +190,14 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation } LinkEnumerationState otlpLinks = default; - activity.EnumerateLinks(ref otlpLinks); + activity.EnumerateLinks(ref otlpLinks, SdkConfiguration.Instance.SpanLinkCountLimit); if (otlpLinks.Created) { otlpSpan.Links.AddRange(otlpLinks.Links); otlpLinks.Links.Return(); } + // TODO: The drop counts should be set when necessary. // Activity does not limit number of attributes, events, links, etc so drop counts are always zero. return otlpSpan; @@ -259,7 +261,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation }; TagEnumerationState otlpTags = default; - activityLink.EnumerateTags(ref otlpTags); + activityLink.EnumerateTags(ref otlpTags, SdkConfiguration.Instance.LinkAttributeCountLimit); if (otlpTags.Created) { otlpLink.Attributes.AddRange(otlpTags.Tags); @@ -279,7 +281,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation }; TagEnumerationState otlpTags = default; - activityEvent.EnumerateTags(ref otlpTags); + activityEvent.EnumerateTags(ref otlpTags, SdkConfiguration.Instance.EventAttributeCountLimit); if (otlpTags.Created) { otlpEvent.Attributes.AddRange(otlpTags.Tags); @@ -355,7 +357,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation this.Created = true; } - if (OtlpKeyValueTransformer.Instance.TryTransformTag(activityTag, out var attribute)) + if (OtlpKeyValueTransformer.Instance.TryTransformTag(activityTag, out var attribute, SdkConfiguration.Instance.AttributeValueLengthLimit)) { PooledList.Add(ref this.Tags, attribute); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExporterClientValidation.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExporterClientValidation.cs index ad09136a7..d483a6023 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExporterClientValidation.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ExportClient/ExporterClientValidation.cs @@ -30,7 +30,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClie return; } - if (options.Endpoint.Scheme.Equals("http", StringComparison.InvariantCultureIgnoreCase)) + if (options.Endpoint.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase)) { if (AppContext.TryGetSwitch( "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", out var unencryptedIsSupported) == false diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs index 3cdf4c296..f3c1547f1 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterExtensions.cs @@ -25,9 +25,6 @@ namespace OpenTelemetry.Metrics /// public static class OtlpMetricExporterExtensions { - private const int DefaultExportIntervalMilliseconds = 60000; - private const int DefaultExportTimeoutMilliseconds = 30000; - /// /// Adds to the using default options. /// @@ -111,9 +108,7 @@ namespace OpenTelemetry.Metrics var metricReader = PeriodicExportingMetricReaderHelper.CreatePeriodicExportingMetricReader( metricExporter, - metricReaderOptions, - DefaultExportIntervalMilliseconds, - DefaultExportTimeoutMilliseconds); + metricReaderOptions); return builder.AddReader(metricReader); } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md index 85fae396d..2d7588afb 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md @@ -18,9 +18,9 @@ dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol ## Configuration -You can configure the `OtlpExporter` through `OtlpExporterOptions` -properties and environment variables. The `OtlpExporterOptions` -setters take precedence over the environment variables. +You can configure the `OtlpExporter` through `Options` types properties +and environment variables. +The `Options` type setters take precedence over the environment variables. ## Options Properties @@ -63,6 +63,15 @@ values of the `OtlpExporterOptions` | `OTEL_EXPORTER_OTLP_TIMEOUT` | `TimeoutMilliseconds` | | `OTEL_EXPORTER_OTLP_PROTOCOL` | `Protocol` (`grpc` or `http/protobuf`)| +The following environment variables can be used to override the default +values of the `PeriodicExportingMetricReaderOptions` +(following the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.12.0/specification/sdk-environment-variables.md#periodic-exporting-metricreader). + +| Environment variable | `PeriodicExportingMetricReaderOptions` property | +| ------------------------------| ------------------------------------------------| +| `OTEL_METRIC_EXPORT_INTERVAL` | `ExportIntervalMilliseconds` | +| `OTEL_METRIC_EXPORT_TIMEOUT` | `ExportTimeoutMilliseconds` | + `FormatException` is thrown in case of an invalid value for any of the supported environment variables. diff --git a/src/OpenTelemetry.Exporter.Prometheus/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/net6.0/PublicAPI.Shipped.txt similarity index 100% rename from src/OpenTelemetry.Exporter.Prometheus/.publicApi/net462/PublicAPI.Shipped.txt rename to src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/net6.0/PublicAPI.Shipped.txt diff --git a/src/OpenTelemetry.Exporter.Prometheus/.publicApi/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/net6.0/PublicAPI.Unshipped.txt similarity index 63% rename from src/OpenTelemetry.Exporter.Prometheus/.publicApi/net6.0/PublicAPI.Unshipped.txt rename to src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/net6.0/PublicAPI.Unshipped.txt index 0c45b3f18..3c9d701eb 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/.publicApi/net6.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/net6.0/PublicAPI.Unshipped.txt @@ -1,27 +1,17 @@ Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions -OpenTelemetry.Exporter.PrometheusExporter -OpenTelemetry.Exporter.PrometheusExporter.Collect.get -> System.Func -OpenTelemetry.Exporter.PrometheusExporter.Collect.set -> void -OpenTelemetry.Exporter.PrometheusExporter.PrometheusExporter(OpenTelemetry.Exporter.PrometheusExporterOptions options) -> void -OpenTelemetry.Exporter.PrometheusExporterOptions -OpenTelemetry.Exporter.PrometheusExporterOptions.HttpListenerPrefixes.get -> System.Collections.Generic.IReadOnlyCollection -OpenTelemetry.Exporter.PrometheusExporterOptions.HttpListenerPrefixes.set -> void -OpenTelemetry.Exporter.PrometheusExporterOptions.PrometheusExporterOptions() -> void -OpenTelemetry.Exporter.PrometheusExporterOptions.ScrapeEndpointPath.get -> string -OpenTelemetry.Exporter.PrometheusExporterOptions.ScrapeEndpointPath.set -> void -OpenTelemetry.Exporter.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int -OpenTelemetry.Exporter.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void -OpenTelemetry.Exporter.PrometheusExporterOptions.StartHttpListener.get -> bool -OpenTelemetry.Exporter.PrometheusExporterOptions.StartHttpListener.set -> void +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.PrometheusExporterOptions() -> void +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeEndpointPath.get -> string +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeEndpointPath.set -> void +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int +OpenTelemetry.Exporter.Prometheus.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions -override OpenTelemetry.Exporter.PrometheusExporter.Dispose(bool disposing) -> void -override OpenTelemetry.Exporter.PrometheusExporter.Export(in OpenTelemetry.Batch metrics) -> OpenTelemetry.ExportResult static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, OpenTelemetry.Metrics.MeterProvider meterProvider, System.Func predicate, string path, System.Action configureBranchedPipeline) -> Microsoft.AspNetCore.Builder.IApplicationBuilder static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, string path) -> Microsoft.AspNetCore.Builder.IApplicationBuilder static Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions.UseOpenTelemetryPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Builder.IApplicationBuilder app, System.Func predicate) -> Microsoft.AspNetCore.Builder.IApplicationBuilder -static OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions.AddPrometheusExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure = null) -> OpenTelemetry.Metrics.MeterProviderBuilder static Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions.MapPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder -static Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions.MapPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string path) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder static Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions.MapPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string path = null, OpenTelemetry.Metrics.MeterProvider meterProvider = null, System.Action configureBranchedPipeline = null) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder +static Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions.MapPrometheusScrapingEndpoint(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string path) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder +static OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions.AddPrometheusExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure = null) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Instrumentation.AspNet/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/AssemblyInfo.cs similarity index 59% rename from src/OpenTelemetry.Instrumentation.AspNet/AssemblyInfo.cs rename to src/OpenTelemetry.Exporter.Prometheus.AspNetCore/AssemblyInfo.cs index 0f14529a4..574fd52fb 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/AssemblyInfo.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/AssemblyInfo.cs @@ -13,10 +13,11 @@ // See the License for the specific language governing permissions and // limitations under the License. // + using System.Runtime.CompilerServices; #if SIGNED -[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.AspNet.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] #else -[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.AspNet.Tests")] +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests")] #endif diff --git a/src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md similarity index 80% rename from src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md rename to src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index 5787a17c1..ca3dd4072 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +* Split up Prometheus projects based on its hosting mechanism, + HttpListener and AspNetCore, into their own projects + and assemblies. + ([#3430](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3430) + [#3503](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3503) + [#3507](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3507)) + * Added `IEndpointRouteBuilder` extension methods to help with Prometheus middleware configuration on ASP.NET Core ([#3295](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3295)) @@ -75,8 +82,8 @@ Released 2021-Sep-23 Released 2021-Sep-13 * Bug fixes - ([#2289](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2289)) - ([#2309](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2309)) + ([#2289](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2289) + [#2309](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2309)) ## 1.2.0-alpha2 diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj new file mode 100644 index 000000000..6fcd3effc --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj @@ -0,0 +1,36 @@ + + + + + net6.0 + ASP.NET Core middleware for hosting OpenTelemetry .NET Prometheus Exporter + $(PackageTags);prometheus;metrics + core- + + + + + false + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterApplicationBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs similarity index 96% rename from src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterApplicationBuilderExtensions.cs rename to src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs index ef96f4722..100b4d699 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterApplicationBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs @@ -14,12 +14,9 @@ // limitations under the License. // -#if NETCOREAPP3_1_OR_GREATER - using System; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -using OpenTelemetry.Exporter; using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Internal; using OpenTelemetry.Metrics; @@ -93,8 +90,8 @@ namespace Microsoft.AspNetCore.Builder /// The to add /// middleware to. /// Optional - /// containing a otherwise the primary - /// SDK provider will be resolved using application services. + /// containing a Prometheus exporter otherwise the primary SDK provider + /// will be resolved using application services. /// Optional predicate for deciding if a given /// should be branched. If supplied is ignored. @@ -151,4 +148,3 @@ namespace Microsoft.AspNetCore.Builder } } } -#endif diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterEndpointRouteBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs similarity index 95% rename from src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterEndpointRouteBuilderExtensions.cs rename to src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs index 02b58a342..e24e829aa 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterEndpointRouteBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs @@ -14,13 +14,10 @@ // limitations under the License. // -#if NETCOREAPP3_1_OR_GREATER - using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; -using OpenTelemetry.Exporter; using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Internal; using OpenTelemetry.Metrics; @@ -70,8 +67,8 @@ namespace Microsoft.AspNetCore.Builder /// If not provided then /// is used. /// Optional - /// containing a otherwise the primary - /// SDK provider will be resolved using application services. + /// containing a Prometheus exporter otherwise the primary SDK provider + /// will be resolved using application services. /// Optional callback to /// configure the branched pipeline. Called before registration of the /// Prometheus middleware. @@ -109,4 +106,3 @@ namespace Microsoft.AspNetCore.Builder } } } -#endif diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs similarity index 83% rename from src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMeterProviderBuilderExtensions.cs rename to src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs index ada7d1c90..7039db2a3 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs @@ -15,11 +15,14 @@ // using System; -using OpenTelemetry.Exporter; +using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Internal; namespace OpenTelemetry.Metrics { + /// + /// Extension methods to simplify registering a PrometheusExporter. + /// public static class PrometheusExporterMeterProviderBuilderExtensions { /// @@ -47,9 +50,11 @@ namespace OpenTelemetry.Metrics { configure?.Invoke(options); - var exporter = new PrometheusExporter(options); - var reader = new BaseExportingMetricReader(exporter); - reader.TemporalityPreference = MetricReaderTemporalityPreference.Cumulative; + var exporter = new PrometheusExporter(scrapeEndpointPath: options.ScrapeEndpointPath, scrapeResponseCacheDurationMilliseconds: options.ScrapeResponseCacheDurationMilliseconds); + var reader = new BaseExportingMetricReader(exporter) + { + TemporalityPreference = MetricReaderTemporalityPreference.Cumulative, + }; return builder.AddReader(reader); } diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs similarity index 99% rename from src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterMiddleware.cs rename to src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs index 826ca1357..2b2368a60 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterMiddleware.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs @@ -14,7 +14,6 @@ // limitations under the License. // -#if NETCOREAPP3_1_OR_GREATER using System; using System.Diagnostics; using System.Threading.Tasks; @@ -102,4 +101,3 @@ namespace OpenTelemetry.Exporter.Prometheus } } } -#endif diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterOptions.cs new file mode 100644 index 000000000..248b991f4 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterOptions.cs @@ -0,0 +1,52 @@ +// +// 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 OpenTelemetry.Internal; + +namespace OpenTelemetry.Exporter.Prometheus +{ + /// + /// Prometheus exporter options. + /// + public class PrometheusExporterOptions + { + internal const string DefaultScrapeEndpointPath = "/metrics"; + + private int scrapeResponseCacheDurationMilliseconds = 300; + + /// + /// Gets or sets the path to use for the scraping endpoint. Default value: "/metrics". + /// + public string ScrapeEndpointPath { get; set; } = DefaultScrapeEndpointPath; + + /// + /// Gets or sets the cache duration in milliseconds for scrape responses. Default value: 300. + /// + /// + /// Note: Specify 0 to disable response caching. + /// + public int ScrapeResponseCacheDurationMilliseconds + { + get => this.scrapeResponseCacheDurationMilliseconds; + set + { + Guard.ThrowIfOutOfRange(value, min: 0); + + this.scrapeResponseCacheDurationMilliseconds = value; + } + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus/README.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md similarity index 53% rename from src/OpenTelemetry.Exporter.Prometheus/README.md rename to src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md index ecac7ad4d..d9cb8bfdc 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/README.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/README.md @@ -1,23 +1,29 @@ -# Prometheus Exporter for OpenTelemetry .NET +# Prometheus Exporter AspNetCore for OpenTelemetry .NET -[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Exporter.Prometheus.svg)](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus) -[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Exporter.Prometheus.svg)](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus) +[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Exporter.Prometheus.AspNetCore.svg)](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus.AspNetCore) +[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Exporter.Prometheus.AspNetCore.svg)](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus.AspNetCore) + +An [OpenTelemetry Prometheus exporter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/prometheus.md) +for configuring an ASP.NET Core application with an endpoint for Prometheus +to scrape. ## Prerequisite * [Get Prometheus](https://prometheus.io/docs/introduction/first_steps/) -## Steps to enable OpenTelemetry.Exporter.Prometheus +## Steps to enable OpenTelemetry.Exporter.Prometheus.AspNetCore ### Step 1: Install Package ```shell -dotnet add package OpenTelemetry.Exporter.Prometheus +dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore ``` ### Step 2: Configure OpenTelemetry MeterProvider -* When using OpenTelemetry.Extensions.Hosting package on .NET Core 3.1+: +* When using + [OpenTelemetry.Extensions.Hosting](../OpenTelemetry.Extensions.Hosting/README.md) + package on .NET Core 3.1+: ```csharp services.AddOpenTelemetryMetrics(builder => @@ -28,18 +34,19 @@ dotnet add package OpenTelemetry.Exporter.Prometheus * Or configure directly: - Call the `AddPrometheusExporter` `MeterProviderBuilder` extension to + Call the `MeterProviderBuilder.AddPrometheusExporter` extension to register the Prometheus exporter. ```csharp - using var meterProvider = Sdk.CreateMeterProviderBuilder() + var meterProvider = Sdk.CreateMeterProviderBuilder() .AddPrometheusExporter() .Build(); + builder.Services.AddSingleton(meterProvider); ``` ### Step 3: Configure Prometheus Scraping Endpoint -* On .NET Core 3.1+ register Prometheus scraping middleware using the +* Register Prometheus scraping middleware using the `UseOpenTelemetryPrometheusScrapingEndpoint` extension: ```csharp @@ -72,54 +79,22 @@ dotnet add package OpenTelemetry.Exporter.Prometheus } ``` -* On .NET Framework an HTTP listener is automatically started which will respond - to scraping requests. See the [Configuration](#configuration) section for - details on the settings available. This may also be turned on in .NET Core (it - is OFF by default) when the ASP.NET Core pipeline is not available for - middleware registration. - ## Configuration The `PrometheusExporter` can be configured using the `PrometheusExporterOptions` -properties. Refer to -[`TestPrometheusExporter.cs`](../../examples/Console/TestPrometheusExporter.cs) -for example use. - -### StartHttpListener - -Set to `true` to start an HTTP listener which will respond to Prometheus scrape -requests using the [HttpListenerPrefixes](#httplistenerprefixes) and -[ScrapeEndpointPath](#scrapeendpointpath) options. - -Defaults: - -* On .NET Framework this is `true` by default. - -* On .NET Core 3.1+ this is `false` by default. Users running ASP.NET Core - should use the `UseOpenTelemetryPrometheusScrapingEndpoint` extension to - register the scraping middleware instead of using the listener. - -### HttpListenerPrefixes - -Defines the prefixes which will be used by the listener when `StartHttpListener` -is `true`. The default value is `["http://localhost:9464/"]`. You may specify -multiple endpoints. - -For details see: -[HttpListenerPrefixCollection.Add(String)](https://docs.microsoft.com/dotnet/api/system.net.httplistenerprefixcollection.add) +properties. ### ScrapeEndpointPath -Defines the path for the Prometheus scrape endpoint for -either the HTTP listener or the middleware registered by +Defines the path for the Prometheus scrape endpoint for the middleware +registered by `UseOpenTelemetryPrometheusScrapingEndpoint`. Default value: `"/metrics"`. ### ScrapeResponseCacheDurationMilliseconds Configures scrape endpoint response caching. Multiple scrape requests within the cache duration time period will receive the same previously generated response. -The default value is `10000` (10 seconds). Set to `0` to disable response -caching. +The default value is `300`. Set to `0` to disable response caching. ## Troubleshooting diff --git a/src/OpenTelemetry.Exporter.Prometheus/.publicApi/net6.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Shipped.txt similarity index 100% rename from src/OpenTelemetry.Exporter.Prometheus/.publicApi/net6.0/PublicAPI.Shipped.txt rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Shipped.txt diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..e05aa673e --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/net462/PublicAPI.Unshipped.txt @@ -0,0 +1,8 @@ +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.UriPrefixes.set -> void +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.ScrapeEndpointPath.get -> string +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.ScrapeEndpointPath.set -> void +OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions +static OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure = null) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Shipped.txt similarity index 100% rename from src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net462/PublicAPI.Shipped.txt rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Shipped.txt diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..e05aa673e --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,8 @@ +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.UriPrefixes.set -> void +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.ScrapeEndpointPath.get -> string +OpenTelemetry.Exporter.Prometheus.PrometheusHttpListenerOptions.ScrapeEndpointPath.set -> void +OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions +static OpenTelemetry.Metrics.PrometheusHttpListenerMeterProviderBuilderExtensions.AddPrometheusHttpListener(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure = null) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Exporter.Prometheus/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/AssemblyInfo.cs similarity index 77% rename from src/OpenTelemetry.Exporter.Prometheus/AssemblyInfo.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/AssemblyInfo.cs index 619668af3..e05d67144 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/AssemblyInfo.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/AssemblyInfo.cs @@ -17,9 +17,9 @@ using System.Runtime.CompilerServices; #if SIGNED +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.HttpListener.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] [assembly: InternalsVisibleTo("Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] #else +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.HttpListener.Tests")] [assembly: InternalsVisibleTo("Benchmarks")] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests")] #endif diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md new file mode 100644 index 000000000..9d5161528 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -0,0 +1,99 @@ +# Changelog + +## Unreleased + +* Split up Prometheus projects based on its hosting mechanism, + HttpListener and AspNetCore, into their own projects + and assemblies. + ([#3430](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3430) + [#3503](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3503) + [#3507](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3507)) +* Fixed bug + [#2840](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2840) by + allowing `+` and `*` to be used in the URI prefixes (e.g. `"http://*:9184"`). + ([#3521](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3521)) + +## 1.3.0-rc.2 + +Released 2022-June-1 + +## 1.3.0-beta.2 + +Released 2022-May-16 + +## 1.3.0-beta.1 + +Released 2022-Apr-15 + +* Added `IApplicationBuilder` extension methods to help with Prometheus + middleware configuration on ASP.NET Core + ([#3029](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3029)) + +* Changed Prometheus exporter to return 204 No Content and log a warning event + if there are no metrics to collect. + +* Removes .NET Framework 4.6.1. The minimum .NET Framework + version supported is .NET 4.6.2. ([#3190](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3190)) + +## 1.2.0-rc5 + +Released 2022-Apr-12 + +## 1.2.0-rc4 + +Released 2022-Mar-30 + +## 1.2.0-rc3 + +Released 2022-Mar-04 + +## 1.2.0-rc2 + +Released 2022-Feb-02 + +* Update default `httpListenerPrefixes` for PrometheusExporter to be `http://localhost:9464/`. +([#2783](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2783)) + +## 1.2.0-rc1 + +Released 2021-Nov-29 + +* Bug fix for handling Histogram with empty buckets. + ([#2651](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2651)) + +## 1.2.0-beta2 + +Released 2021-Nov-19 + +* Added scrape endpoint response caching feature & + `ScrapeResponseCacheDurationMilliseconds` option + ([#2610](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2610)) + +## 1.2.0-beta1 + +Released 2021-Oct-08 + +## 1.2.0-alpha4 + +Released 2021-Sep-23 + +## 1.2.0-alpha3 + +Released 2021-Sep-13 + +* Bug fixes + ([#2289](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2289) + [#2309](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2309)) + +## 1.2.0-alpha2 + +Released 2021-Aug-24 + +* Revamped to support the new Metrics API/SDK. + Supports Counter, Gauge and Histogram. + +## 1.0.0-rc1.1 + +Released 2020-Nov-17 + +* Initial release diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs similarity index 93% rename from src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusCollectionManager.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index 8f672645b..704422b63 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -25,7 +25,7 @@ namespace OpenTelemetry.Exporter.Prometheus internal sealed class PrometheusCollectionManager { private readonly PrometheusExporter exporter; - private readonly int scrapeResponseCacheDurationInMilliseconds; + private readonly int scrapeResponseCacheDurationMilliseconds; private readonly Func, ExportResult> onCollectRef; private byte[] buffer = new byte[85000]; // encourage the object to live in LOH (large object heap) private int globalLockState; @@ -38,7 +38,7 @@ namespace OpenTelemetry.Exporter.Prometheus public PrometheusCollectionManager(PrometheusExporter exporter) { this.exporter = exporter; - this.scrapeResponseCacheDurationInMilliseconds = this.exporter.Options.ScrapeResponseCacheDurationMilliseconds; + this.scrapeResponseCacheDurationMilliseconds = this.exporter.ScrapeResponseCacheDurationMilliseconds; this.onCollectRef = this.OnCollect; } @@ -53,8 +53,8 @@ namespace OpenTelemetry.Exporter.Prometheus // If we are within {ScrapeResponseCacheDurationMilliseconds} of the // last successful collect, return the previous view. if (this.previousDataViewGeneratedAtUtc.HasValue - && this.scrapeResponseCacheDurationInMilliseconds > 0 - && this.previousDataViewGeneratedAtUtc.Value.AddMilliseconds(this.scrapeResponseCacheDurationInMilliseconds) >= DateTime.UtcNow) + && this.scrapeResponseCacheDurationMilliseconds > 0 + && this.previousDataViewGeneratedAtUtc.Value.AddMilliseconds(this.scrapeResponseCacheDurationMilliseconds) >= DateTime.UtcNow) { Interlocked.Increment(ref this.readerCount); this.ExitGlobalLock(); @@ -91,7 +91,7 @@ namespace OpenTelemetry.Exporter.Prometheus this.ExitGlobalLock(); CollectionResponse response; - bool result = this.ExecuteCollect(); + var result = this.ExecuteCollect(); if (result) { this.previousDataViewGeneratedAtUtc = DateTime.UtcNow; @@ -169,14 +169,14 @@ namespace OpenTelemetry.Exporter.Prometheus private bool ExecuteCollect() { this.exporter.OnExport = this.onCollectRef; - bool result = this.exporter.Collect(Timeout.Infinite); + var result = this.exporter.Collect(Timeout.Infinite); this.exporter.OnExport = null; return result; } private ExportResult OnCollect(Batch metrics) { - int cursor = 0; + var cursor = 0; try { @@ -191,7 +191,7 @@ namespace OpenTelemetry.Exporter.Prometheus } catch (IndexOutOfRangeException) { - int bufferSize = this.buffer.Length * 2; + var bufferSize = this.buffer.Length * 2; // there are two cases we might run into the following condition: // 1. we have many metrics to be exported - in this case we probably want diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs similarity index 62% rename from src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs index 4af4eb17f..93514ef4c 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs @@ -15,48 +15,40 @@ // using System; -using OpenTelemetry.Exporter.Prometheus; +using OpenTelemetry.Internal; using OpenTelemetry.Metrics; -namespace OpenTelemetry.Exporter +namespace OpenTelemetry.Exporter.Prometheus { /// /// Exporter of OpenTelemetry metrics to Prometheus. /// [ExportModes(ExportModes.Pull)] - public class PrometheusExporter : BaseExporter, IPullMetricExporter + internal sealed class PrometheusExporter : BaseExporter, IPullMetricExporter { - internal const string HttpListenerStartFailureExceptionMessage = "PrometheusExporter http listener could not be started."; - internal readonly PrometheusExporterOptions Options; - private readonly PrometheusExporterHttpServer metricsHttpServer; private Func funcCollect; private Func, ExportResult> funcExport; - private bool disposed; + private bool disposed = false; /// /// Initializes a new instance of the class. /// - /// Options for the exporter. - public PrometheusExporter(PrometheusExporterOptions options) + /// Scraping endpoint. + /// + /// The cache duration in milliseconds for scrape responses. Default value: 0. + /// + public PrometheusExporter(string scrapeEndpointPath = null, int scrapeResponseCacheDurationMilliseconds = 0) { - this.Options = options; - - if (options.StartHttpListener) - { - try - { - this.metricsHttpServer = new PrometheusExporterHttpServer(this); - this.metricsHttpServer.Start(); - } - catch (Exception ex) - { - throw new InvalidOperationException(HttpListenerStartFailureExceptionMessage, ex); - } - } + Guard.ThrowIfOutOfRange(scrapeResponseCacheDurationMilliseconds, min: 0); + this.ScrapeEndpointPath = scrapeEndpointPath ?? "/metrics"; + this.ScrapeResponseCacheDurationMilliseconds = scrapeResponseCacheDurationMilliseconds; this.CollectionManager = new PrometheusCollectionManager(this); } + /// + /// Gets or sets the Collect delegate. + /// public Func Collect { get => this.funcCollect; @@ -69,20 +61,28 @@ namespace OpenTelemetry.Exporter set => this.funcExport = value; } + internal Action OnDispose { get; set; } + internal PrometheusCollectionManager CollectionManager { get; } + internal int ScrapeResponseCacheDurationMilliseconds { get; } + + internal string ScrapeEndpointPath { get; } + + /// public override ExportResult Export(in Batch metrics) { return this.OnExport(metrics); } + /// protected override void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { - this.metricsHttpServer?.Dispose(); + this.OnDispose?.Invoke(); } this.disposed = true; diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterEventSource.cs similarity index 100% rename from src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterEventSource.cs diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs similarity index 98% rename from src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusSerializer.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index dfe95008a..ac8bd042c 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -20,6 +20,7 @@ using System; using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; +using OpenTelemetry.Internal; namespace OpenTelemetry.Exporter.Prometheus { @@ -39,11 +40,7 @@ namespace OpenTelemetry.Exporter.Prometheus [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int WriteDouble(byte[] buffer, int cursor, double value) { -#if NETCOREAPP3_1_OR_GREATER - if (double.IsFinite(value)) -#else - if (!double.IsInfinity(value) && !double.IsNaN(value)) -#endif + if (MathHelper.IsFinite(value)) { #if NETCOREAPP3_1_OR_GREATER Span span = stackalloc char[128]; diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs similarity index 100% rename from src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusSerializerExt.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs diff --git a/src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj similarity index 72% rename from src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj index 526089a32..e9c463717 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/OpenTelemetry.Exporter.Prometheus.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj @@ -2,16 +2,12 @@ - net6.0;net462 - Prometheus exporter for OpenTelemetry .NET + netstandard2.0;net462 + Stand-alone HttpListener for hosting OpenTelemetry .NET Prometheus Exporter $(PackageTags);prometheus;metrics core- - - $(NoWarn),1591 - - @@ -27,8 +23,4 @@ - - - - diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterHttpServer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs similarity index 83% rename from src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterHttpServer.cs rename to src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index 554f5cf34..3bd122a77 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterHttpServer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,10 +22,7 @@ using OpenTelemetry.Internal; namespace OpenTelemetry.Exporter.Prometheus { - /// - /// An HTTP listener used to expose Prometheus metrics. - /// - internal sealed class PrometheusExporterHttpServer : IDisposable + internal sealed class PrometheusHttpListener : IDisposable { private readonly PrometheusExporter exporter; private readonly HttpListener httpListener = new(); @@ -35,20 +32,19 @@ namespace OpenTelemetry.Exporter.Prometheus private Task workerThread; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The instance. - public PrometheusExporterHttpServer(PrometheusExporter exporter) + /// The exporter instance. + /// The configured HttpListener options. + public PrometheusHttpListener(PrometheusExporter exporter, PrometheusHttpListenerOptions options) { Guard.ThrowIfNull(exporter); + Guard.ThrowIfNull(options); this.exporter = exporter; - if ((exporter.Options.HttpListenerPrefixes?.Count ?? 0) <= 0) - { - throw new ArgumentException("No HttpListenerPrefixes were specified on PrometheusExporterOptions."); - } - string path = exporter.Options.ScrapeEndpointPath ?? PrometheusExporterOptions.DefaultScrapeEndpointPath; + string path = this.exporter.ScrapeEndpointPath; + if (!path.StartsWith("/")) { path = $"/{path}"; @@ -59,16 +55,16 @@ namespace OpenTelemetry.Exporter.Prometheus path = $"{path}/"; } - foreach (string prefix in exporter.Options.HttpListenerPrefixes) + foreach (string uriPrefix in options.UriPrefixes) { - this.httpListener.Prefixes.Add($"{prefix.TrimEnd('/')}{path}"); + this.httpListener.Prefixes.Add($"{uriPrefix.TrimEnd('/')}{path}"); } } /// - /// Start Http Server. + /// Start the HttpListener. /// - /// An optional that can be used to stop the HTTP server. + /// An optional that can be used to stop the HTTP listener. public void Start(CancellationToken token = default) { lock (this.syncObject) @@ -88,7 +84,7 @@ namespace OpenTelemetry.Exporter.Prometheus } /// - /// Stop exporter. + /// Gracefully stop the PrometheusHttpListener. /// public void Stop() { @@ -108,9 +104,10 @@ namespace OpenTelemetry.Exporter.Prometheus /// public void Dispose() { + this.Stop(); + if (this.httpListener != null && this.httpListener.IsListening) { - this.Stop(); this.httpListener.Close(); } } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs new file mode 100644 index 000000000..6c0b7b75a --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs @@ -0,0 +1,87 @@ +// +// 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; +using OpenTelemetry.Exporter.Prometheus; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Metrics +{ + /// + /// Extension methods to simplify registering a PrometheusHttpListener. + /// + public static class PrometheusHttpListenerMeterProviderBuilderExtensions + { + /// + /// Adds PrometheusHttpListener to MeterProviderBuilder. + /// + /// builder to use. + /// PrometheusHttpListenerOptions options. + /// The instance of to chain calls. + public static MeterProviderBuilder AddPrometheusHttpListener( + this MeterProviderBuilder builder, + Action configure = null) + { + Guard.ThrowIfNull(builder); + + if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder) + { + return deferredMeterProviderBuilder.Configure((sp, builder) => + { + AddPrometheusHttpListener(builder, sp.GetOptions(), configure); + }); + } + + return AddPrometheusHttpListener(builder, new PrometheusHttpListenerOptions(), configure); + } + + private static MeterProviderBuilder AddPrometheusHttpListener( + MeterProviderBuilder builder, + PrometheusHttpListenerOptions options, + Action configure = null) + { + configure?.Invoke(options); + + var exporter = new PrometheusExporter(scrapeEndpointPath: options.ScrapeEndpointPath); + + var reader = new BaseExportingMetricReader(exporter) + { + TemporalityPreference = MetricReaderTemporalityPreference.Cumulative, + }; + + try + { + var listener = new PrometheusHttpListener(exporter, options); + exporter.OnDispose = () => listener.Dispose(); + listener.Start(); + } + catch (Exception ex) + { + try + { + reader.Dispose(); + } + catch + { + } + + throw new InvalidOperationException("PrometheusExporter HttpListener could not be started.", ex); + } + + return builder.AddReader(reader); + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs new file mode 100644 index 000000000..68f88c164 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs @@ -0,0 +1,55 @@ +// +// 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; +using System.Collections.Generic; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Exporter.Prometheus +{ + /// + /// options. + /// + public class PrometheusHttpListenerOptions + { + private IReadOnlyCollection uriPrefixes = new string[] { "http://localhost:9464/" }; + + /// + /// Gets or sets the path to use for the scraping endpoint. Default value: "/metrics". + /// + public string ScrapeEndpointPath { get; set; } = "/metrics"; + + /// + /// Gets or sets the URI (Uniform Resource Identifier) prefixes to use for the http listener. + /// Default value: ["http://localhost:9464/"]. + /// + public IReadOnlyCollection UriPrefixes + { + get => this.uriPrefixes; + set + { + Guard.ThrowIfNull(value); + + if (value.Count == 0) + { + throw new ArgumentException("Empty list provided.", nameof(this.UriPrefixes)); + } + + this.uriPrefixes = value; + } + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md new file mode 100644 index 000000000..b97cb0bd2 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md @@ -0,0 +1,57 @@ +# Prometheus Exporter HttpListener for OpenTelemetry .NET + +[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Exporter.Prometheus.HttpListener.svg)](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus.HttpListener) +[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Exporter.Prometheus.HttpListener.svg)](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus.HttpListener) + +An [OpenTelemetry Prometheus exporter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/prometheus.md) +that configures an [HttpListener](https://docs.microsoft.com/dotnet/api/system.net.httplistener) +instance for Prometheus to scrape. + +## Prerequisite + +* [Get Prometheus](https://prometheus.io/docs/introduction/first_steps/) + +## Steps to enable OpenTelemetry.Exporter.Prometheus.HttpListener + +### Step 1: Install Package + +```shell +dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener +``` + +### Step 2: Add PrometheusHttpListener + +```csharp +var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddMeter(MyMeter.Name) + .AddPrometheusHttpListener( + options => options.UriPrefixes = new string[] { "http://localhost:9464/" }) + .Build(); +``` + +### UriPrefixes + +Defines one or more URI (Uniform Resource Identifier) prefixes which will be +used by the HTTP listener. The default value is `["http://localhost:9464/"]`. + +Refer to +[HttpListenerPrefixCollection.Add(String)](https://docs.microsoft.com/dotnet/api/system.net.httplistenerprefixcollection.add) +for more details. + +### ScrapeEndpointPath + +Defines the Prometheus scrape endpoint path. Default value: `"/metrics"`. + +## Troubleshooting + +This component uses an +[EventSource](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventsource) +with the name "OpenTelemetry-Exporter-Prometheus" for its internal logging. +Please refer to [SDK +troubleshooting](../OpenTelemetry/README.md#troubleshooting) for instructions on +seeing these internal logs. + +## References + +* [OpenTelemetry Project](https://opentelemetry.io/) +* [Prometheus](https://prometheus.io) diff --git a/src/OpenTelemetry.Exporter.Prometheus/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus/.publicApi/net462/PublicAPI.Unshipped.txt deleted file mode 100644 index 70bd44b3d..000000000 --- a/src/OpenTelemetry.Exporter.Prometheus/.publicApi/net462/PublicAPI.Unshipped.txt +++ /dev/null @@ -1,18 +0,0 @@ -OpenTelemetry.Exporter.PrometheusExporter -OpenTelemetry.Exporter.PrometheusExporter.Collect.get -> System.Func -OpenTelemetry.Exporter.PrometheusExporter.Collect.set -> void -OpenTelemetry.Exporter.PrometheusExporter.PrometheusExporter(OpenTelemetry.Exporter.PrometheusExporterOptions options) -> void -OpenTelemetry.Exporter.PrometheusExporterOptions -OpenTelemetry.Exporter.PrometheusExporterOptions.HttpListenerPrefixes.get -> System.Collections.Generic.IReadOnlyCollection -OpenTelemetry.Exporter.PrometheusExporterOptions.HttpListenerPrefixes.set -> void -OpenTelemetry.Exporter.PrometheusExporterOptions.PrometheusExporterOptions() -> void -OpenTelemetry.Exporter.PrometheusExporterOptions.ScrapeEndpointPath.get -> string -OpenTelemetry.Exporter.PrometheusExporterOptions.ScrapeEndpointPath.set -> void -OpenTelemetry.Exporter.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.get -> int -OpenTelemetry.Exporter.PrometheusExporterOptions.ScrapeResponseCacheDurationMilliseconds.set -> void -OpenTelemetry.Exporter.PrometheusExporterOptions.StartHttpListener.get -> bool -OpenTelemetry.Exporter.PrometheusExporterOptions.StartHttpListener.set -> void -OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions -override OpenTelemetry.Exporter.PrometheusExporter.Dispose(bool disposing) -> void -override OpenTelemetry.Exporter.PrometheusExporter.Export(in OpenTelemetry.Batch metrics) -> OpenTelemetry.ExportResult -static OpenTelemetry.Metrics.PrometheusExporterMeterProviderBuilderExtensions.AddPrometheusExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure = null) -> OpenTelemetry.Metrics.MeterProviderBuilder \ No newline at end of file diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterOptions.cs deleted file mode 100644 index 4039e5863..000000000 --- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterOptions.cs +++ /dev/null @@ -1,96 +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; -using System.Collections.Generic; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Exporter -{ - /// - /// options. - /// - public class PrometheusExporterOptions - { - internal const string DefaultScrapeEndpointPath = "/metrics"; - internal Func GetUtcNowDateTimeOffset = () => DateTimeOffset.UtcNow; - - private int scrapeResponseCacheDurationMilliseconds = 10 * 1000; - private IReadOnlyCollection httpListenerPrefixes = new string[] { "http://localhost:9464/" }; - -#if NETCOREAPP3_1_OR_GREATER - /// - /// Gets or sets a value indicating whether or not an http listener - /// should be started. Default value: False. - /// - public bool StartHttpListener { get; set; } -#else - /// - /// Gets or sets a value indicating whether or not an http listener - /// should be started. Default value: True. - /// - public bool StartHttpListener { get; set; } = true; -#endif - - /// - /// Gets or sets the prefixes to use for the http listener. Default - /// value: http://localhost:9464/. - /// - public IReadOnlyCollection HttpListenerPrefixes - { - get => this.httpListenerPrefixes; - set - { - Guard.ThrowIfNull(value); - - foreach (string inputUri in value) - { - if (!(Uri.TryCreate(inputUri, UriKind.Absolute, out var uri) && - (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))) - { - throw new ArgumentException( - "Prometheus server path should be a valid URI with http/https scheme.", - nameof(this.httpListenerPrefixes)); - } - } - - this.httpListenerPrefixes = value; - } - } - - /// - /// Gets or sets the path to use for the scraping endpoint. Default value: /metrics. - /// - public string ScrapeEndpointPath { get; set; } = DefaultScrapeEndpointPath; - - /// - /// Gets or sets the cache duration in milliseconds for scrape responses. Default value: 10,000 (10 seconds). - /// - /// - /// Note: Specify 0 to disable response caching. - /// - public int ScrapeResponseCacheDurationMilliseconds - { - get => this.scrapeResponseCacheDurationMilliseconds; - set - { - Guard.ThrowIfOutOfRange(value, min: 0); - - this.scrapeResponseCacheDurationMilliseconds = value; - } - } - } -} diff --git a/src/OpenTelemetry.Exporter.ZPages/CHANGELOG.md b/src/OpenTelemetry.Exporter.ZPages/CHANGELOG.md index cd272cb8e..97f7a2abd 100644 --- a/src/OpenTelemetry.Exporter.ZPages/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.ZPages/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.0.0-rc9.5 + +Released 2022-Aug-02 + ## 1.0.0-rc9.4 Released 2022-Jun-03 @@ -76,9 +80,9 @@ Released 2020-Sep-15 Released 2020-08-28 -* Renamed extension method from `UseZPagesExporter` to `AddZPagesExporter` +* Renamed extension method from `UseZPagesExporter` to `AddZPagesExporter`. ([#1066](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1066)) -* Changed `ZPagesExporter` to use `ZPagesProcessor` by default +* Changed `ZPagesExporter` to use `ZPagesProcessor` by default. ([#1108](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1108)) ## 0.4.0-beta.2 diff --git a/src/OpenTelemetry.Exporter.ZPages/ZPagesExporterStatsHttpServer.cs b/src/OpenTelemetry.Exporter.ZPages/ZPagesExporterStatsHttpServer.cs index 3db455b70..af40141fe 100644 --- a/src/OpenTelemetry.Exporter.ZPages/ZPagesExporterStatsHttpServer.cs +++ b/src/OpenTelemetry.Exporter.ZPages/ZPagesExporterStatsHttpServer.cs @@ -65,6 +65,8 @@ namespace OpenTelemetry.Exporter.ZPages new CancellationTokenSource() : CancellationTokenSource.CreateLinkedTokenSource(token); + this.httpListener.Start(); + this.workerThread = Task.Factory.StartNew(this.WorkerThread, default, TaskCreationOptions.LongRunning, TaskScheduler.Default); } } @@ -110,8 +112,6 @@ namespace OpenTelemetry.Exporter.ZPages private void WorkerThread() { - this.httpListener.Start(); - try { while (!this.tokenSource.IsCancellationRequested) diff --git a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md index d62d7bc4e..d07671b08 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.4.0-alpha.1 + +Released 2022-Aug-02 + ## 1.3.0 Released 2022-Jun-03 @@ -192,12 +196,12 @@ Released 2021-Jan-29 * Zipkin will now set the `error` tag to the `Status.Description` value or an empty string when `Status.StatusCode` (`otel.status_code` tag) is set to `ERROR`. - ([#1579](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1579), - [#1620](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1620), & + ([#1579](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1579) + [#1620](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1620) [#1655](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1655)) * Zipkin will no longer send the `otel.status_code` tag if the value is `UNSET`. - ([#1609](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1609) & + ([#1609](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1609) [#1620](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1620)) * Zipkin bool tag values will now be sent as `true`/`false` instead of @@ -249,13 +253,13 @@ Released 2020-Sep-15 Released 2020-08-28 -* Renamed extension method from `UseZipkinExporter` to `AddZipkinExporter` +* Renamed extension method from `UseZipkinExporter` to `AddZipkinExporter`. ([#1066](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1066)) -* Changed `ZipkinExporter` to use `BatchExportActivityProcessor` by default +* Changed `ZipkinExporter` to use `BatchExportActivityProcessor` by default. ([#1103](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1103)) * Fixed issue when span has both the `net.peer.name` and `net.peer.port` - attributes but did not include `net.peer.port` in the service address field - ([#1168](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1168)). + attributes but did not include `net.peer.port` in the service address field. + ([#1168](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1168)) ## 0.4.0-beta.2 diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Shipped.txt new file mode 100644 index 000000000..7dc5c5811 --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..32c7f36ae --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +#nullable enable +OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter +OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.OpenTelemetryEventSourceLogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, System.Func! shouldListenToFunc, bool disposeProvider = true) -> void +override OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.Dispose() -> void diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Shipped.txt new file mode 100644 index 000000000..7dc5c5811 --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..32c7f36ae --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +#nullable enable +OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter +OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.OpenTelemetryEventSourceLogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, System.Func! shouldListenToFunc, bool disposeProvider = true) -> void +override OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.Dispose() -> void diff --git a/src/OpenTelemetry.Extensions.EventSource/AssemblyInfo.cs b/src/OpenTelemetry.Extensions.EventSource/AssemblyInfo.cs new file mode 100644 index 000000000..a51a83d9d --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/AssemblyInfo.cs @@ -0,0 +1,35 @@ +// +// 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; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(false)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; + public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; + public const string MoqPublicKey = ""; +} +#endif diff --git a/src/OpenTelemetry.Extensions.EventSource/CHANGELOG.md b/src/OpenTelemetry.Extensions.EventSource/CHANGELOG.md new file mode 100644 index 000000000..63bfc986b --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## Unreleased + +Initial release. diff --git a/src/OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj b/src/OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj new file mode 100644 index 000000000..dfc6d2e0a --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.1;netstandard2.0 + Extensions for using OpenTelemetry with System.Diagnostics.Tracing.EventSource + enable + AllEnabledByDefault + latest + + + + + + + + + + + diff --git a/src/OpenTelemetry.Extensions.EventSource/OpenTelemetryEventSourceLogEmitter.cs b/src/OpenTelemetry.Extensions.EventSource/OpenTelemetryEventSourceLogEmitter.cs new file mode 100644 index 000000000..58b244ea4 --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/OpenTelemetryEventSourceLogEmitter.cs @@ -0,0 +1,224 @@ +// +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Globalization; +using System.Linq; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Logs +{ + /// + /// Implements an which will convert events into OpenTelemetry logs. + /// + public sealed class OpenTelemetryEventSourceLogEmitter : EventListener + { + private readonly bool includeFormattedMessage; + private readonly OpenTelemetryLoggerProvider openTelemetryLoggerProvider; + private readonly LogEmitter logEmitter; + private readonly object lockObj = new(); + private readonly Func shouldListenToFunc; + private readonly List eventSources = new(); + private readonly List? eventSourcesBeforeConstructor = new(); + private readonly bool disposeProvider; + + /// + /// Initializes a new instance of the class. + /// + /// . + /// Callback function used to decide if + /// events should be captured for a given . Return if no + /// events should be captured. + /// Controls whether or not the supplied + /// will be disposed when + /// the is disposed. Default value: . + public OpenTelemetryEventSourceLogEmitter( + OpenTelemetryLoggerProvider openTelemetryLoggerProvider, + Func shouldListenToFunc, + bool disposeProvider = true) + { + Guard.ThrowIfNull(openTelemetryLoggerProvider); + Guard.ThrowIfNull(shouldListenToFunc); + + this.includeFormattedMessage = openTelemetryLoggerProvider.IncludeFormattedMessage; + this.openTelemetryLoggerProvider = openTelemetryLoggerProvider!; + this.disposeProvider = disposeProvider; + this.shouldListenToFunc = shouldListenToFunc; + + var logEmitter = this.openTelemetryLoggerProvider.CreateEmitter(); + Debug.Assert(logEmitter != null, "logEmitter was null"); + + this.logEmitter = logEmitter!; + + lock (this.lockObj) + { + foreach (EventSource eventSource in this.eventSourcesBeforeConstructor) + { + this.ProcessSource(eventSource); + } + + this.eventSourcesBeforeConstructor = null; + } + } + + /// + public override void Dispose() + { + foreach (EventSource eventSource in this.eventSources) + { + this.DisableEvents(eventSource); + } + + this.eventSources.Clear(); + + if (this.disposeProvider) + { + this.openTelemetryLoggerProvider.Dispose(); + } + + base.Dispose(); + } + +#pragma warning disable CA1062 // Validate arguments of public methods + /// + protected override void OnEventSourceCreated(EventSource eventSource) + { + Debug.Assert(eventSource != null, "EventSource was null."); + + try + { + if (this.eventSourcesBeforeConstructor != null) + { + lock (this.lockObj) + { + if (this.eventSourcesBeforeConstructor != null) + { + this.eventSourcesBeforeConstructor.Add(eventSource!); + return; + } + } + } + + this.ProcessSource(eventSource!); + } + finally + { + base.OnEventSourceCreated(eventSource); + } + } +#pragma warning restore CA1062 // Validate arguments of public methods + +#pragma warning disable CA1062 // Validate arguments of public methods + /// + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + Debug.Assert(eventData != null, "EventData was null."); + + string? rawMessage = eventData!.Message; + + LogRecordData data = new(Activity.Current) + { +#if NETSTANDARD2_1_OR_GREATER + Timestamp = eventData.TimeStamp, +#endif + EventId = new EventId(eventData.EventId, eventData.EventName), + LogLevel = ConvertEventLevelToLogLevel(eventData.Level), + }; + + LogRecordAttributeList attributes = default; + + attributes.Add("event_source.name", eventData.EventSource.Name); + + if (eventData.ActivityId != Guid.Empty) + { + attributes.Add("event_source.activity_id", eventData.ActivityId); + } + + if (eventData.RelatedActivityId != Guid.Empty) + { + attributes.Add("event_source.related_activity_id", eventData.RelatedActivityId); + } + + int payloadCount = eventData.Payload?.Count ?? 0; + + if (payloadCount > 0 && payloadCount == eventData.PayloadNames?.Count) + { + for (int i = 0; i < payloadCount; i++) + { + string name = eventData.PayloadNames[i]; + + if (!string.IsNullOrEmpty(rawMessage) && !this.includeFormattedMessage) + { + // TODO: This code converts the event message from + // string.Format syntax (eg: "Some message {0} {1}") + // into structured log format (eg: "Some message + // {propertyName1} {propertyName2}") but it is + // expensive. Probably needs a cache. +#if NETSTANDARD2_0 + rawMessage = rawMessage.Replace($"{{{i}}}", $"{{{name}}}"); +#else + rawMessage = rawMessage.Replace($"{{{i}}}", $"{{{name}}}", StringComparison.Ordinal); +#endif + } + + attributes.Add(name, eventData.Payload![i]); + } + } + + if (!string.IsNullOrEmpty(rawMessage) && this.includeFormattedMessage && payloadCount > 0) + { + rawMessage = string.Format(CultureInfo.InvariantCulture, rawMessage, eventData.Payload!.ToArray()); + } + + data.Message = rawMessage; + + this.logEmitter.Emit(in data, in attributes); + } +#pragma warning restore CA1062 // Validate arguments of public methods + + private static LogLevel ConvertEventLevelToLogLevel(EventLevel eventLevel) + { + return eventLevel switch + { + EventLevel.Informational => LogLevel.Information, + EventLevel.Warning => LogLevel.Warning, + EventLevel.Error => LogLevel.Error, + EventLevel.Critical => LogLevel.Critical, + _ => LogLevel.Trace, + }; + } + + private void ProcessSource(EventSource eventSource) + { + EventLevel? eventLevel = this.shouldListenToFunc(eventSource.Name); + + if (eventLevel.HasValue) + { + this.eventSources.Add(eventSource); + this.EnableEvents(eventSource, eventLevel.Value, EventKeywords.All); + } + } + } +} diff --git a/src/OpenTelemetry.Extensions.EventSource/README.md b/src/OpenTelemetry.Extensions.EventSource/README.md new file mode 100644 index 000000000..5259aae34 --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/README.md @@ -0,0 +1,38 @@ +# OpenTelemetry.Extensions.EventSource + +[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Extensions.EventSource.svg)](https://www.nuget.org/packages/OpenTelemetry.Extensions.EventSource) +[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Extensions.EventSource.svg)](https://www.nuget.org/packages/OpenTelemetry.Extensions.EventSource) + +This project contains an +[EventListener](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventlistener) +which can be used to translate events written to an +[EventSource](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventsource) +into OpenTelemetry logs. + +## Installation + +```shell +dotnet add package OpenTelemetry.Extensions.EventSource --prerelease +``` + +## Usage Example + +```csharp +// Step 1: Configure OpenTelemetryLoggerProvider... +var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => +{ + options + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyService")) + .AddConsoleExporter(); +}); + +// Step 2: Create OpenTelemetryEventSourceLogEmitter to listen to events... +using var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => name.StartsWith("OpenTelemetry") ? EventLevel.LogAlways : null, + disposeProvider: true); +``` + +## References + +* [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md index 799952828..76d47d932 100644 --- a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.0.0-rc9.5 + +Released 2022-Aug-02 + ## 1.0.0-rc9.4 Released 2022-Jun-03 @@ -37,7 +41,8 @@ Released 2022-Feb-02 Released 2021-Oct-08 * Removes upper constraint for Microsoft.Extensions.Hosting.Abstractions - dependency. ([#2179](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2179)) + dependency. + ([#2179](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2179)) * Added `AddOpenTelemetryMetrics` extensions on `IServiceCollection` to register OpenTelemetry `MeterProvider` with application services. Added diff --git a/src/OpenTelemetry.Extensions.Propagators/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.Propagators/.publicApi/net462/PublicAPI.Unshipped.txt index e69de29bb..ee1db2f0d 100644 --- a/src/OpenTelemetry.Extensions.Propagators/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Extensions.Propagators/.publicApi/net462/PublicAPI.Unshipped.txt @@ -0,0 +1,5 @@ +OpenTelemetry.Extensions.Propagators.JaegerPropagator +OpenTelemetry.Extensions.Propagators.JaegerPropagator.JaegerPropagator() -> void +override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func> getter) -> OpenTelemetry.Context.Propagation.PropagationContext +override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Fields.get -> System.Collections.Generic.ISet +override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action setter) -> void diff --git a/src/OpenTelemetry.Extensions.Propagators/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.Propagators/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index e69de29bb..ee1db2f0d 100644 --- a/src/OpenTelemetry.Extensions.Propagators/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Extensions.Propagators/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,5 @@ +OpenTelemetry.Extensions.Propagators.JaegerPropagator +OpenTelemetry.Extensions.Propagators.JaegerPropagator.JaegerPropagator() -> void +override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Extract(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Func> getter) -> OpenTelemetry.Context.Propagation.PropagationContext +override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Fields.get -> System.Collections.Generic.ISet +override OpenTelemetry.Extensions.Propagators.JaegerPropagator.Inject(OpenTelemetry.Context.Propagation.PropagationContext context, T carrier, System.Action setter) -> void diff --git a/src/OpenTelemetry.Extensions.Propagators/CHANGELOG.md b/src/OpenTelemetry.Extensions.Propagators/CHANGELOG.md index 1e23a998e..d4a09cab2 100644 --- a/src/OpenTelemetry.Extensions.Propagators/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Propagators/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +## 1.4.0-alpha.1 + +Released 2022-Aug-02 + +* Add `JaegerPropagator`. + ([1881](https://github.com/open-telemetry/opentelemetry-dotnet/issues/1881)) + ## 1.3.0 Released 2022-Jun-03 diff --git a/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs b/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs new file mode 100644 index 000000000..a3fa2e98d --- /dev/null +++ b/src/OpenTelemetry.Extensions.Propagators/JaegerPropagator.cs @@ -0,0 +1,180 @@ +// +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using OpenTelemetry.Context.Propagation; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Extensions.Propagators; + +/// +/// A text map propagator for Jaeger trace context. See https://www.jaegertracing.io/docs/next-release/client-libraries/#propagation-format. +/// +public class JaegerPropagator : TextMapPropagator +{ + internal const string JaegerHeader = "uber-trace-id"; + internal const string JaegerDelimiter = ":"; + internal const string JaegerDelimiterEncoded = "%3A"; // while the spec defines the delimiter as a ':', some clients will url encode headers. + internal const string SampledValue = "1"; + + internal static readonly string[] JaegerDelimiters = { JaegerDelimiter, JaegerDelimiterEncoded }; + + private static readonly int TraceId128BitLength = "0af7651916cd43dd8448eb211c80319c".Length; + private static readonly int SpanIdLength = "00f067aa0ba902b7".Length; + + /// + public override ISet Fields => new HashSet { JaegerHeader }; + + /// + public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + { + if (context.ActivityContext.IsValid()) + { + // If a valid context has already been extracted, perform a noop. + return context; + } + + if (carrier == null) + { + OpenTelemetryPropagatorsEventSource.Log.FailedToExtractActivityContext(nameof(JaegerPropagator), "null carrier"); + return context; + } + + if (getter == null) + { + OpenTelemetryPropagatorsEventSource.Log.FailedToExtractActivityContext(nameof(JaegerPropagator), "null getter"); + return context; + } + + try + { + var jaegerHeaderCollection = getter(carrier, JaegerHeader); + var jaegerHeader = jaegerHeaderCollection?.First(); + + if (string.IsNullOrWhiteSpace(jaegerHeader)) + { + return context; + } + + var jaegerHeaderParsed = TryExtractTraceContext(jaegerHeader, out var traceId, out var spanId, out var traceOptions); + + if (!jaegerHeaderParsed) + { + return context; + } + + return new PropagationContext( + new ActivityContext(traceId, spanId, traceOptions, isRemote: true), + context.Baggage); + } + catch (Exception ex) + { + OpenTelemetryPropagatorsEventSource.Log.ActivityContextExtractException(nameof(JaegerPropagator), ex); + } + + return context; + } + + /// + public override void Inject(PropagationContext context, T carrier, Action setter) + { + // from https://www.jaegertracing.io/docs/next-release/client-libraries/#propagation-format + // parent id is optional and deprecated, will not attempt to set it. + // 128 bit uber-trace-id=e0ad975be108cd107990683f59cda9e6:e422f3fe664f6342:0:1 + const string defaultParentSpanId = "0"; + + if (context.ActivityContext.TraceId == default || context.ActivityContext.SpanId == default) + { + OpenTelemetryPropagatorsEventSource.Log.FailedToInjectActivityContext(nameof(JaegerPropagator), "Invalid context"); + return; + } + + if (carrier == null) + { + OpenTelemetryPropagatorsEventSource.Log.FailedToInjectActivityContext(nameof(JaegerPropagator), "null carrier"); + return; + } + + if (setter == null) + { + OpenTelemetryPropagatorsEventSource.Log.FailedToInjectActivityContext(nameof(JaegerPropagator), "null setter"); + return; + } + + var flags = (context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? "1" : "0"; + + var jaegerTrace = string.Join( + JaegerDelimiter, + context.ActivityContext.TraceId.ToHexString(), + context.ActivityContext.SpanId.ToHexString(), + defaultParentSpanId, + flags); + + setter(carrier, JaegerHeader, jaegerTrace); + } + + internal static bool TryExtractTraceContext(string jaegerHeader, out ActivityTraceId traceId, out ActivitySpanId spanId, out ActivityTraceFlags traceOptions) + { + // from https://www.jaegertracing.io/docs/next-release/client-libraries/#propagation-format + // parent id is optional and deprecated. will not attempt to store it. + // 128 bit uber-trace-id=e0ad975be108cd107990683f59cda9e6:e422f3fe664f6342:0:1 + // 128 bit with encoded delimiter uber-trace-id=e0ad975be108cd107990683f59cda9e6%3Ae422f3fe664f6342%3A0%3A1 + // 64 bit uber-trace-id=7990683f59cda9e6:e422f3fe664f6342:0:1 + // 64 bit with encoded delimiter uber-trace-id=7990683f59cda9e6%3Ae422f3fe664f6342%3A0%3A1 + + traceId = default; + spanId = default; + traceOptions = ActivityTraceFlags.None; + + if (string.IsNullOrWhiteSpace(jaegerHeader)) + { + return false; + } + + var traceComponents = jaegerHeader.Split(JaegerDelimiters, StringSplitOptions.RemoveEmptyEntries); + if (traceComponents.Length != 4) + { + return false; + } + + var traceIdStr = traceComponents[0]; + if (traceIdStr.Length < TraceId128BitLength) + { + traceIdStr = traceIdStr.PadLeft(TraceId128BitLength, '0'); + } + + traceId = ActivityTraceId.CreateFromString(traceIdStr.AsSpan()); + + var spanIdStr = traceComponents[1]; + if (spanIdStr.Length < SpanIdLength) + { + spanIdStr = spanIdStr.PadLeft(SpanIdLength, '0'); + } + + spanId = ActivitySpanId.CreateFromString(spanIdStr.AsSpan()); + + var traceFlagsStr = traceComponents[3]; + if (SampledValue.Equals(traceFlagsStr)) + { + traceOptions |= ActivityTraceFlags.Recorded; + } + + return true; + } +} diff --git a/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Shipped.txt new file mode 100644 index 000000000..7dc5c5811 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..87a33a78e --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,3 @@ +#nullable enable +Serilog.OpenTelemetrySerilogExtensions +static Serilog.OpenTelemetrySerilogExtensions.OpenTelemetry(this Serilog.Configuration.LoggerSinkConfiguration! loggerConfiguration, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, bool disposeProvider = true) -> Serilog.LoggerConfiguration! diff --git a/src/OpenTelemetry.Extensions.Serilog/AssemblyInfo.cs b/src/OpenTelemetry.Extensions.Serilog/AssemblyInfo.cs new file mode 100644 index 000000000..a51a83d9d --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/AssemblyInfo.cs @@ -0,0 +1,35 @@ +// +// 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; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(false)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; + public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; + public const string MoqPublicKey = ""; +} +#endif diff --git a/src/OpenTelemetry.Extensions.Serilog/CHANGELOG.md b/src/OpenTelemetry.Extensions.Serilog/CHANGELOG.md new file mode 100644 index 000000000..63bfc986b --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## Unreleased + +Initial release. diff --git a/src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj new file mode 100644 index 000000000..993c4cb48 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj @@ -0,0 +1,24 @@ + + + + netstandard2.0 + Extensions to enable OpenTelemetry logging when using the Serilog library + $(PackageTags);serilog;logging + enable + AllEnabledByDefault + latest + + + + + + + + + + + + + + + diff --git a/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogExtensions.cs b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogExtensions.cs new file mode 100644 index 000000000..cf5262f00 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogExtensions.cs @@ -0,0 +1,54 @@ +// +// 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 OpenTelemetry.Internal; +using OpenTelemetry.Logs; +using Serilog.Configuration; + +namespace Serilog +{ + /// + /// Contains Serilog extension methods. + /// + public static class OpenTelemetrySerilogExtensions + { + /// + /// Adds a sink to Serilog which will + /// write to OpenTelemetry. + /// + /// . + /// . + /// Controls whether or not the supplied + /// will be disposed when + /// the logger is disposed. Default value: . + /// Supplied for chaining calls. + public static LoggerConfiguration OpenTelemetry( + this LoggerSinkConfiguration loggerConfiguration, + OpenTelemetryLoggerProvider openTelemetryLoggerProvider, + bool disposeProvider = true) + { + Guard.ThrowIfNull(loggerConfiguration); + Guard.ThrowIfNull(openTelemetryLoggerProvider); + +#pragma warning disable CA2000 // Dispose objects before losing scope + return loggerConfiguration.Sink(new OpenTelemetrySerilogSink(openTelemetryLoggerProvider, disposeProvider)); +#pragma warning restore CA2000 // Dispose objects before losing scope + } + } +} diff --git a/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogSink.cs b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogSink.cs new file mode 100644 index 000000000..49e866532 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogSink.cs @@ -0,0 +1,135 @@ +// +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Extensions.Logging; +using Serilog.Core; +using Serilog.Events; + +namespace OpenTelemetry.Logs +{ + internal sealed class OpenTelemetrySerilogSink : ILogEventSink, IDisposable + { + private readonly OpenTelemetryLoggerProvider openTelemetryLoggerProvider; + private readonly bool includeFormattedMessage; + private readonly LogEmitter logEmitter; + private readonly bool disposeProvider; + + public OpenTelemetrySerilogSink(OpenTelemetryLoggerProvider openTelemetryLoggerProvider, bool disposeProvider) + { + Debug.Assert(openTelemetryLoggerProvider != null, "openTelemetryLoggerProvider was null"); + + this.openTelemetryLoggerProvider = openTelemetryLoggerProvider!; + this.disposeProvider = disposeProvider; + + var logEmitter = this.openTelemetryLoggerProvider.CreateEmitter(); + Debug.Assert(logEmitter != null, "logEmitter was null"); + + this.logEmitter = logEmitter!; + + // TODO: This project can only access IncludeFormattedMessage + // because it can see SDK internals. At some point this is likely + // not to be the case. Need to figure out where to put + // IncludeFormattedMessage so that extensions can see it. Ideas: + // Make it public on OpenTelemetryLoggerProvider or expose it on + // LogEmitter instance. + this.includeFormattedMessage = this.openTelemetryLoggerProvider.IncludeFormattedMessage; + } + + public void Emit(LogEvent logEvent) + { + Debug.Assert(logEvent != null, "LogEvent was null."); + + LogRecordData data = new(Activity.Current) + { + Timestamp = logEvent!.Timestamp.UtcDateTime, + LogLevel = (LogLevel)(int)logEvent.Level, + Message = this.includeFormattedMessage ? logEvent.RenderMessage() : logEvent.MessageTemplate.Text, + Exception = logEvent.Exception, + }; + + LogRecordAttributeList attributes = default; + foreach (KeyValuePair property in logEvent.Properties) + { + // TODO: Serilog supports complex type logging. This is not yet + // supported in OpenTelemetry. + if (property.Key == Constants.SourceContextPropertyName + && property.Value is ScalarValue sourceContextValue) + { + data.CategoryName = sourceContextValue.Value as string; + } + else if (property.Value is ScalarValue scalarValue) + { + attributes.Add(property.Key, scalarValue.Value); + } + else if (property.Value is SequenceValue sequenceValue) + { + IReadOnlyList elements = sequenceValue.Elements; + if (elements.Count > 0) + { + // Note: The goal here is to build a typed array (eg + // int[]) if all the element types match otherwise + // fallback to object[] + + Type? elementType = null; + Array? values = null; + + for (int i = 0; i < elements.Count; i++) + { + if (elements[i] is ScalarValue value) + { + Type currentElementType = value.Value?.GetType() ?? typeof(object); + + if (values == null) + { + elementType = currentElementType; + values = Array.CreateInstance(elementType, elements.Count); + } + else if (!elementType!.IsAssignableFrom(currentElementType)) + { + // Array with mixed types detected + object[] newValues = new object[elements.Count]; + values.CopyTo(newValues, 0); + values = newValues; + elementType = typeof(object); + } + + values.SetValue(value.Value, i); + } + } + + if (values != null) + { + attributes.Add(property.Key, values); + } + } + } + } + + this.logEmitter.Emit(in data, in attributes); + } + + public void Dispose() + { + if (this.disposeProvider) + { + this.openTelemetryLoggerProvider.Dispose(); + } + } + } +} diff --git a/src/OpenTelemetry.Extensions.Serilog/README.md b/src/OpenTelemetry.Extensions.Serilog/README.md new file mode 100644 index 000000000..39372b4e1 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/README.md @@ -0,0 +1,38 @@ +# OpenTelemetry.Extensions.Serilog + +[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Extensions.Serilog.svg)](https://www.nuget.org/packages/OpenTelemetry.Extensions.Serilog) +[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Extensions.Serilog.svg)](https://www.nuget.org/packages/OpenTelemetry.Extensions.Serilog) + +This project contains a [Serilog](https://github.com/serilog/) +[sink](https://github.com/serilog/serilog/wiki/Configuration-Basics#sinks) for +writing log messages to OpenTelemetry. + +## Installation + +```shell +dotnet add package OpenTelemetry.Extensions.Serilog --prerelease +``` + +## Usage Example + +```csharp +// Step 1: Configure OpenTelemetryLoggerProvider... +var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => +{ + options + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyService")) + .AddConsoleExporter(); +}); + +// Step 2: Register OpenTelemetry sink with Serilog... +Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true) + .CreateLogger(); + +// Step 3: When application is shutdown flush all log messages and dispose provider... +Log.CloseAndFlush(); +``` + +## References + +* [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net462/PublicAPI.Unshipped.txt deleted file mode 100644 index 367f7a1e6..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net462/PublicAPI.Unshipped.txt +++ /dev/null @@ -1,16 +0,0 @@ -const OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.AspNetActivityName = "Microsoft.AspNet.HttpReqIn" -> string -const OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.AspNetSourceName = "OpenTelemetry.Instrumentation.AspNet.Telemetry" -> string -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Dispose() -> void -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Init(System.Web.HttpApplication context) -> void -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.TelemetryHttpModule() -> void -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnExceptionCallback.get -> System.Action -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnExceptionCallback.set -> void -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnRequestStartedCallback.get -> System.Action -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnRequestStartedCallback.set -> void -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnRequestStoppedCallback.get -> System.Action -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnRequestStoppedCallback.set -> void -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.TextMapPropagator.get -> OpenTelemetry.Context.Propagation.TextMapPropagator -OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.TextMapPropagator.set -> void -static OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Options.get -> OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs deleted file mode 100644 index 4a1e648ad..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs +++ /dev/null @@ -1,224 +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; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Web; -using OpenTelemetry.Context; -using OpenTelemetry.Context.Propagation; - -namespace OpenTelemetry.Instrumentation.AspNet -{ - /// - /// Activity helper class. - /// - internal static class ActivityHelper - { - /// - /// Key to store the state in HttpContext. - /// - internal const string ContextKey = "__AspnetInstrumentationContext__"; - internal static readonly object StartedButNotSampledObj = new(); - - private const string BaggageSlotName = "otel.baggage"; - private static readonly Func> HttpRequestHeaderValuesGetter = (request, name) => request.Headers.GetValues(name); - private static readonly ActivitySource AspNetSource = new( - TelemetryHttpModule.AspNetSourceName, - typeof(ActivityHelper).Assembly.GetName().Version.ToString()); - - /// - /// Try to get the started for the running . - /// - /// . - /// Started or if 1) start has not been called or 2) start was - /// called but sampling decided not to create an instance. - /// if start has been called. - public static bool HasStarted(HttpContext context, out Activity aspNetActivity) - { - Debug.Assert(context != null, "Context is null."); - - object itemValue = context.Items[ContextKey]; - if (itemValue is ContextHolder contextHolder) - { - aspNetActivity = contextHolder.Activity; - return true; - } - - aspNetActivity = null; - return itemValue == StartedButNotSampledObj; - } - - /// - /// Creates root (first level) activity that describes incoming request. - /// - /// . - /// . - /// Callback action. - /// New root activity. - public static Activity StartAspNetActivity(TextMapPropagator textMapPropagator, HttpContext context, Action onRequestStartedCallback) - { - Debug.Assert(context != null, "Context is null."); - - PropagationContext propagationContext = textMapPropagator.Extract(default, context.Request, HttpRequestHeaderValuesGetter); - - Activity activity = AspNetSource.StartActivity(TelemetryHttpModule.AspNetActivityName, ActivityKind.Server, propagationContext.ActivityContext); - - if (activity != null) - { - if (textMapPropagator is not TraceContextPropagator) - { - Baggage.Current = propagationContext.Baggage; - - context.Items[ContextKey] = new ContextHolder { Activity = activity, Baggage = RuntimeContext.GetValue(BaggageSlotName) }; - } - else - { - context.Items[ContextKey] = new ContextHolder { Activity = activity }; - } - - try - { - onRequestStartedCallback?.Invoke(activity, context); - } - catch (Exception callbackEx) - { - AspNetTelemetryEventSource.Log.CallbackException(activity, "OnStarted", callbackEx); - } - - AspNetTelemetryEventSource.Log.ActivityStarted(activity); - } - else - { - context.Items[ContextKey] = StartedButNotSampledObj; - } - - return activity; - } - - /// - /// Stops the activity and notifies listeners about it. - /// - /// . - /// . - /// . - /// Callback action. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void StopAspNetActivity(TextMapPropagator textMapPropagator, Activity aspNetActivity, HttpContext context, Action onRequestStoppedCallback) - { - Debug.Assert(context != null, "Context is null."); - - if (aspNetActivity == null) - { - Debug.Assert(context.Items[ContextKey] == StartedButNotSampledObj, "Context item is not StartedButNotSampledObj."); - - // This is the case where a start was called but no activity was - // created due to a sampling decision. - context.Items[ContextKey] = null; - return; - } - - Debug.Assert(context.Items[ContextKey] is ContextHolder, "Context item is not an ContextHolder instance."); - - var currentActivity = Activity.Current; - - aspNetActivity.Stop(); - context.Items[ContextKey] = null; - - try - { - onRequestStoppedCallback?.Invoke(aspNetActivity, context); - } - catch (Exception callbackEx) - { - AspNetTelemetryEventSource.Log.CallbackException(aspNetActivity, "OnStopped", callbackEx); - } - - AspNetTelemetryEventSource.Log.ActivityStopped(currentActivity); - - if (textMapPropagator is not TraceContextPropagator) - { - Baggage.Current = default; - } - - if (currentActivity != aspNetActivity) - { - Activity.Current = currentActivity; - } - } - - /// - /// Notifies listeners about an unhandled exception thrown on the . - /// - /// . - /// . - /// . - /// Callback action. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteActivityException(Activity aspNetActivity, HttpContext context, Exception exception, Action onExceptionCallback) - { - Debug.Assert(context != null, "Context is null."); - Debug.Assert(exception != null, "Exception is null."); - - if (aspNetActivity != null) - { - try - { - onExceptionCallback?.Invoke(aspNetActivity, context, exception); - } - catch (Exception callbackEx) - { - AspNetTelemetryEventSource.Log.CallbackException(aspNetActivity, "OnException", callbackEx); - } - - AspNetTelemetryEventSource.Log.ActivityException(aspNetActivity, exception); - } - } - - /// - /// It's possible that a request is executed in both native threads and managed threads, - /// in such case Activity.Current will be lost during native thread and managed thread switch. - /// This method is intended to restore the current activity in order to correlate the child - /// activities with the root activity of the request. - /// - /// . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void RestoreContextIfNeeded(HttpContext context) - { - Debug.Assert(context != null, "Context is null."); - - if (context.Items[ContextKey] is ContextHolder contextHolder && Activity.Current != contextHolder.Activity) - { - Activity.Current = contextHolder.Activity; - if (contextHolder.Baggage != null) - { - RuntimeContext.SetValue(BaggageSlotName, contextHolder.Baggage); - } - - AspNetTelemetryEventSource.Log.ActivityRestored(contextHolder.Activity); - } - } - - internal class ContextHolder - { - public Activity Activity; - public object Baggage; - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs deleted file mode 100644 index 3fd4e239d..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs +++ /dev/null @@ -1,122 +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; -using System.Diagnostics; -using System.Diagnostics.Tracing; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Instrumentation.AspNet -{ - /// - /// ETW EventSource tracing class. - /// - [EventSource(Name = "OpenTelemetry-Instrumentation-AspNet-Telemetry", Guid = "1de158cc-f7ce-4293-bd19-2358c93c8186")] - internal sealed class AspNetTelemetryEventSource : EventSource - { - /// - /// Instance of the PlatformEventSource class. - /// - public static readonly AspNetTelemetryEventSource Log = new(); - - [NonEvent] - public void ActivityStarted(Activity activity) - { - if (this.IsEnabled(EventLevel.Verbose, EventKeywords.All)) - { - this.ActivityStarted(activity?.Id); - } - } - - [NonEvent] - public void ActivityStopped(Activity activity) - { - if (this.IsEnabled(EventLevel.Verbose, EventKeywords.All)) - { - this.ActivityStopped(activity?.Id); - } - } - - [NonEvent] - public void ActivityRestored(Activity activity) - { - if (this.IsEnabled(EventLevel.Informational, EventKeywords.All)) - { - this.ActivityRestored(activity?.Id); - } - } - - [NonEvent] - public void ActivityException(Activity activity, Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.ActivityException(activity?.Id, ex.ToInvariantString()); - } - } - - [NonEvent] - public void CallbackException(Activity activity, string eventName, Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.CallbackException(activity?.Id, eventName, ex.ToInvariantString()); - } - } - - [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] - public void TraceCallback(string callback) - { - this.WriteEvent(1, callback); - } - - [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] - public void ActivityStarted(string id) - { - this.WriteEvent(2, id); - } - - [Event(3, Message = "Activity stopped, Id='{0}'", Level = EventLevel.Verbose)] - public void ActivityStopped(string id) - { - this.WriteEvent(3, id); - } - - [Event(4, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] - public void ActivityRestored(string id) - { - this.WriteEvent(4, id); - } - - [Event(5, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] - public void OnExecuteRequestStepInvocationError(string error) - { - this.WriteEvent(5, error); - } - - [Event(6, Message = "Activity exception, Id='{0}': {1}", Level = EventLevel.Error)] - public void ActivityException(string id, string ex) - { - this.WriteEvent(6, id, ex); - } - - [Event(7, Message = "Callback exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] - public void CallbackException(string id, string eventName, string ex) - { - this.WriteEvent(7, id, eventName, ex); - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AssemblyInfo.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AssemblyInfo.cs deleted file mode 100644 index 5f10011c2..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AssemblyInfo.cs +++ /dev/null @@ -1,28 +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.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: ComVisible(false)] - -#if SIGNED -[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.AspNet.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -#else -[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests")] -[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.AspNet.Tests")] -#endif diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md deleted file mode 100644 index fbb62f7eb..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md +++ /dev/null @@ -1,59 +0,0 @@ -# Changelog - -## Unreleased - -## 1.0.0-rc9.4 - -Released 2022-Jun-03 - -## 1.0.0-rc9.3 - -Released 2022-Apr-15 - -* Removes .NET Framework 4.6.1. The minimum .NET Framework - version supported is .NET 4.6.2. ([#3190](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3190)) - -## 1.0.0-rc9.2 - -Released 2022-Apr-12 - -## 1.0.0-rc9.1 - -Released 2022-Mar-30 - -## 1.0.0-rc10 (broken. use 1.0.0-rc9.1 and newer) - -Released 2022-Mar-04 - -## 1.0.0-rc9 - -Released 2022-Feb-02 - -## 1.0.0-rc8 - -Released 2021-Oct-08 - -* Adopted the donation - [Microsoft.AspNet.TelemetryCorrelation](https://github.com/aspnet/Microsoft.AspNet.TelemetryCorrelation) - from [.NET Foundation](https://dotnetfoundation.org/) - ([#2223](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2223)) - -* Renamed the module, refactored existing code - ([#2224](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2224) - [#2225](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2225) - [#2226](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2226) - [#2229](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2229) - [#2231](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2231) - [#2235](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2235) - [#2238](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2238) - [#2240](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2240)) - -* Updated to use - [ActivitySource](https://docs.microsoft.com/dotnet/api/system.diagnostics.activitysource) - & OpenTelemetry.API - ([#2249](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2249) & - follow-ups (linked to #2249)) - -* TelemetryHttpModule will now restore Baggage on .NET 4.7.1+ runtimes when IIS - switches threads - ([#2314](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2314)) diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj deleted file mode 100644 index 924be5d24..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net462 - A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. - $(PackageTags);distributed-tracing;AspNet;MVC;WebAPI - - - - - - - - - - - - - - - - - - - - diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/README.md b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/README.md deleted file mode 100644 index c2d88b999..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/README.md +++ /dev/null @@ -1,136 +0,0 @@ -# ASP.NET Telemetry HttpModule for OpenTelemetry - -[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/) - -The ASP.NET Telemetry HttpModule enables distributed tracing of incoming ASP.NET -requests using the OpenTelemetry API. - -## Usage - -### Step 1: Install NuGet package - -If you are using the traditional `packages.config` reference style, a -`web.config` transform should run automatically and configure the -`TelemetryHttpModule` for you. If you are using the more modern PackageReference -style, this may be needed to be done manually. For more information, see: -[Migrate from packages.config to -PackageReference](https://docs.microsoft.com/nuget/consume-packages/migrate-packages-config-to-package-reference). - -To configure your `web.config` manually, add this: - -```xml - - - - - -``` - -### Step 2: Register a listener - -`TelemetryHttpModule` registers an -[ActivitySource](https://docs.microsoft.com/dotnet/api/system.diagnostics.activitysource) -with the name `OpenTelemetry.Instrumentation.AspNet.Telemetry`. By default, .NET -`ActivitySource` will not generate any `Activity` objects unless there is a -registered listener. - -To register a listener automatically using OpenTelemetry, please use the -[OpenTelemetry.Instrumentation.AspNet](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.AspNet/) -NuGet package. - -To register a listener manually, use code such as the following: - -```csharp -using System.Diagnostics; -using System.Web; -using System.Web.Http; -using System.Web.Mvc; -using System.Web.Routing; -using OpenTelemetry.Instrumentation.AspNet; - -namespace Examples.AspNet -{ - public class WebApiApplication : HttpApplication - { - private ActivityListener aspNetActivityListener; - - protected void Application_Start() - { - this.aspNetActivityListener = new ActivityListener - { - ShouldListenTo = (activitySource) => - { - // Only listen to TelemetryHttpModule's ActivitySource. - return activitySource.Name == TelemetryHttpModule.AspNetSourceName; - }, - Sample = (ref ActivityCreationOptions options) => - { - // Sample everything created by TelemetryHttpModule's ActivitySource. - return ActivitySamplingResult.AllDataAndRecorded; - }, - }; - - ActivitySource.AddActivityListener(this.aspNetActivityListener); - - GlobalConfiguration.Configure(WebApiConfig.Register); - - AreaRegistration.RegisterAllAreas(); - RouteConfig.RegisterRoutes(RouteTable.Routes); - } - - protected void Application_End() - { - this.aspNetActivityListener?.Dispose(); - } - } -} -``` - -## Options - -`TelemetryHttpModule` provides a static options property -(`TelemetryHttpModule.Options`) which can be used to configure the -`TelemetryHttpModule` and listen to events it fires. - -### TextMapPropagator - -`TextMapPropagator` controls how trace context will be extracted from incoming -Http request messages. By default, [W3C Trace -Context](https://www.w3.org/TR/trace-context/) is enabled. - -The OpenTelemetry API ships with a handful of [standard -implementations](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry.Api/Context/Propagation) -which may be used, or you can write your own by deriving from the -`TextMapPropagator` class. - -To add support for -[Baggage](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/baggage/api.md) -propagation in addition to W3C Trace Context, use: - -```csharp -TelemetryHttpModuleOptions.TextMapPropagator = new CompositeTextMapPropagator( - new TextMapPropagator[] - { - new TraceContextPropagator(), - new BaggagePropagator(), - }); -``` - -Note: When using the `OpenTelemetry.Instrumentation.AspNet` -`TelemetryHttpModuleOptions.TextMapPropagator` is automatically initialized to -the SDK default propagator (`Propagators.DefaultTextMapPropagator`) which by -default supports W3C Trace Context & Baggage. - -### Events - -`OnRequestStartedCallback`, `OnRequestStoppedCallback`, & `OnExceptionCallback` -are provided on `TelemetryHttpModuleOptions` and will be fired by the -`TelemetryHttpModule` as requests are processed. - -A typical use case for these events is to add information (tags, events, and/or -links) to the created `Activity` based on the request, response, and/or -exception event being fired. diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModule.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModule.cs deleted file mode 100644 index 395c5aec1..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModule.cs +++ /dev/null @@ -1,147 +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; -using System.Diagnostics; -using System.Reflection; -using System.Web; - -namespace OpenTelemetry.Instrumentation.AspNet -{ - /// - /// Http Module sets ambient state using Activity API from DiagnosticsSource package. - /// - public class TelemetryHttpModule : IHttpModule - { - /// - /// OpenTelemetry.Instrumentation.AspNet name. - /// - public const string AspNetSourceName = "OpenTelemetry.Instrumentation.AspNet.Telemetry"; - - /// - /// for OpenTelemetry.Instrumentation.AspNet created objects. - /// - public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; - - // ServerVariable set only on rewritten HttpContext by URL Rewrite module. - private const string URLRewriteRewrittenRequest = "IIS_WasUrlRewritten"; - - // ServerVariable set on every request if URL module is registered in HttpModule pipeline. - private const string URLRewriteModuleVersion = "IIS_UrlRewriteModule"; - - private static readonly MethodInfo OnExecuteRequestStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); - - /// - /// Gets the applied to requests processed by the handler. - /// - public static TelemetryHttpModuleOptions Options { get; } = new TelemetryHttpModuleOptions(); - - /// - public void Dispose() - { - } - - /// - public void Init(HttpApplication context) - { - context.BeginRequest += this.Application_BeginRequest; - context.EndRequest += this.Application_EndRequest; - context.Error += this.Application_Error; - - if (HttpRuntime.UsingIntegratedPipeline && OnExecuteRequestStepMethodInfo != null) - { - // OnExecuteRequestStep is availabile starting with 4.7.1 - try - { - OnExecuteRequestStepMethodInfo.Invoke(context, new object[] { (Action)this.OnExecuteRequestStep }); - } - catch (Exception e) - { - AspNetTelemetryEventSource.Log.OnExecuteRequestStepInvocationError(e.Message); - } - } - } - - private void Application_BeginRequest(object sender, EventArgs e) - { - AspNetTelemetryEventSource.Log.TraceCallback("Application_BeginRequest"); - ActivityHelper.StartAspNetActivity(Options.TextMapPropagator, ((HttpApplication)sender).Context, Options.OnRequestStartedCallback); - } - - private void OnExecuteRequestStep(HttpContextBase context, Action step) - { - // Called only on 4.7.1+ runtimes - - if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) - { - ActivityHelper.RestoreContextIfNeeded(context.ApplicationInstance.Context); - } - - step(); - } - - private void Application_EndRequest(object sender, EventArgs e) - { - AspNetTelemetryEventSource.Log.TraceCallback("Application_EndRequest"); - bool trackActivity = true; - - var context = ((HttpApplication)sender).Context; - - if (!ActivityHelper.HasStarted(context, out Activity aspNetActivity)) - { - // Rewrite: In case of rewrite, a new request context is created, called the child request, and it goes through the entire IIS/ASP.NET integrated pipeline. - // The child request can be mapped to any of the handlers configured in IIS, and it's execution is no different than it would be if it was received via the HTTP stack. - // The parent request jumps ahead in the pipeline to the end request notification, and waits for the child request to complete. - // When the child request completes, the parent request executes the end request notifications and completes itself. - // Do not create activity for parent request. Parent request has IIS_UrlRewriteModule ServerVariable with success response code. - // Child request contains an additional ServerVariable named - IIS_WasUrlRewritten. - // Track failed response activity: Different modules in the pipeline has ability to end the response. For example, authentication module could set HTTP 401 in OnBeginRequest and end the response. - if (context.Request.ServerVariables != null && context.Request.ServerVariables[URLRewriteRewrittenRequest] == null && context.Request.ServerVariables[URLRewriteModuleVersion] != null && context.Response.StatusCode == 200) - { - trackActivity = false; - } - else - { - // Activity has never been started - aspNetActivity = ActivityHelper.StartAspNetActivity(Options.TextMapPropagator, context, Options.OnRequestStartedCallback); - } - } - - if (trackActivity) - { - ActivityHelper.StopAspNetActivity(Options.TextMapPropagator, aspNetActivity, context, Options.OnRequestStoppedCallback); - } - } - - private void Application_Error(object sender, EventArgs e) - { - AspNetTelemetryEventSource.Log.TraceCallback("Application_Error"); - - var context = ((HttpApplication)sender).Context; - - var exception = context.Error; - if (exception != null) - { - if (!ActivityHelper.HasStarted(context, out Activity aspNetActivity)) - { - aspNetActivity = ActivityHelper.StartAspNetActivity(Options.TextMapPropagator, context, Options.OnRequestStartedCallback); - } - - ActivityHelper.WriteActivityException(aspNetActivity, context, exception, Options.OnExceptionCallback); - } - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModuleOptions.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModuleOptions.cs deleted file mode 100644 index 2d0e42efb..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModuleOptions.cs +++ /dev/null @@ -1,67 +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; -using System.Diagnostics; -using System.Web; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Instrumentation.AspNet -{ - /// - /// Stores options for the . - /// - public class TelemetryHttpModuleOptions - { - private TextMapPropagator textMapPropagator = new TraceContextPropagator(); - - internal TelemetryHttpModuleOptions() - { - } - - /// - /// Gets or sets the to use to - /// extract from incoming requests. - /// - public TextMapPropagator TextMapPropagator - { - get => this.textMapPropagator; - set - { - Guard.ThrowIfNull(value); - - this.textMapPropagator = value; - } - } - - /// - /// Gets or sets a callback action to be fired when a request is started. - /// - public Action OnRequestStartedCallback { get; set; } - - /// - /// Gets or sets a callback action to be fired when a request is stopped. - /// - public Action OnRequestStoppedCallback { get; set; } - - /// - /// Gets or sets a callback action to be fired when an unhandled - /// exception is thrown processing a request. - /// - public Action OnExceptionCallback { get; set; } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.install.xdt b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.install.xdt deleted file mode 100644 index 660535465..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.install.xdt +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.uninstall.xdt b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.uninstall.xdt deleted file mode 100644 index 5b76f2bc4..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.uninstall.xdt +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/OpenTelemetry.Instrumentation.AspNet/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry.Instrumentation.AspNet/.publicApi/net462/PublicAPI.Shipped.txt deleted file mode 100644 index 5f282702b..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/.publicApi/net462/PublicAPI.Shipped.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/OpenTelemetry.Instrumentation.AspNet/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.AspNet/.publicApi/net462/PublicAPI.Unshipped.txt deleted file mode 100644 index a9ba7f916..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/.publicApi/net462/PublicAPI.Unshipped.txt +++ /dev/null @@ -1,12 +0,0 @@ -OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions -OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.AspNetInstrumentationOptions() -> void -OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.Enrich.get -> System.Action -OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.Enrich.set -> void -OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.Filter.get -> System.Func -OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.Filter.set -> void -OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.RecordException.get -> bool -OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.RecordException.set -> void -OpenTelemetry.Metrics.MeterProviderBuilderExtensions -OpenTelemetry.Trace.TracerProviderBuilderExtensions -static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddAspNetInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configureAspNetInstrumentationOptions = null) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs b/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs deleted file mode 100644 index 2bd2a988b..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs +++ /dev/null @@ -1,44 +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; -using OpenTelemetry.Instrumentation.AspNet.Implementation; - -namespace OpenTelemetry.Instrumentation.AspNet -{ - /// - /// Asp.Net Requests instrumentation. - /// - internal sealed class AspNetInstrumentation : IDisposable - { - private readonly HttpInListener httpInListener; - - /// - /// Initializes a new instance of the class. - /// - /// Configuration options for ASP.NET instrumentation. - public AspNetInstrumentation(AspNetInstrumentationOptions options) - { - this.httpInListener = new HttpInListener(options); - } - - /// - public void Dispose() - { - this.httpInListener?.Dispose(); - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs deleted file mode 100644 index 4209ce23e..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs +++ /dev/null @@ -1,63 +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; -using System.Diagnostics; -using System.Web; - -namespace OpenTelemetry.Instrumentation.AspNet -{ - /// - /// Options for ASP.NET instrumentation. - /// - public class AspNetInstrumentationOptions - { - /// - /// Gets or sets a filter callback function that determines on a per - /// request basis whether or not to collect telemetry. - /// - /// - /// The filter callback receives the for the - /// current request and should return a boolean. - /// - /// If filter returns the request is - /// collected. - /// If filter returns or throws an - /// exception the request is filtered out (NOT collected). - /// - /// - public Func Filter { get; set; } - - /// - /// Gets or sets an action to enrich an Activity. - /// - /// - /// : the activity being enriched. - /// string: the name of the event. - /// object: the raw object from which additional information can be extracted to enrich the activity. - /// The type of this object depends on the event, which is given by the above parameter. - /// - public Action Enrich { get; set; } - - /// - /// Gets or sets a value indicating whether the exception will be recorded as ActivityEvent or not. - /// - /// - /// See: . - /// - public bool RecordException { get; set; } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet/AspNetMetrics.cs b/src/OpenTelemetry.Instrumentation.AspNet/AspNetMetrics.cs deleted file mode 100644 index 373fe8a55..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/AspNetMetrics.cs +++ /dev/null @@ -1,53 +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; -using System.Diagnostics.Metrics; -using System.Reflection; -using OpenTelemetry.Instrumentation.AspNet.Implementation; - -namespace OpenTelemetry.Instrumentation.AspNet -{ - /// - /// Asp.Net Requests instrumentation. - /// - internal class AspNetMetrics : IDisposable - { - internal static readonly AssemblyName AssemblyName = typeof(HttpInMetricsListener).Assembly.GetName(); - internal static readonly string InstrumentationName = AssemblyName.Name; - internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString(); - - private readonly Meter meter; - - private readonly HttpInMetricsListener httpInMetricsListener; - - /// - /// Initializes a new instance of the class. - /// - public AspNetMetrics() - { - this.meter = new Meter(InstrumentationName, InstrumentationVersion); - this.httpInMetricsListener = new HttpInMetricsListener(this.meter); - } - - /// - public void Dispose() - { - this.meter?.Dispose(); - this.httpInMetricsListener?.Dispose(); - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md deleted file mode 100644 index 9f6c1880f..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md +++ /dev/null @@ -1,162 +0,0 @@ -# Changelog - -## Unreleased - -## 1.0.0-rc9.4 - -Released 2022-Jun-03 - -## 1.0.0-rc9.3 - -Released 2022-Apr-15 - -* Removes .NET Framework 4.6.1. The minimum .NET Framework - version supported is .NET 4.6.2. ([#3190](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3190)) - -## 1.0.0-rc9.2 - -Released 2022-Apr-12 - -## 1.0.0-rc9.1 - -Released 2022-Mar-30 - -* Added ASP.NET metrics instrumentation to collect `http.server.duration`. - ([#2985](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2985)) - -* Fix: Http server span status is now unset for `400`-`499`. - ([#2904](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2904)) - -## 1.0.0-rc10 (broken. use 1.0.0-rc9.1 and newer) - -Released 2022-Mar-04 - -## 1.0.0-rc9 - -Released 2022-Feb-02 - -## 1.0.0-rc8 - -Released 2021-Oct-08 - -* Removes .NET Framework 4.5.2, .NET 4.6 support. The minimum .NET Framework - version supported is .NET 4.6.1. ([#2138](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2138)) - -* Replaced `http.path` tag on activity with `http.target`. - ([#2266](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2266)) - -* ASP.NET instrumentation now uses - [OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/) - instead of - [Microsoft.AspNet.TelemetryCorrelation](https://www.nuget.org/packages/Microsoft.AspNet.TelemetryCorrelation/) - to listen for incoming http requests to the process. Please see the (Step 2: - Modify - Web.config)[https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry.Instrumentation.AspNet#step-2-modify-webconfig] - README section for details on the new HttpModule definition required. - ([#2222](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2222)) - -* Added `RecordException` option. Specify `true` to have unhandled exception - details automatically captured on spans. - ([#2256](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2256)) - -## 1.0.0-rc7 - -Released 2021-Jul-12 - -## 1.0.0-rc6 - -Released 2021-Jun-25 - -## 1.0.0-rc5 - -Released 2021-Jun-09 - -## 1.0.0-rc4 - -Released 2021-Apr-23 - -* Sanitize `http.url` attribute. ([#1961](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1961)) - -## 1.0.0-rc3 - -Released 2021-Mar-19 - -* Leverages added AddLegacySource API from OpenTelemetry SDK to trigger Samplers - and ActivityProcessors. Samplers, ActivityProcessor.OnStart will now get the - Activity before any enrichment done by the instrumentation. - ([#1836](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1836)) -* Performance optimization by leveraging sampling decision and short circuiting - activity enrichment. - ([#1903](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1903)) - -## 1.0.0-rc2 - -Released 2021-Jan-29 - -## 1.0.0-rc1.1 - -Released 2020-Nov-17 - -* AspNetInstrumentation sets ActivitySource to activities created outside - ActivitySource. - ([#1515](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1515/)) - -## 0.8.0-beta.1 - -Released 2020-Nov-5 - -* Renamed TextMapPropagator to TraceContextPropagator, CompositePropagator to - CompositeTextMapPropagator. IPropagator is renamed to TextMapPropagator and - changed from interface to abstract class. - ([#1427](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1427)) -* Propagators.DefaultTextMapPropagator will be used as the default Propagator. - ([#1427](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1428)) -* Removed Propagator from Instrumentation Options. Instrumentation now always - respect the Propagator.DefaultTextMapPropagator. - ([#1448](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1448)) - -## 0.7.0-beta.1 - -Released 2020-Oct-16 - -* Instrumentation no longer store raw objects like `HttpRequest` in - Activity.CustomProperty. To enrich activity, use the Enrich action on the - instrumentation. - ([#1261](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1261)) -* Span Status is populated as per new spec - ([#1313](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1313)) - -## 0.6.0-beta.1 - -Released 2020-Sep-15 - -## 0.5.0-beta.2 - -Released 2020-08-28 - -* Added Filter public API on AspNetInstrumentationOptions to allow filtering of - instrumentation based on HttpContext. - -* Asp.Net Instrumentation automatically populates HttpRequest, HttpResponse in - Activity custom property - -* Changed the default propagation to support W3C Baggage - ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048)) - * The default ITextFormat is now `CompositePropagator(TraceContextFormat, - BaggageFormat)`. Baggage sent via the [W3C - Baggage](https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md) - header will now be parsed and set on incoming Http spans. -* Renamed `ITextPropagator` to `IPropagator` - ([#1190](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1190)) - -## 0.4.0-beta.2 - -Released 2020-07-24 - -* First beta release - -## 0.3.0-beta - -Released 2020-07-23 - -* Initial release diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/AspNetInstrumentationEventSource.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/AspNetInstrumentationEventSource.cs deleted file mode 100644 index 58bd374d2..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/AspNetInstrumentationEventSource.cs +++ /dev/null @@ -1,67 +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; -using System.Diagnostics.Tracing; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Instrumentation.AspNet.Implementation -{ - /// - /// EventSource events emitted from the project. - /// - [EventSource(Name = "OpenTelemetry-Instrumentation-AspNet")] - internal sealed class AspNetInstrumentationEventSource : EventSource - { - public static AspNetInstrumentationEventSource Log = new(); - - [NonEvent] - public void RequestFilterException(string operationName, Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.RequestFilterException(operationName, ex.ToInvariantString()); - } - } - - [NonEvent] - public void EnrichmentException(string eventName, Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.EnrichmentException(eventName, ex.ToInvariantString()); - } - } - - [Event(1, Message = "Request is filtered out and will not be collected. Operation='{0}'", Level = EventLevel.Verbose)] - public void RequestIsFilteredOut(string operationName) - { - this.WriteEvent(1, operationName); - } - - [Event(2, Message = "Filter callback threw an exception. Request will not be collected. Operation='{0}': {1}", Level = EventLevel.Error)] - public void RequestFilterException(string operationName, string exception) - { - this.WriteEvent(2, operationName, exception); - } - - [Event(3, Message = "Enrich callback threw an exception. Event='{0}': {1}", Level = EventLevel.Error)] - public void EnrichmentException(string eventName, string exception) - { - this.WriteEvent(3, eventName, exception); - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs deleted file mode 100644 index 946de293e..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs +++ /dev/null @@ -1,197 +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; -using System.Diagnostics; -using System.Web; -using System.Web.Routing; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Internal; -using OpenTelemetry.Trace; - -namespace OpenTelemetry.Instrumentation.AspNet.Implementation -{ - internal sealed class HttpInListener : IDisposable - { - private readonly PropertyFetcher routeFetcher = new("Route"); - private readonly PropertyFetcher routeTemplateFetcher = new("RouteTemplate"); - private readonly AspNetInstrumentationOptions options; - - public HttpInListener(AspNetInstrumentationOptions options) - { - Guard.ThrowIfNull(options); - - this.options = options; - - TelemetryHttpModule.Options.TextMapPropagator = Propagators.DefaultTextMapPropagator; - - TelemetryHttpModule.Options.OnRequestStartedCallback += this.OnStartActivity; - TelemetryHttpModule.Options.OnRequestStoppedCallback += this.OnStopActivity; - TelemetryHttpModule.Options.OnExceptionCallback += this.OnException; - } - - public void Dispose() - { - TelemetryHttpModule.Options.OnRequestStartedCallback -= this.OnStartActivity; - TelemetryHttpModule.Options.OnRequestStoppedCallback -= this.OnStopActivity; - TelemetryHttpModule.Options.OnExceptionCallback -= this.OnException; - } - - /// - /// Gets the OpenTelemetry standard uri tag value for a span based on its request . - /// - /// . - /// Span uri value. - private static string GetUriTagValueFromRequestUri(Uri uri) - { - return string.IsNullOrEmpty(uri.UserInfo) ? uri.ToString() : string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment); - } - - private void OnStartActivity(Activity activity, HttpContext context) - { - if (activity.IsAllDataRequested) - { - try - { - // todo: Ideally we would also check - // Sdk.SuppressInstrumentation here to prevent tagging a - // span that will not be collected but we can't do that - // without an SDK reference. Need the spec to come around on - // this. - - if (this.options.Filter?.Invoke(context) == false) - { - AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); - activity.IsAllDataRequested = false; - activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; - return; - } - } - catch (Exception ex) - { - AspNetInstrumentationEventSource.Log.RequestFilterException(activity.OperationName, ex); - activity.IsAllDataRequested = false; - activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; - return; - } - - var request = context.Request; - var requestValues = request.Unvalidated; - - // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md - var path = requestValues.Path; - activity.DisplayName = path; - - if (request.Url.Port == 80 || request.Url.Port == 443) - { - activity.SetTag(SemanticConventions.AttributeHttpHost, request.Url.Host); - } - else - { - activity.SetTag(SemanticConventions.AttributeHttpHost, request.Url.Host + ":" + request.Url.Port); - } - - activity.SetTag(SemanticConventions.AttributeHttpMethod, request.HttpMethod); - activity.SetTag(SemanticConventions.AttributeHttpTarget, path); - activity.SetTag(SemanticConventions.AttributeHttpUserAgent, request.UserAgent); - activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUriTagValueFromRequestUri(request.Url)); - - try - { - this.options.Enrich?.Invoke(activity, "OnStartActivity", request); - } - catch (Exception ex) - { - AspNetInstrumentationEventSource.Log.EnrichmentException("OnStartActivity", ex); - } - } - } - - private void OnStopActivity(Activity activity, HttpContext context) - { - if (activity.IsAllDataRequested) - { - var response = context.Response; - - activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode); - - if (activity.GetStatus().StatusCode == StatusCode.Unset) - { - activity.SetStatus(SpanHelper.ResolveSpanStatusForHttpStatusCode(activity.Kind, response.StatusCode)); - } - - var routeData = context.Request.RequestContext.RouteData; - - string template = null; - if (routeData.Values.TryGetValue("MS_SubRoutes", out object msSubRoutes)) - { - // WebAPI attribute routing flows here. Use reflection to not take a dependency on microsoft.aspnet.webapi.core\[version]\lib\[framework]\System.Web.Http. - - if (msSubRoutes is Array attributeRouting && attributeRouting.Length == 1) - { - var subRouteData = attributeRouting.GetValue(0); - - _ = this.routeFetcher.TryFetch(subRouteData, out var route); - _ = this.routeTemplateFetcher.TryFetch(route, out template); - } - } - else if (routeData.Route is Route route) - { - // MVC + WebAPI traditional routing & MVC attribute routing flow here. - template = route.Url; - } - - if (!string.IsNullOrEmpty(template)) - { - // Override the name that was previously set to the path part of URL. - activity.DisplayName = template; - activity.SetTag(SemanticConventions.AttributeHttpRoute, template); - } - - try - { - this.options.Enrich?.Invoke(activity, "OnStopActivity", response); - } - catch (Exception ex) - { - AspNetInstrumentationEventSource.Log.EnrichmentException("OnStopActivity", ex); - } - } - } - - private void OnException(Activity activity, HttpContext context, Exception exception) - { - if (activity.IsAllDataRequested) - { - if (this.options.RecordException) - { - activity.RecordException(exception); - } - - activity.SetStatus(Status.Error.WithDescription(exception.Message)); - - try - { - this.options.Enrich?.Invoke(activity, "OnException", exception); - } - catch (Exception ex) - { - AspNetInstrumentationEventSource.Log.EnrichmentException("OnException", ex); - } - } - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInMetricsListener.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInMetricsListener.cs deleted file mode 100644 index ece456273..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInMetricsListener.cs +++ /dev/null @@ -1,54 +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; -using System.Diagnostics; -using System.Diagnostics.Metrics; -using System.Web; -using OpenTelemetry.Trace; - -namespace OpenTelemetry.Instrumentation.AspNet.Implementation -{ - internal sealed class HttpInMetricsListener : IDisposable - { - private readonly Histogram httpServerDuration; - - public HttpInMetricsListener(Meter meter) - { - this.httpServerDuration = meter.CreateHistogram("http.server.duration", "ms", "measures the duration of the inbound HTTP request"); - TelemetryHttpModule.Options.OnRequestStoppedCallback += this.OnStopActivity; - } - - public void Dispose() - { - TelemetryHttpModule.Options.OnRequestStoppedCallback -= this.OnStopActivity; - } - - private void OnStopActivity(Activity activity, HttpContext context) - { - // TODO: This is just a minimal set of attributes. See the spec for additional attributes: - // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server - var tags = new TagList - { - { SemanticConventions.AttributeHttpMethod, context.Request.HttpMethod }, - { SemanticConventions.AttributeHttpScheme, context.Request.Url.Scheme }, - { SemanticConventions.AttributeHttpStatusCode, context.Response.StatusCode }, - }; - - this.httpServerDuration.Record(activity.Duration.TotalMilliseconds, tags); - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNet/MeterProviderBuilderExtensions.cs deleted file mode 100644 index e92740546..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/MeterProviderBuilderExtensions.cs +++ /dev/null @@ -1,42 +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 OpenTelemetry.Instrumentation.AspNet; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Metrics -{ - /// - /// Extension methods to simplify registering of ASP.NET request instrumentation. - /// - public static class MeterProviderBuilderExtensions - { - /// - /// Enables the incoming requests automatic data collection for ASP.NET. - /// - /// being configured. - /// The instance of to chain the calls. - public static MeterProviderBuilder AddAspNetInstrumentation( - this MeterProviderBuilder builder) - { - Guard.ThrowIfNull(builder); - - var instrumentation = new AspNetMetrics(); - builder.AddMeter(AspNetMetrics.InstrumentationName); - return builder.AddInstrumentation(() => instrumentation); - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj b/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj deleted file mode 100644 index 4e80d0a18..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - net462 - ASP.NET instrumentation for OpenTelemetry .NET - $(PackageTags);distributed-tracing;AspNet;MVC;WebAPI - true - - - - - - - - - - - - - - - - - diff --git a/src/OpenTelemetry.Instrumentation.AspNet/README.md b/src/OpenTelemetry.Instrumentation.AspNet/README.md deleted file mode 100644 index 3a2ed2c1b..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/README.md +++ /dev/null @@ -1,167 +0,0 @@ -# ASP.NET Instrumentation for OpenTelemetry - -[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Instrumentation.AspNet.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.AspNet) -[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Instrumentation.AspNet.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.AspNet) - -This is an [Instrumentation -Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library), -which instruments [ASP.NET](https://docs.microsoft.com/aspnet/overview) and -collect metrics and traces about incoming web requests. - -**Note: This component is based on the OpenTelemetry semantic conventions for -[metrics](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/metrics/semantic_conventions) -and -[traces](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions). -These conventions are -[Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/document-status.md), -and hence, this package is a [pre-release](../../VERSIONING.md#pre-releases). -Until a [stable -version](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/telemetry-stability.md) -is released, there can be breaking changes. You can track the progress from -[milestones](https://github.com/open-telemetry/opentelemetry-dotnet/milestone/23).** - -## Steps to enable OpenTelemetry.Instrumentation.AspNet - -### Step 1: Install Package - -Add a reference to the -[`OpenTelemetry.Instrumentation.AspNet`](https://www.nuget.org/packages/opentelemetry.instrumentation.aspnet) -package. Also, add any other instrumentations & exporters you will need. - -```shell -dotnet add package OpenTelemetry.Instrumentation.AspNet -``` - -### Step 2: Modify Web.config - -`OpenTelemetry.Instrumentation.AspNet` requires adding an additional HttpModule -to your web server. This additional HttpModule is shipped as part of -[`OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule`](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/) -which is implicitly brought by `OpenTelemetry.Instrumentation.AspNet`. The -following shows changes required to your `Web.config` when using IIS web server. - -```xml - - - - - -``` - -### Step 3: Enable ASP.NET Instrumentation at application startup - -ASP.NET instrumentation must be enabled at application startup. This is -typically done in the `Global.asax.cs` as shown below. This example also sets up -the OpenTelemetry Jaeger exporter, which requires adding the package -[`OpenTelemetry.Exporter.Jaeger`](../OpenTelemetry.Exporter.Jaeger/README.md) to -the application. - -```csharp -using OpenTelemetry; -using OpenTelemetry.Trace; - -public class WebApiApplication : HttpApplication -{ - private TracerProvider tracerProvider; - protected void Application_Start() - { - this.tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddAspNetInstrumentation() - .AddJaegerExporter() - .Build(); - } - protected void Application_End() - { - this.tracerProvider?.Dispose(); - } -} -``` - -## Advanced configuration - -This instrumentation can be configured to change the default behavior by using -`AspNetInstrumentationOptions`, which allows configuring `Filter` as explained -below. - -### Filter - -This instrumentation by default collects all the incoming http requests. It -allows filtering of requests by using the `Filter` function in -`AspNetInstrumentationOptions`. This defines the condition for allowable -requests. The Filter receives the `HttpContext` of the incoming request, and -does not collect telemetry about the request if the Filter returns false or -throws exception. - -The following code snippet shows how to use `Filter` to only allow GET requests. - -```csharp -this.tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddAspNetInstrumentation( - (options) => options.Filter = - (httpContext) => - { - // only collect telemetry about HTTP GET requests - return httpContext.Request.HttpMethod.Equals("GET"); - }) - .Build(); -``` - -It is important to note that this `Filter` option is specific to this -instrumentation. OpenTelemetry has a concept of a -[Sampler](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling), -and the `Filter` option does the filtering *before* the Sampler is invoked. - -### Enrich - -This option allows one to enrich the activity with additional information from -the raw `HttpRequest`, `HttpResponse` objects. The `Enrich` action is called -only when `activity.IsAllDataRequested` is `true`. It contains the activity -itself (which can be enriched), the name of the event, and the actual raw -object. For event name "OnStartActivity", the actual object will be -`HttpRequest`. For event name "OnStopActivity", the actual object will be -`HttpResponse` - -The following code snippet shows how to add additional tags using `Enrich`. - -```csharp -this.tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddAspNetInstrumentation((options) => options.Enrich - = (activity, eventName, rawObject) => - { - if (eventName.Equals("OnStartActivity")) - { - if (rawObject is HttpRequest httpRequest) - { - activity.SetTag("physicalPath", httpRequest.PhysicalPath); - } - } - else if (eventName.Equals("OnStopActivity")) - { - if (rawObject is HttpResponse httpResponse) - { - activity.SetTag("responseType", httpResponse.ContentType); - } - } - }) - .Build(); -``` - -[Processor](../../docs/trace/extending-the-sdk/README.md#processor), is the -general extensibility point to add additional properties to any activity. The -`Enrich` option is specific to this instrumentation, and is provided to get -access to `HttpRequest` and `HttpResponse`. - -### RecordException - -This instrumentation automatically sets Activity Status to Error if an unhandled -exception is thrown. Additionally, `RecordException` feature may be turned on, -to store the exception to the Activity itself as ActivityEvent. - -## References - -* [ASP.NET](https://dotnet.microsoft.com/apps/aspnet) -* [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Instrumentation.AspNet/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNet/TracerProviderBuilderExtensions.cs deleted file mode 100644 index b58aeb83b..000000000 --- a/src/OpenTelemetry.Instrumentation.AspNet/TracerProviderBuilderExtensions.cs +++ /dev/null @@ -1,49 +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; -using OpenTelemetry.Instrumentation.AspNet; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Trace -{ - /// - /// Extension methods to simplify registering of ASP.NET request instrumentation. - /// - public static class TracerProviderBuilderExtensions - { - /// - /// Enables the incoming requests automatic data collection for ASP.NET. - /// - /// being configured. - /// ASP.NET Request configuration options. - /// The instance of to chain the calls. - public static TracerProviderBuilder AddAspNetInstrumentation( - this TracerProviderBuilder builder, - Action configureAspNetInstrumentationOptions = null) - { - Guard.ThrowIfNull(builder); - - var aspnetOptions = new AspNetInstrumentationOptions(); - configureAspNetInstrumentationOptions?.Invoke(aspnetOptions); - - builder.AddInstrumentation(() => new AspNetInstrumentation(aspnetOptions)); - builder.AddSource(TelemetryHttpModule.AspNetSourceName); - - return builder; - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md index 66e2bf0f8..7dd26e20f 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md @@ -2,13 +2,26 @@ ## Unreleased +## 1.0.0-rc9.5 + +Released 2022-Aug-02 + +* Fix Remote IP Address - NULL reference exception. + ([#3481](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3481)) +* Metrics instrumentation to correctly populate `http.flavor` tag. + (1.1 instead of HTTP/1.1 etc.) + ([#3379](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3379)) +* Tracing instrumentation to populate `http.flavor` tag. + ([#3372](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3372)) +* Tracing instrumentation to populate `http.scheme` tag. + ([#3392](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3392)) + ## 1.0.0-rc9.4 Released 2022-Jun-03 * Added additional metric dimensions. - ([3247](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3247)) - + ([#3247](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3247)) * Removes net5.0 target as .NET 5.0 is going out of support. The package keeps netstandard2.1 target, so it can still be used with .NET5.0 apps. @@ -144,7 +157,7 @@ Released 2020-Sep-15 added by the library are removed from the span. The information from these attributes is contained in other attributes that follow the conventions of OpenTelemetry. - ([#1260](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1260)). + ([#1260](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1260)) ## 0.5.0-beta.2 diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs index 8e80800d1..a37863ad8 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs @@ -153,8 +153,10 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation } 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)); var userAgent = request.Headers["User-Agent"].FirstOrDefault(); if (!string.IsNullOrEmpty(userAgent)) @@ -358,7 +360,11 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation activity.DisplayName = grpcMethod.TrimStart('/'); activity.SetTag(SemanticConventions.AttributeRpcSystem, GrpcTagHelper.RpcSystemGrpc); - activity.SetTag(SemanticConventions.AttributeNetPeerIp, context.Connection.RemoteIpAddress.ToString()); + if (context.Connection.RemoteIpAddress != null) + { + activity.SetTag(SemanticConventions.AttributeNetPeerIp, context.Connection.RemoteIpAddress.ToString()); + } + activity.SetTag(SemanticConventions.AttributeNetPeerPort, context.Connection.RemotePort); bool validConversion = GrpcTagHelper.TryGetGrpcStatusCodeFromActivity(activity, out int status); diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs index 2becf9cf1..6d9947705 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs @@ -78,7 +78,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation { tags = new TagList { - { SemanticConventions.AttributeHttpFlavor, context.Request.Protocol }, + { SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol) }, { SemanticConventions.AttributeHttpScheme, context.Request.Scheme }, { SemanticConventions.AttributeHttpMethod, context.Request.Method }, { SemanticConventions.AttributeHttpHost, host }, @@ -90,7 +90,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation { tags = new TagList { - { SemanticConventions.AttributeHttpFlavor, context.Request.Protocol }, + { SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol) }, { SemanticConventions.AttributeHttpScheme, context.Request.Scheme }, { SemanticConventions.AttributeHttpMethod, context.Request.Method }, { SemanticConventions.AttributeHttpHost, host }, diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpTagHelper.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpTagHelper.cs new file mode 100644 index 000000000..73114efe0 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpTagHelper.cs @@ -0,0 +1,47 @@ +// +// 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. +// + +namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation +{ + /// + /// A collection of helper methods to be used when building Http activities. + /// + internal static class HttpTagHelper + { + /// + /// Gets the OpenTelemetry standard version tag value for a span based on its protocol/>. + /// + /// . + /// Span flavor value. + public static string GetFlavorTagValueFromProtocol(string protocol) + { + switch (protocol) + { + case "HTTP/2": + return "2.0"; + + case "HTTP/3": + return "3.0"; + + case "HTTP/1.1": + return "1.1"; + + default: + return protocol; + } + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.GrpcNetClient/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.GrpcNetClient/CHANGELOG.md index 8b25cf20d..843828f26 100644 --- a/src/OpenTelemetry.Instrumentation.GrpcNetClient/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.GrpcNetClient/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.0.0-rc9.5 + +Released 2022-Aug-02 + ## 1.0.0-rc9.4 Released 2022-Jun-03 @@ -98,7 +102,7 @@ Released 2020-Sep-15 * The `grpc.method` and `grpc.status_code` attributes added by the library are removed from the span. The information from these attributes is contained in other attributes that follow the conventions of OpenTelemetry. - ([#1260](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1260)). + ([#1260](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1260)) ## 0.5.0-beta.2 diff --git a/src/OpenTelemetry.Instrumentation.Http/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.Http/.publicApi/net462/PublicAPI.Unshipped.txt index b4630d8b7..f303cf77a 100644 --- a/src/OpenTelemetry.Instrumentation.Http/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Instrumentation.Http/.publicApi/net462/PublicAPI.Unshipped.txt @@ -6,8 +6,6 @@ OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.Filter.set - OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.HttpClientInstrumentationOptions() -> void OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.RecordException.get -> bool OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.RecordException.set -> void -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.SetHttpFlavor.get -> bool -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.SetHttpFlavor.set -> void OpenTelemetry.Instrumentation.Http.HttpWebRequestInstrumentationOptions OpenTelemetry.Instrumentation.Http.HttpWebRequestInstrumentationOptions.Enrich.get -> System.Action OpenTelemetry.Instrumentation.Http.HttpWebRequestInstrumentationOptions.Enrich.set -> void @@ -16,8 +14,6 @@ OpenTelemetry.Instrumentation.Http.HttpWebRequestInstrumentationOptions.Filter.s OpenTelemetry.Instrumentation.Http.HttpWebRequestInstrumentationOptions.HttpWebRequestInstrumentationOptions() -> void OpenTelemetry.Instrumentation.Http.HttpWebRequestInstrumentationOptions.RecordException.get -> bool OpenTelemetry.Instrumentation.Http.HttpWebRequestInstrumentationOptions.RecordException.set -> void -OpenTelemetry.Instrumentation.Http.HttpWebRequestInstrumentationOptions.SetHttpFlavor.get -> bool -OpenTelemetry.Instrumentation.Http.HttpWebRequestInstrumentationOptions.SetHttpFlavor.set -> void OpenTelemetry.Metrics.MeterProviderBuilderExtensions OpenTelemetry.Trace.TracerProviderBuilderExtensions static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Instrumentation.Http/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.Http/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 04b9e3c9f..8ac18d7a6 100644 --- a/src/OpenTelemetry.Instrumentation.Http/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Instrumentation.Http/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -6,8 +6,6 @@ OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.Filter.set - OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.HttpClientInstrumentationOptions() -> void OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.RecordException.get -> bool OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.RecordException.set -> void -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.SetHttpFlavor.get -> bool -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.SetHttpFlavor.set -> void OpenTelemetry.Metrics.MeterProviderBuilderExtensions OpenTelemetry.Trace.TracerProviderBuilderExtensions static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md index bcdbd2972..20fb945c2 100644 --- a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md @@ -2,6 +2,22 @@ ## Unreleased +## 1.0.0-rc9.5 + +Released 2022-Aug-02 + +* Added `http.scheme` tag to tracing instrumentation. + ([#3464](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3464)) + +* [Breaking] Removes `SetHttpFlavor` option. "http.flavor" is + now always automatically populated. + To remove this tag, set "http.flavor" to null using `ActivityProcessor`. + ([#3380](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3380)) + +* Fix `Enrich` not getting invoked when SocketException due to HostNotFound + occurs. + ([#3407](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3407)) + ## 1.0.0-rc9.4 Released 2022-Jun-03 diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs index d7552aafe..6206cfee3 100644 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs @@ -19,7 +19,6 @@ using System.Diagnostics; using System.Net.Http; using System.Runtime.CompilerServices; using OpenTelemetry.Instrumentation.Http.Implementation; -using OpenTelemetry.Trace; namespace OpenTelemetry.Instrumentation.Http { @@ -28,11 +27,6 @@ namespace OpenTelemetry.Instrumentation.Http /// public class HttpClientInstrumentationOptions { - /// - /// Gets or sets a value indicating whether or not the HTTP version should be added as the tag. Default value: False. - /// - public bool SetHttpFlavor { get; set; } - /// /// Gets or sets a Filter function that determines whether or not to collect telemetry about requests on a per request basis. /// The Filter gets the HttpRequestMessage, and should return a boolean. diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpWebRequestInstrumentationOptions.netfx.cs b/src/OpenTelemetry.Instrumentation.Http/HttpWebRequestInstrumentationOptions.netfx.cs index 70bf22906..b695aebf3 100644 --- a/src/OpenTelemetry.Instrumentation.Http/HttpWebRequestInstrumentationOptions.netfx.cs +++ b/src/OpenTelemetry.Instrumentation.Http/HttpWebRequestInstrumentationOptions.netfx.cs @@ -19,7 +19,6 @@ using System; using System.Diagnostics; using System.Net; using OpenTelemetry.Instrumentation.Http.Implementation; -using OpenTelemetry.Trace; namespace OpenTelemetry.Instrumentation.Http { @@ -28,11 +27,6 @@ namespace OpenTelemetry.Instrumentation.Http /// public class HttpWebRequestInstrumentationOptions { - /// - /// Gets or sets a value indicating whether or not the HTTP version should be added as the tag. Default value: False. - /// - public bool SetHttpFlavor { get; set; } - /// /// Gets or sets a Filter function that determines whether or not to collect telemetry about requests on a per request basis. /// The Filter gets the HttpWebRequest, and should return a boolean. diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs index c07cf4ecd..549537080 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs @@ -17,7 +17,6 @@ using System; using System.Diagnostics; using System.Net.Http; -using System.Net.Sockets; using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -112,13 +111,11 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation ActivityInstrumentationHelper.SetActivitySourceProperty(activity, ActivitySource); ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Client); + activity.SetTag(SemanticConventions.AttributeHttpScheme, request.RequestUri.Scheme); activity.SetTag(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method)); activity.SetTag(SemanticConventions.AttributeHttpHost, HttpTagHelper.GetHostTagValueFromRequestUri(request.RequestUri)); activity.SetTag(SemanticConventions.AttributeHttpUrl, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri)); - if (this.options.SetHttpFlavor) - { - activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version)); - } + activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version)); try { @@ -197,20 +194,7 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation if (exc is HttpRequestException) { - if (exc.InnerException is SocketException exception) - { - switch (exception.SocketErrorCode) - { - case SocketError.HostNotFound: - activity.SetStatus(Status.Error.WithDescription(exc.Message)); - return; - } - } - - if (exc.InnerException != null) - { - activity.SetStatus(Status.Error.WithDescription(exc.Message)); - } + activity.SetStatus(Status.Error.WithDescription(exc.Message)); } try diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs index 17427bf10..f9c9e303b 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs @@ -99,10 +99,7 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation activity.SetTag(SemanticConventions.AttributeHttpMethod, request.Method); activity.SetTag(SemanticConventions.AttributeHttpHost, HttpTagHelper.GetHostTagValueFromRequestUri(request.RequestUri)); activity.SetTag(SemanticConventions.AttributeHttpUrl, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri)); - if (Options.SetHttpFlavor) - { - activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.ProtocolVersion)); - } + activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.ProtocolVersion)); try { diff --git a/src/OpenTelemetry.Instrumentation.Http/README.md b/src/OpenTelemetry.Instrumentation.Http/README.md index 8cdad77f0..b3d07df56 100644 --- a/src/OpenTelemetry.Instrumentation.Http/README.md +++ b/src/OpenTelemetry.Instrumentation.Http/README.md @@ -3,8 +3,8 @@ [![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Instrumentation.Http.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.Http) [![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Instrumentation.Http.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.Http) -This is an -[Instrumentation Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library), +This is an [Instrumentation +Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library), which instruments [System.Net.Http.HttpClient](https://docs.microsoft.com/dotnet/api/system.net.http.httpclient) and @@ -27,7 +27,8 @@ is released, there can be breaking changes. You can track the progress from ### Step 1: Install Package -Add a reference to the [`OpenTelemetry.Instrumentation.Http`](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.Http) +Add a reference to the +[`OpenTelemetry.Instrumentation.Http`](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.Http) package. Also, add any other instrumentations & exporters you will need. ```shell @@ -38,9 +39,9 @@ dotnet add package OpenTelemetry.Instrumentation.Http HTTP instrumentation must be enabled at application startup. -The following example demonstrates adding HTTP instrumentation to a -console application. This example also sets up the OpenTelemetry Console -exporter, which requires adding the package +The following example demonstrates adding HTTP instrumentation to a console +application. This example also sets up the OpenTelemetry Console exporter, which +requires adding the package [`OpenTelemetry.Exporter.Console`](../OpenTelemetry.Exporter.Console/README.md) to the application. @@ -59,12 +60,13 @@ public class Program } ``` -For an ASP.NET Core application, adding instrumentation is typically done in -the `ConfigureServices` of your `Startup` class. Refer to documentation for +For an ASP.NET Core application, adding instrumentation is typically done in the +`ConfigureServices` of your `Startup` class. Refer to documentation for [OpenTelemetry.Instrumentation.AspNetCore](../OpenTelemetry.Instrumentation.AspNetCore/README.md). For an ASP.NET application, adding instrumentation is typically done in the -`Global.asax.cs`. Refer to documentation for [OpenTelemetry.Instrumentation.AspNet](../OpenTelemetry.Instrumentation.AspNet/README.md). +`Global.asax.cs`. Refer to the documentation for +[OpenTelemetry.Instrumentation.AspNet](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/src/OpenTelemetry.Instrumentation.AspNet/README.md). ## Advanced configuration @@ -77,23 +79,6 @@ applications, it underneath uses `HttpWebRequest`. Because of this, Framework applications, irrespective of whether `HttpWebRequest` or `HttpClient` is used. -### SetHttpFlavor - -By default, this instrumentation does not add the `http.flavor` attribute. The -`http.flavor` attribute specifies the kind of HTTP protocol used -(e.g., `1.1` for HTTP 1.1). The `SetHttpFlavor` option can be used to include -the `http.flavor` attribute. - -The following example shows how to use `SetHttpFlavor`. - -```csharp -using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddHttpClientInstrumentation( - (options) => options.SetHttpFlavor = true) - .AddConsoleExporter() - .Build(); -``` - ### Filter This instrumentation by default collects all the outgoing HTTP requests. It @@ -104,8 +89,7 @@ the condition for allowable requests. The Filter receives the request object - representing the outgoing request and does not collect telemetry about the request if the Filter returns false or throws exception. -The following code snippet shows how to use `Filter` to only allow GET -requests. +The following code snippet shows how to use `Filter` to only allow GET requests. ```csharp using var tracerProvider = Sdk.CreateTracerProviderBuilder() @@ -142,8 +126,7 @@ For event name "OnStartActivity", the actual object will be For event name "OnStopActivity", the actual object will be `HttpResponseMessage`. -For event name "OnException", the actual object will be -`Exception`. +For event name "OnException", the actual object will be `Exception`. Example: @@ -180,14 +163,11 @@ var tracerProvider = Sdk.CreateTracerProviderBuilder() #### HttpWebRequestInstrumentationOptions -For event name "OnStartActivity", the actual object will be -`HttpWebRequest`. +For event name "OnStartActivity", the actual object will be `HttpWebRequest`. -For event name "OnStopActivity", the actual object will be -`HttpWebResponse`. +For event name "OnStopActivity", the actual object will be `HttpWebResponse`. -For event name "OnException", the actual object will be -`Exception`. +For event name "OnException", the actual object will be `Exception`. Example: @@ -222,10 +202,10 @@ var tracerProvider = Sdk.CreateTracerProviderBuilder() }).Build(); ``` -[Processor](../../docs/trace/extending-the-sdk/README.md#processor), -is the general extensibility point to add additional properties to any -activity. The `Enrich` option is specific to this instrumentation, and is -provided to get access to raw request, response, and exception objects. +[Processor](../../docs/trace/extending-the-sdk/README.md#processor), is the +general extensibility point to add additional properties to any activity. The +`Enrich` option is specific to this instrumentation, and is provided to get +access to raw request, response, and exception objects. ### RecordException diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md index c74d54c09..3f0e444a3 100644 --- a/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 1.0.0-rc9.5 + +Released 2022-Aug-02 + +* Update the `ActivitySource.Name` from "OpenTelemetry.SqlClient" to + "OpenTelemetry.Instrumentation.SqlClient". + ([#3435](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3435)) + ## 1.0.0-rc9.4 Released 2022-Jun-03 diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlActivitySourceHelper.cs b/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlActivitySourceHelper.cs index ce7b1ac89..95c68fda9 100644 --- a/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlActivitySourceHelper.cs +++ b/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlActivitySourceHelper.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Reflection; using OpenTelemetry.Trace; namespace OpenTelemetry.Instrumentation.SqlClient.Implementation @@ -26,19 +27,17 @@ namespace OpenTelemetry.Instrumentation.SqlClient.Implementation /// internal class SqlActivitySourceHelper { - public const string ActivitySourceName = "OpenTelemetry.SqlClient"; - public const string ActivityName = ActivitySourceName + ".Execute"; - public const string MicrosoftSqlServerDatabaseSystemName = "mssql"; + public static readonly AssemblyName AssemblyName = typeof(SqlActivitySourceHelper).Assembly.GetName(); + public static readonly string ActivitySourceName = AssemblyName.Name; + public static readonly Version Version = AssemblyName.Version; + public static readonly ActivitySource ActivitySource = new(ActivitySourceName, Version.ToString()); + public static readonly string ActivityName = ActivitySourceName + ".Execute"; + public static readonly IEnumerable> CreationTags = new[] { new KeyValuePair(SemanticConventions.AttributeDbSystem, MicrosoftSqlServerDatabaseSystemName), }; - - private static readonly Version Version = typeof(SqlActivitySourceHelper).Assembly.GetName().Version; -#pragma warning disable SA1202 // Elements should be ordered by access <- In this case, Version MUST come before ActivitySource otherwise null ref exception is thrown. - internal static readonly ActivitySource ActivitySource = new(ActivitySourceName, Version.ToString()); -#pragma warning restore SA1202 // Elements should be ordered by access } } diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/README.md b/src/OpenTelemetry.Instrumentation.SqlClient/README.md index d305be1b0..82005adc7 100644 --- a/src/OpenTelemetry.Instrumentation.SqlClient/README.md +++ b/src/OpenTelemetry.Instrumentation.SqlClient/README.md @@ -3,8 +3,8 @@ [![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Instrumentation.SqlClient.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.SqlClient) [![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Instrumentation.SqlClient.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.SqlClient) -This is an -[Instrumentation Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library), +This is an [Instrumentation +Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library), which instruments [Microsoft.Data.SqlClient](https://www.nuget.org/packages/Microsoft.Data.SqlClient) and @@ -37,9 +37,9 @@ dotnet add package OpenTelemetry.Instrumentation.SqlClient SqlClient instrumentation must be enabled at application startup. -The following example demonstrates adding SqlClient instrumentation to a -console application. This example also sets up the OpenTelemetry Console -exporter, which requires adding the package +The following example demonstrates adding SqlClient instrumentation to a console +application. This example also sets up the OpenTelemetry Console exporter, which +requires adding the package [`OpenTelemetry.Exporter.Console`](../OpenTelemetry.Exporter.Console/README.md) to the application. @@ -58,12 +58,13 @@ public class Program } ``` -For an ASP.NET Core application, adding instrumentation is typically done in -the `ConfigureServices` of your `Startup` class. Refer to documentation for +For an ASP.NET Core application, adding instrumentation is typically done in the +`ConfigureServices` of your `Startup` class. Refer to documentation for [OpenTelemetry.Instrumentation.AspNetCore](../OpenTelemetry.Instrumentation.AspNetCore/README.md). For an ASP.NET application, adding instrumentation is typically done in the -`Global.asax.cs`. Refer to documentation for [OpenTelemetry.Instrumentation.AspNet](../OpenTelemetry.Instrumentation.AspNet/README.md). +`Global.asax.cs`. Refer to the documentation for +[OpenTelemetry.Instrumentation.AspNet](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/src/OpenTelemetry.Instrumentation.AspNet/README.md). ## Advanced configuration @@ -72,8 +73,9 @@ This instrumentation can be configured to change the default behavior by using ### Capturing 'db.statement' -The `SqlClientInstrumentationOptions` class exposes several properties that can be -used to configure how the [`db.statement`](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md#call-level-attributes) +The `SqlClientInstrumentationOptions` class exposes several properties that can +be used to configure how the +[`db.statement`](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md#call-level-attributes) attribute is captured upon execution of a query. #### .NET Core - SetDbStatementForStoredProcedure and SetDbStatementForText @@ -88,7 +90,8 @@ attribute to the stored procedure command name. `SetDbStatementForText` is _false_ by default (to prevent accidental capture of sensitive data that might be part of the SQL statement text). When set to -`true`, the instrumentation will set [`db.statement`](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md#call-level-attributes) +`true`, the instrumentation will set +[`db.statement`](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md#call-level-attributes) attribute to the text of the SQL command being executed. To disable capturing stored procedure commands use configuration like below. @@ -118,8 +121,8 @@ For .NET Framework, `SetDbStatementForStoredProcedure` and `SetDbStatementForText` are not available. Instead, a single `SetDbStatement` property should be used to control whether this instrumentation should set the [`db.statement`](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md#call-level-attributes) -attribute to the text of the `SqlCommand` being executed. This could either be -a name of a stored procedure or a full text of a `CommandType.Text` query. +attribute to the text of the `SqlCommand` being executed. This could either be a +name of a stored procedure or a full text of a `CommandType.Text` query. On .NET Framework, unlike .NET Core, the instrumentation capabilities for both [`Microsoft.Data.SqlClient`](https://www.nuget.org/packages/Microsoft.Data.SqlClient/) @@ -133,8 +136,8 @@ and `System.Data.SqlClient` are limited: query text. Since `CommandType.Text` might contain sensitive data, all SQL capturing is -_disabled_ by default to protect against accidentally sending full query text -to a telemetry backend. If you are only using stored procedures or have no +_disabled_ by default to protect against accidentally sending full query text to +a telemetry backend. If you are only using stored procedures or have no sensitive data in your `sqlCommand.CommandText`, you can enable SQL capturing using the options like below: @@ -169,11 +172,11 @@ using var tracerProvider = Sdk.CreateTracerProviderBuilder() ## Enrich -This option, available on .NET Core only, allows one to enrich the activity -with additional information from the raw `SqlCommand` object. The `Enrich` -action is called only when `activity.IsAllDataRequested` is `true`. It contains -the activity itself (which can be enriched), the name of the event, and the -actual raw object. +This option, available on .NET Core only, allows one to enrich the activity with +additional information from the raw `SqlCommand` object. The `Enrich` action is +called only when `activity.IsAllDataRequested` is `true`. It contains the +activity itself (which can be enriched), the name of the event, and the actual +raw object. Currently there is only one event name reported, "OnCustom". The actual object is `Microsoft.Data.SqlClient.SqlCommand` for `Microsoft.Data.SqlClient` and @@ -197,15 +200,16 @@ using var tracerProvider = Sdk.CreateTracerProviderBuilder() .Build(); ``` -[Processor](../../docs/trace/extending-the-sdk/README.md#processor), -is the general extensibility point to add additional properties to any activity. -The `Enrich` option is specific to this instrumentation, and is provided to -get access to `SqlCommand` object. +[Processor](../../docs/trace/extending-the-sdk/README.md#processor), is the +general extensibility point to add additional properties to any activity. The +`Enrich` option is specific to this instrumentation, and is provided to get +access to `SqlCommand` object. ### RecordException -This option, available on .NET Core only, can be set to instruct the instrumentation -to record SqlExceptions as Activity [events](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md). +This option, available on .NET Core only, can be set to instruct the +instrumentation to record SqlExceptions as Activity +[events](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md). The default value is `false` and can be changed by the code like below. @@ -221,4 +225,5 @@ using var tracerProvider = Sdk.CreateTracerProviderBuilder() * [OpenTelemetry Project](https://opentelemetry.io/) -* [OpenTelemetry semantic conventions for database calls](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md) +* [OpenTelemetry semantic conventions for database + calls](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md) diff --git a/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md b/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md index 30a4b6558..4affe1b02 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md +++ b/src/OpenTelemetry.Shims.OpenTracing/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 1.0.0-rc9.5 + +Released 2022-Aug-02 + +* Fix: Handling of OpenTracing spans when used in conjunction + with legacy "Microsoft.AspNetCore.Hosting.HttpRequestIn" activities. + ([#3509](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3506)) + ## 1.0.0-rc9.4 Released 2022-Jun-03 diff --git a/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs b/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs index 483549e8f..5cdbe55e8 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/SpanBuilderShim.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using OpenTelemetry.Internal; using OpenTelemetry.Trace; using OpenTracing; @@ -50,14 +49,6 @@ namespace OpenTelemetry.Shims.OpenTracing /// private readonly List> attributes = new(); - /// - /// The set of operation names for System.Diagnostics.Activity based automatic instrumentations that indicate a root span. - /// - private readonly IList rootOperationNamesForActivityBasedAutoInstrumentations = new List - { - "Microsoft.AspNetCore.Hosting.HttpRequestIn", - }; - /// /// The parent as an TelemetrySpan, if any. /// @@ -79,7 +70,7 @@ namespace OpenTelemetry.Shims.OpenTracing private bool error; - public SpanBuilderShim(Tracer tracer, string spanName, IList rootOperationNamesForActivityBasedAutoInstrumentations = null) + public SpanBuilderShim(Tracer tracer, string spanName) { Guard.ThrowIfNull(tracer); Guard.ThrowIfNull(spanName); @@ -87,7 +78,6 @@ namespace OpenTelemetry.Shims.OpenTracing this.tracer = tracer; this.spanName = spanName; this.ScopeManager = new ScopeManagerShim(this.tracer); - this.rootOperationNamesForActivityBasedAutoInstrumentations = rootOperationNamesForActivityBasedAutoInstrumentations ?? this.rootOperationNamesForActivityBasedAutoInstrumentations; } private IScopeManager ScopeManager { get; } @@ -172,13 +162,6 @@ namespace OpenTelemetry.Shims.OpenTracing { span = this.tracer.StartSpan(this.spanName, this.spanKind, this.parentSpanContext, default, this.links, this.explicitStartTime ?? default); } - else if (this.parentSpan == null && !this.parentSpanContext.IsValid && Activity.Current != null && Activity.Current.IdFormat == ActivityIdFormat.W3C) - { - if (this.rootOperationNamesForActivityBasedAutoInstrumentations.Contains(Activity.Current.OperationName)) - { - span = Tracer.CurrentSpan; - } - } if (span == null) { diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt index 56774a7fc..afa4f5a68 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt @@ -1,21 +1,21 @@ #nullable enable -~abstract OpenTelemetry.BaseExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult -~abstract OpenTelemetry.BaseExportProcessor.OnExport(T data) -> void -~OpenTelemetry.BaseExporter -~OpenTelemetry.BaseExporter.ParentProvider.get -> OpenTelemetry.BaseProvider -~OpenTelemetry.BaseExportProcessor -~OpenTelemetry.BaseExportProcessor.BaseExportProcessor(OpenTelemetry.BaseExporter exporter) -> void -~OpenTelemetry.BaseProcessor.ParentProvider.get -> OpenTelemetry.BaseProvider -~OpenTelemetry.Batch -~OpenTelemetry.Batch.Batch(T[] items, int count) -> void -~OpenTelemetry.Batch.Enumerator.Current.get -> T +abstract OpenTelemetry.BaseExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult +abstract OpenTelemetry.BaseExportProcessor.OnExport(T! data) -> void +OpenTelemetry.BaseExporter +OpenTelemetry.BaseExporter.ParentProvider.get -> OpenTelemetry.BaseProvider? +OpenTelemetry.BaseExportProcessor +OpenTelemetry.BaseExportProcessor.BaseExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void +OpenTelemetry.BaseProcessor.ParentProvider.get -> OpenTelemetry.BaseProvider? +OpenTelemetry.Batch +OpenTelemetry.Batch.Batch(T![]! items, int count) -> void +OpenTelemetry.Batch.Enumerator.Current.get -> T! ~OpenTelemetry.Batch.GetEnumerator() -> OpenTelemetry.Batch.Enumerator ~OpenTelemetry.BatchActivityExportProcessor.BatchActivityExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void -~OpenTelemetry.BatchExportProcessor -~OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +OpenTelemetry.BatchExportProcessor +OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void ~OpenTelemetry.BatchExportProcessorOptions -~OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.CompositeProcessor -~OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable> processors) -> void +OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.CompositeProcessor! +OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable!>! processors) -> void ~OpenTelemetry.Metrics.BaseExportingMetricReader.BaseExportingMetricReader(OpenTelemetry.BaseExporter exporter) -> void ~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.get -> double[] ~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.set -> void @@ -46,8 +46,8 @@ ~OpenTelemetry.Resources.ResourceBuilder.Build() -> OpenTelemetry.Resources.Resource ~OpenTelemetry.Resources.ResourceBuilder.Clear() -> OpenTelemetry.Resources.ResourceBuilder ~OpenTelemetry.SimpleActivityExportProcessor.SimpleActivityExportProcessor(OpenTelemetry.BaseExporter exporter) -> void -~OpenTelemetry.SimpleExportProcessor -~OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter exporter) -> void +OpenTelemetry.SimpleExportProcessor +OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void ~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler) -> void ~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler, OpenTelemetry.Trace.Sampler remoteParentSampled = null, OpenTelemetry.Trace.Sampler remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler localParentSampled = null, OpenTelemetry.Trace.Sampler localParentNotSampled = null) -> void ~OpenTelemetry.Trace.Sampler.Description.get -> string @@ -60,19 +60,19 @@ ~OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision, System.Collections.Generic.IEnumerable> attributes) -> void ~OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(string instrumentationName, string instrumentationVersion, System.Func instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder ~OpenTelemetry.Trace.TracerProviderBuilderBase.Build() -> OpenTelemetry.Trace.TracerProvider -~override OpenTelemetry.BaseExportProcessor.OnEnd(T data) -> void +override OpenTelemetry.BaseExportProcessor.OnEnd(T! data) -> void ~override OpenTelemetry.BatchActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void -~override OpenTelemetry.BatchExportProcessor.OnExport(T data) -> void +override OpenTelemetry.BatchExportProcessor.OnExport(T! data) -> void ~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Metrics.MeterProviderBuilder ~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddMeter(params string[] names) -> OpenTelemetry.Metrics.MeterProviderBuilder ~override OpenTelemetry.SimpleActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void -~override OpenTelemetry.SimpleExportProcessor.OnExport(T data) -> void +override OpenTelemetry.SimpleExportProcessor.OnExport(T! data) -> void ~override OpenTelemetry.Trace.SamplingResult.Equals(object obj) -> bool ~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder ~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddLegacySource(string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder ~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddSource(params string[] names) -> OpenTelemetry.Trace.TracerProviderBuilder -~override sealed OpenTelemetry.BaseExportProcessor.OnStart(T data) -> void -~readonly OpenTelemetry.BaseExportProcessor.exporter -> OpenTelemetry.BaseExporter +override sealed OpenTelemetry.BaseExportProcessor.OnStart(T! data) -> void +readonly OpenTelemetry.BaseExportProcessor.exporter -> OpenTelemetry.BaseExporter! ~readonly OpenTelemetry.Metrics.BaseExportingMetricReader.exporter -> OpenTelemetry.BaseExporter ~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddReader(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, OpenTelemetry.Metrics.MetricReader reader) -> OpenTelemetry.Metrics.MeterProviderBuilder ~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, string instrumentName, OpenTelemetry.Metrics.MetricStreamConfiguration metricStreamConfiguration) -> OpenTelemetry.Metrics.MeterProviderBuilder @@ -145,7 +145,7 @@ OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Failure = 1 -> OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Success = 0 -> OpenTelemetry.ExportResult OpenTelemetry.Logs.LogRecord -OpenTelemetry.Logs.LogRecord.CategoryName.get -> string! +OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? OpenTelemetry.Logs.LogRecord.EventId.get -> Microsoft.Extensions.Logging.EventId OpenTelemetry.Logs.LogRecord.Exception.get -> System.Exception? OpenTelemetry.Logs.LogRecord.ForEachScope(System.Action! callback, TState state) -> void diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index e69de29bb..791e1c4f8 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -0,0 +1,20 @@ +OpenTelemetry.Logs.LogRecord.CategoryName.set -> void +OpenTelemetry.Logs.LogRecord.EventId.set -> void +OpenTelemetry.Logs.LogRecord.Exception.set -> void +OpenTelemetry.Logs.LogRecord.LogLevel.set -> void +OpenTelemetry.Logs.LogRecord.SpanId.set -> void +OpenTelemetry.Logs.LogRecord.Timestamp.set -> void +OpenTelemetry.Logs.LogRecord.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecord.TraceId.set -> void +OpenTelemetry.Logs.LogRecord.TraceState.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider() -> void +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ConfigureResource(System.Action! configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +*REMOVED*static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, bool disposeProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Shipped.txt new file mode 100644 index 000000000..afa4f5a68 --- /dev/null +++ b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Shipped.txt @@ -0,0 +1,359 @@ +#nullable enable +abstract OpenTelemetry.BaseExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult +abstract OpenTelemetry.BaseExportProcessor.OnExport(T! data) -> void +OpenTelemetry.BaseExporter +OpenTelemetry.BaseExporter.ParentProvider.get -> OpenTelemetry.BaseProvider? +OpenTelemetry.BaseExportProcessor +OpenTelemetry.BaseExportProcessor.BaseExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void +OpenTelemetry.BaseProcessor.ParentProvider.get -> OpenTelemetry.BaseProvider? +OpenTelemetry.Batch +OpenTelemetry.Batch.Batch(T![]! items, int count) -> void +OpenTelemetry.Batch.Enumerator.Current.get -> T! +~OpenTelemetry.Batch.GetEnumerator() -> OpenTelemetry.Batch.Enumerator +~OpenTelemetry.BatchActivityExportProcessor.BatchActivityExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +OpenTelemetry.BatchExportProcessor +OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +~OpenTelemetry.BatchExportProcessorOptions +OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.CompositeProcessor! +OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable!>! processors) -> void +~OpenTelemetry.Metrics.BaseExportingMetricReader.BaseExportingMetricReader(OpenTelemetry.BaseExporter exporter) -> void +~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.get -> double[] +~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.set -> void +~OpenTelemetry.Metrics.IPullMetricExporter.Collect.get -> System.Func +~OpenTelemetry.Metrics.IPullMetricExporter.Collect.set -> void +~OpenTelemetry.Metrics.MeterProviderBuilderBase.Build() -> OpenTelemetry.Metrics.MeterProvider +~OpenTelemetry.Metrics.Metric.Description.get -> string +~OpenTelemetry.Metrics.Metric.MeterName.get -> string +~OpenTelemetry.Metrics.Metric.MeterVersion.get -> string +~OpenTelemetry.Metrics.Metric.Name.get -> string +~OpenTelemetry.Metrics.Metric.Unit.get -> string +~OpenTelemetry.Metrics.MetricPoint.GetHistogramBuckets() -> OpenTelemetry.Metrics.HistogramBuckets +~OpenTelemetry.Metrics.MetricReaderOptions.PeriodicExportingMetricReaderOptions.get -> OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions +~OpenTelemetry.Metrics.MetricReaderOptions.PeriodicExportingMetricReaderOptions.set -> void +~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.get -> string +~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.set -> void +~OpenTelemetry.Metrics.MetricStreamConfiguration.Name.get -> string +~OpenTelemetry.Metrics.MetricStreamConfiguration.Name.set -> void +~OpenTelemetry.Metrics.MetricStreamConfiguration.TagKeys.get -> string[] +~OpenTelemetry.Metrics.MetricStreamConfiguration.TagKeys.set -> void +~OpenTelemetry.Metrics.PeriodicExportingMetricReader.PeriodicExportingMetricReader(OpenTelemetry.BaseExporter exporter, int exportIntervalMilliseconds = 60000, int exportTimeoutMilliseconds = 30000) -> void +~OpenTelemetry.ReadOnlyTagCollection.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair +~OpenTelemetry.Resources.IResourceDetector.Detect() -> OpenTelemetry.Resources.Resource +~OpenTelemetry.Resources.Resource.Attributes.get -> System.Collections.Generic.IEnumerable> +~OpenTelemetry.Resources.Resource.Merge(OpenTelemetry.Resources.Resource other) -> OpenTelemetry.Resources.Resource +~OpenTelemetry.Resources.Resource.Resource(System.Collections.Generic.IEnumerable> attributes) -> void +~OpenTelemetry.Resources.ResourceBuilder.AddDetector(OpenTelemetry.Resources.IResourceDetector resourceDetector) -> OpenTelemetry.Resources.ResourceBuilder +~OpenTelemetry.Resources.ResourceBuilder.Build() -> OpenTelemetry.Resources.Resource +~OpenTelemetry.Resources.ResourceBuilder.Clear() -> OpenTelemetry.Resources.ResourceBuilder +~OpenTelemetry.SimpleActivityExportProcessor.SimpleActivityExportProcessor(OpenTelemetry.BaseExporter exporter) -> void +OpenTelemetry.SimpleExportProcessor +OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void +~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler) -> void +~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler, OpenTelemetry.Trace.Sampler remoteParentSampled = null, OpenTelemetry.Trace.Sampler remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler localParentSampled = null, OpenTelemetry.Trace.Sampler localParentNotSampled = null) -> void +~OpenTelemetry.Trace.Sampler.Description.get -> string +~OpenTelemetry.Trace.Sampler.Description.set -> void +~OpenTelemetry.Trace.SamplingParameters.Links.get -> System.Collections.Generic.IEnumerable +~OpenTelemetry.Trace.SamplingParameters.Name.get -> string +~OpenTelemetry.Trace.SamplingParameters.SamplingParameters(System.Diagnostics.ActivityContext parentContext, System.Diagnostics.ActivityTraceId traceId, string name, System.Diagnostics.ActivityKind kind, System.Collections.Generic.IEnumerable> tags = null, System.Collections.Generic.IEnumerable links = null) -> void +~OpenTelemetry.Trace.SamplingParameters.Tags.get -> System.Collections.Generic.IEnumerable> +~OpenTelemetry.Trace.SamplingResult.Attributes.get -> System.Collections.Generic.IEnumerable> +~OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision, System.Collections.Generic.IEnumerable> attributes) -> void +~OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(string instrumentationName, string instrumentationVersion, System.Func instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder +~OpenTelemetry.Trace.TracerProviderBuilderBase.Build() -> OpenTelemetry.Trace.TracerProvider +override OpenTelemetry.BaseExportProcessor.OnEnd(T! data) -> void +~override OpenTelemetry.BatchActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void +override OpenTelemetry.BatchExportProcessor.OnExport(T! data) -> void +~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Metrics.MeterProviderBuilder +~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddMeter(params string[] names) -> OpenTelemetry.Metrics.MeterProviderBuilder +~override OpenTelemetry.SimpleActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void +override OpenTelemetry.SimpleExportProcessor.OnExport(T! data) -> void +~override OpenTelemetry.Trace.SamplingResult.Equals(object obj) -> bool +~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder +~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddLegacySource(string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder +~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddSource(params string[] names) -> OpenTelemetry.Trace.TracerProviderBuilder +override sealed OpenTelemetry.BaseExportProcessor.OnStart(T! data) -> void +readonly OpenTelemetry.BaseExportProcessor.exporter -> OpenTelemetry.BaseExporter! +~readonly OpenTelemetry.Metrics.BaseExportingMetricReader.exporter -> OpenTelemetry.BaseExporter +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddReader(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, OpenTelemetry.Metrics.MetricReader reader) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, string instrumentName, OpenTelemetry.Metrics.MetricStreamConfiguration metricStreamConfiguration) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, string instrumentName, string name) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Func viewConfig) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.Build(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder) -> OpenTelemetry.Metrics.MeterProvider +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetMaxMetricPointsPerMetricStream(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, int maxMetricPointsPerMetricStream) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetMaxMetricStreams(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, int maxMetricStreams) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetResourceBuilder(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, OpenTelemetry.Resources.ResourceBuilder resourceBuilder) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderExtensions.ForceFlush(this OpenTelemetry.Metrics.MeterProvider provider, int timeoutMilliseconds = -1) -> bool +~static OpenTelemetry.Metrics.MeterProviderExtensions.Shutdown(this OpenTelemetry.Metrics.MeterProvider provider, int timeoutMilliseconds = -1) -> bool +~static OpenTelemetry.Metrics.MetricStreamConfiguration.Drop.get -> OpenTelemetry.Metrics.MetricStreamConfiguration +~static OpenTelemetry.ProviderExtensions.GetDefaultResource(this OpenTelemetry.BaseProvider baseProvider) -> OpenTelemetry.Resources.Resource +~static OpenTelemetry.ProviderExtensions.GetResource(this OpenTelemetry.BaseProvider baseProvider) -> OpenTelemetry.Resources.Resource +~static OpenTelemetry.Resources.Resource.Empty.get -> OpenTelemetry.Resources.Resource +~static OpenTelemetry.Resources.ResourceBuilder.CreateDefault() -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Resources.ResourceBuilder.CreateEmpty() -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Resources.ResourceBuilderExtensions.AddAttributes(this OpenTelemetry.Resources.ResourceBuilder resourceBuilder, System.Collections.Generic.IEnumerable> attributes) -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Resources.ResourceBuilderExtensions.AddEnvironmentVariableDetector(this OpenTelemetry.Resources.ResourceBuilder resourceBuilder) -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Resources.ResourceBuilderExtensions.AddService(this OpenTelemetry.Resources.ResourceBuilder resourceBuilder, string serviceName, string serviceNamespace = null, string serviceVersion = null, bool autoGenerateServiceInstanceId = true, string serviceInstanceId = null) -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Resources.ResourceBuilderExtensions.AddTelemetrySdk(this OpenTelemetry.Resources.ResourceBuilder resourceBuilder) -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Sdk.CreateMeterProviderBuilder() -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Sdk.CreateTracerProviderBuilder() -> OpenTelemetry.Trace.TracerProviderBuilder +~static OpenTelemetry.Sdk.SetDefaultTextMapPropagator(OpenTelemetry.Context.Propagation.TextMapPropagator textMapPropagator) -> void +~static OpenTelemetry.SuppressInstrumentationScope.Begin(bool value = true) -> System.IDisposable +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddProcessor(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.Trace.TracerProviderBuilder +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.Build(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder) -> OpenTelemetry.Trace.TracerProvider +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.SetErrorStatusOnException(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, bool enabled = true) -> OpenTelemetry.Trace.TracerProviderBuilder +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.SetResourceBuilder(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, OpenTelemetry.Resources.ResourceBuilder resourceBuilder) -> OpenTelemetry.Trace.TracerProviderBuilder +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.SetSampler(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, OpenTelemetry.Trace.Sampler sampler) -> OpenTelemetry.Trace.TracerProviderBuilder +~static OpenTelemetry.Trace.TracerProviderExtensions.AddProcessor(this OpenTelemetry.Trace.TracerProvider provider, OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.Trace.TracerProvider +~static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool +~static OpenTelemetry.Trace.TracerProviderExtensions.Shutdown(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool +abstract OpenTelemetry.Trace.Sampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult +Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions +OpenTelemetry.BaseExporter.BaseExporter() -> void +OpenTelemetry.BaseExporter.Dispose() -> void +OpenTelemetry.BaseExporter.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.BaseExporter.Shutdown(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.BaseProcessor +OpenTelemetry.BaseProcessor.BaseProcessor() -> void +OpenTelemetry.BaseProcessor.Dispose() -> void +OpenTelemetry.BaseProcessor.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.BaseProcessor.Shutdown(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Batch.Batch() -> void +OpenTelemetry.Batch.Count.get -> long +OpenTelemetry.Batch.Dispose() -> void +OpenTelemetry.Batch.Enumerator +OpenTelemetry.Batch.Enumerator.Dispose() -> void +OpenTelemetry.Batch.Enumerator.Enumerator() -> void +OpenTelemetry.Batch.Enumerator.MoveNext() -> bool +OpenTelemetry.Batch.Enumerator.Reset() -> void +OpenTelemetry.BatchActivityExportProcessor +OpenTelemetry.BatchExportProcessorOptions.BatchExportProcessorOptions() -> void +OpenTelemetry.BatchExportProcessorOptions.ExporterTimeoutMilliseconds.get -> int +OpenTelemetry.BatchExportProcessorOptions.ExporterTimeoutMilliseconds.set -> void +OpenTelemetry.BatchExportProcessorOptions.MaxExportBatchSize.get -> int +OpenTelemetry.BatchExportProcessorOptions.MaxExportBatchSize.set -> void +OpenTelemetry.BatchExportProcessorOptions.MaxQueueSize.get -> int +OpenTelemetry.BatchExportProcessorOptions.MaxQueueSize.set -> void +OpenTelemetry.BatchExportProcessorOptions.ScheduledDelayMilliseconds.get -> int +OpenTelemetry.BatchExportProcessorOptions.ScheduledDelayMilliseconds.set -> void +OpenTelemetry.BatchLogRecordExportProcessor +OpenTelemetry.BatchLogRecordExportProcessor.BatchLogRecordExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +OpenTelemetry.CompositeProcessor +OpenTelemetry.ExportProcessorType +OpenTelemetry.ExportProcessorType.Batch = 1 -> OpenTelemetry.ExportProcessorType +OpenTelemetry.ExportProcessorType.Simple = 0 -> OpenTelemetry.ExportProcessorType +OpenTelemetry.ExportResult +OpenTelemetry.ExportResult.Failure = 1 -> OpenTelemetry.ExportResult +OpenTelemetry.ExportResult.Success = 0 -> OpenTelemetry.ExportResult +OpenTelemetry.Logs.LogRecord +OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? +OpenTelemetry.Logs.LogRecord.EventId.get -> Microsoft.Extensions.Logging.EventId +OpenTelemetry.Logs.LogRecord.Exception.get -> System.Exception? +OpenTelemetry.Logs.LogRecord.ForEachScope(System.Action! callback, TState state) -> void +OpenTelemetry.Logs.LogRecord.FormattedMessage.get -> string? +OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void +OpenTelemetry.Logs.LogRecord.LogLevel.get -> Microsoft.Extensions.Logging.LogLevel +OpenTelemetry.Logs.LogRecord.SpanId.get -> System.Diagnostics.ActivitySpanId +OpenTelemetry.Logs.LogRecord.State.get -> object? +OpenTelemetry.Logs.LogRecord.State.set -> void +OpenTelemetry.Logs.LogRecord.StateValues.get -> System.Collections.Generic.IReadOnlyList>? +OpenTelemetry.Logs.LogRecord.StateValues.set -> void +OpenTelemetry.Logs.LogRecord.Timestamp.get -> System.DateTime +OpenTelemetry.Logs.LogRecord.TraceFlags.get -> System.Diagnostics.ActivityTraceFlags +OpenTelemetry.Logs.LogRecord.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Logs.LogRecord.TraceState.get -> string? +OpenTelemetry.Logs.LogRecordScope +OpenTelemetry.Logs.LogRecordScope.Enumerator +OpenTelemetry.Logs.LogRecordScope.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.Logs.LogRecordScope.Enumerator.Dispose() -> void +OpenTelemetry.Logs.LogRecordScope.Enumerator.Enumerator() -> void +OpenTelemetry.Logs.LogRecordScope.Enumerator.Enumerator(object? scope) -> void +OpenTelemetry.Logs.LogRecordScope.Enumerator.MoveNext() -> bool +OpenTelemetry.Logs.LogRecordScope.Enumerator.Reset() -> void +OpenTelemetry.Logs.LogRecordScope.GetEnumerator() -> OpenTelemetry.Logs.LogRecordScope.Enumerator +OpenTelemetry.Logs.LogRecordScope.LogRecordScope() -> void +OpenTelemetry.Logs.LogRecordScope.Scope.get -> object? +OpenTelemetry.Logs.OpenTelemetryLoggerOptions +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeFormattedMessage.get -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeFormattedMessage.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeScopes.get -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeScopes.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.OpenTelemetryLoggerOptions() -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ParseStateValues.get -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ParseStateValues.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.SetResourceBuilder(OpenTelemetry.Resources.ResourceBuilder! resourceBuilder) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +OpenTelemetry.Logs.OpenTelemetryLoggerProvider +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateLogger(string! categoryName) -> Microsoft.Extensions.Logging.ILogger! +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(Microsoft.Extensions.Options.IOptionsMonitor! options) -> void +OpenTelemetry.Metrics.AggregationTemporality +OpenTelemetry.Metrics.AggregationTemporality.Cumulative = 1 -> OpenTelemetry.Metrics.AggregationTemporality +OpenTelemetry.Metrics.AggregationTemporality.Delta = 2 -> OpenTelemetry.Metrics.AggregationTemporality +OpenTelemetry.Metrics.BaseExportingMetricReader +OpenTelemetry.Metrics.BaseExportingMetricReader.SupportedExportModes.get -> OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration +OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.ExplicitBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.ExportModes.Pull = 2 -> OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.ExportModes.Push = 1 -> OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.ExportModesAttribute +OpenTelemetry.Metrics.ExportModesAttribute.ExportModesAttribute(OpenTelemetry.Metrics.ExportModes supported) -> void +OpenTelemetry.Metrics.ExportModesAttribute.Supported.get -> OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.HistogramBucket +OpenTelemetry.Metrics.HistogramBucket.BucketCount.get -> long +OpenTelemetry.Metrics.HistogramBucket.ExplicitBound.get -> double +OpenTelemetry.Metrics.HistogramBucket.HistogramBucket() -> void +OpenTelemetry.Metrics.HistogramBuckets +OpenTelemetry.Metrics.HistogramBuckets.Enumerator +OpenTelemetry.Metrics.HistogramBuckets.Enumerator.Current.get -> OpenTelemetry.Metrics.HistogramBucket +OpenTelemetry.Metrics.HistogramBuckets.Enumerator.Enumerator() -> void +OpenTelemetry.Metrics.HistogramBuckets.Enumerator.MoveNext() -> bool +OpenTelemetry.Metrics.HistogramBuckets.GetEnumerator() -> OpenTelemetry.Metrics.HistogramBuckets.Enumerator +OpenTelemetry.Metrics.IPullMetricExporter +OpenTelemetry.Metrics.MeterProviderBuilderBase +OpenTelemetry.Metrics.MeterProviderBuilderBase.MeterProviderBuilderBase() -> void +OpenTelemetry.Metrics.MeterProviderBuilderExtensions +OpenTelemetry.Metrics.MeterProviderExtensions +OpenTelemetry.Metrics.Metric +OpenTelemetry.Metrics.Metric.GetMetricPoints() -> OpenTelemetry.Metrics.MetricPointsAccessor +OpenTelemetry.Metrics.Metric.MetricType.get -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.Metric.Temporality.get -> OpenTelemetry.Metrics.AggregationTemporality +OpenTelemetry.Metrics.MetricPoint +OpenTelemetry.Metrics.MetricPoint.EndTime.get -> System.DateTimeOffset +OpenTelemetry.Metrics.MetricPoint.GetGaugeLastValueDouble() -> double +OpenTelemetry.Metrics.MetricPoint.GetGaugeLastValueLong() -> long +OpenTelemetry.Metrics.MetricPoint.GetHistogramCount() -> long +OpenTelemetry.Metrics.MetricPoint.GetHistogramSum() -> double +OpenTelemetry.Metrics.MetricPoint.GetSumDouble() -> double +OpenTelemetry.Metrics.MetricPoint.GetSumLong() -> long +OpenTelemetry.Metrics.MetricPoint.MetricPoint() -> void +OpenTelemetry.Metrics.MetricPoint.StartTime.get -> System.DateTimeOffset +OpenTelemetry.Metrics.MetricPoint.Tags.get -> OpenTelemetry.ReadOnlyTagCollection +OpenTelemetry.Metrics.MetricPointsAccessor +OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator +OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator.Current.get -> OpenTelemetry.Metrics.MetricPoint +OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator.Enumerator() -> void +OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator.MoveNext() -> bool +OpenTelemetry.Metrics.MetricPointsAccessor.GetEnumerator() -> OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator +OpenTelemetry.Metrics.MetricPointsAccessor.MetricPointsAccessor() -> void +OpenTelemetry.Metrics.MetricReader +OpenTelemetry.Metrics.MetricReader.Collect(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Metrics.MetricReader.Dispose() -> void +OpenTelemetry.Metrics.MetricReader.MetricReader() -> void +OpenTelemetry.Metrics.MetricReader.Shutdown(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Metrics.MetricReader.TemporalityPreference.get -> OpenTelemetry.Metrics.MetricReaderTemporalityPreference +OpenTelemetry.Metrics.MetricReader.TemporalityPreference.set -> void +OpenTelemetry.Metrics.MetricReaderOptions +OpenTelemetry.Metrics.MetricReaderOptions.MetricReaderOptions() -> void +OpenTelemetry.Metrics.MetricReaderOptions.TemporalityPreference.get -> OpenTelemetry.Metrics.MetricReaderTemporalityPreference +OpenTelemetry.Metrics.MetricReaderOptions.TemporalityPreference.set -> void +OpenTelemetry.Metrics.MetricReaderTemporalityPreference +OpenTelemetry.Metrics.MetricReaderTemporalityPreference.Cumulative = 1 -> OpenTelemetry.Metrics.MetricReaderTemporalityPreference +OpenTelemetry.Metrics.MetricReaderTemporalityPreference.Delta = 2 -> OpenTelemetry.Metrics.MetricReaderTemporalityPreference +OpenTelemetry.Metrics.MetricStreamConfiguration +OpenTelemetry.Metrics.MetricStreamConfiguration.MetricStreamConfiguration() -> void +OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.DoubleGauge = 45 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.DoubleSum = 29 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.Histogram = 64 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.LongGauge = 42 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.LongSum = 26 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricTypeExtensions +OpenTelemetry.Metrics.PeriodicExportingMetricReader +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds.get -> int? +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds.set -> void +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds.get -> int? +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds.set -> void +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.PeriodicExportingMetricReaderOptions() -> void +OpenTelemetry.ProviderExtensions +OpenTelemetry.ReadOnlyTagCollection +OpenTelemetry.ReadOnlyTagCollection.Count.get -> int +OpenTelemetry.ReadOnlyTagCollection.Enumerator +OpenTelemetry.ReadOnlyTagCollection.Enumerator.Enumerator() -> void +OpenTelemetry.ReadOnlyTagCollection.Enumerator.MoveNext() -> bool +OpenTelemetry.ReadOnlyTagCollection.GetEnumerator() -> OpenTelemetry.ReadOnlyTagCollection.Enumerator +OpenTelemetry.ReadOnlyTagCollection.ReadOnlyTagCollection() -> void +OpenTelemetry.Resources.IResourceDetector +OpenTelemetry.Resources.Resource +OpenTelemetry.Resources.ResourceBuilder +OpenTelemetry.Resources.ResourceBuilderExtensions +OpenTelemetry.Sdk +OpenTelemetry.SimpleActivityExportProcessor +OpenTelemetry.SimpleLogRecordExportProcessor +OpenTelemetry.SimpleLogRecordExportProcessor.SimpleLogRecordExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void +OpenTelemetry.SuppressInstrumentationScope +OpenTelemetry.SuppressInstrumentationScope.Dispose() -> void +OpenTelemetry.Trace.AlwaysOffSampler +OpenTelemetry.Trace.AlwaysOffSampler.AlwaysOffSampler() -> void +OpenTelemetry.Trace.AlwaysOnSampler +OpenTelemetry.Trace.AlwaysOnSampler.AlwaysOnSampler() -> void +OpenTelemetry.Trace.BatchExportActivityProcessorOptions +OpenTelemetry.Trace.BatchExportActivityProcessorOptions.BatchExportActivityProcessorOptions() -> void +OpenTelemetry.Trace.ParentBasedSampler +OpenTelemetry.Trace.Sampler +OpenTelemetry.Trace.Sampler.Sampler() -> void +OpenTelemetry.Trace.SamplingDecision +OpenTelemetry.Trace.SamplingDecision.Drop = 0 -> OpenTelemetry.Trace.SamplingDecision +OpenTelemetry.Trace.SamplingDecision.RecordAndSample = 2 -> OpenTelemetry.Trace.SamplingDecision +OpenTelemetry.Trace.SamplingDecision.RecordOnly = 1 -> OpenTelemetry.Trace.SamplingDecision +OpenTelemetry.Trace.SamplingParameters +OpenTelemetry.Trace.SamplingParameters.Kind.get -> System.Diagnostics.ActivityKind +OpenTelemetry.Trace.SamplingParameters.ParentContext.get -> System.Diagnostics.ActivityContext +OpenTelemetry.Trace.SamplingParameters.SamplingParameters() -> void +OpenTelemetry.Trace.SamplingParameters.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Trace.SamplingResult +OpenTelemetry.Trace.SamplingResult.Decision.get -> OpenTelemetry.Trace.SamplingDecision +OpenTelemetry.Trace.SamplingResult.Equals(OpenTelemetry.Trace.SamplingResult other) -> bool +OpenTelemetry.Trace.SamplingResult.SamplingResult() -> void +OpenTelemetry.Trace.SamplingResult.SamplingResult(bool isSampled) -> void +OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision) -> void +OpenTelemetry.Trace.TraceIdRatioBasedSampler +OpenTelemetry.Trace.TraceIdRatioBasedSampler.TraceIdRatioBasedSampler(double probability) -> void +OpenTelemetry.Trace.TracerProviderBuilderBase +OpenTelemetry.Trace.TracerProviderBuilderBase.TracerProviderBuilderBase() -> void +OpenTelemetry.Trace.TracerProviderBuilderExtensions +OpenTelemetry.Trace.TracerProviderExtensions +override OpenTelemetry.BaseExportProcessor.Dispose(bool disposing) -> void +override OpenTelemetry.BaseExportProcessor.OnForceFlush(int timeoutMilliseconds) -> bool +override OpenTelemetry.BaseExportProcessor.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.BatchExportProcessor.Dispose(bool disposing) -> void +override OpenTelemetry.BatchExportProcessor.OnForceFlush(int timeoutMilliseconds) -> bool +override OpenTelemetry.BatchExportProcessor.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.BatchLogRecordExportProcessor.OnEnd(OpenTelemetry.Logs.LogRecord! data) -> void +override OpenTelemetry.CompositeProcessor.Dispose(bool disposing) -> void +override OpenTelemetry.CompositeProcessor.OnEnd(T data) -> void +override OpenTelemetry.CompositeProcessor.OnForceFlush(int timeoutMilliseconds) -> bool +override OpenTelemetry.CompositeProcessor.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.CompositeProcessor.OnStart(T data) -> void +override OpenTelemetry.Logs.OpenTelemetryLoggerProvider.Dispose(bool disposing) -> void +override OpenTelemetry.Metrics.BaseExportingMetricReader.Dispose(bool disposing) -> void +override OpenTelemetry.Metrics.BaseExportingMetricReader.OnCollect(int timeoutMilliseconds) -> bool +override OpenTelemetry.Metrics.BaseExportingMetricReader.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.Metrics.PeriodicExportingMetricReader.Dispose(bool disposing) -> void +override OpenTelemetry.Metrics.PeriodicExportingMetricReader.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.Trace.AlwaysOffSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult +override OpenTelemetry.Trace.AlwaysOnSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult +override OpenTelemetry.Trace.ParentBasedSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult +override OpenTelemetry.Trace.SamplingResult.GetHashCode() -> int +override OpenTelemetry.Trace.TraceIdRatioBasedSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static OpenTelemetry.Metrics.MetricTypeExtensions.IsDouble(this OpenTelemetry.Metrics.MetricType self) -> bool +static OpenTelemetry.Metrics.MetricTypeExtensions.IsGauge(this OpenTelemetry.Metrics.MetricType self) -> bool +static OpenTelemetry.Metrics.MetricTypeExtensions.IsHistogram(this OpenTelemetry.Metrics.MetricType self) -> bool +static OpenTelemetry.Metrics.MetricTypeExtensions.IsLong(this OpenTelemetry.Metrics.MetricType self) -> bool +static OpenTelemetry.Metrics.MetricTypeExtensions.IsSum(this OpenTelemetry.Metrics.MetricType self) -> bool +static OpenTelemetry.Sdk.SuppressInstrumentation.get -> bool +static OpenTelemetry.SuppressInstrumentationScope.Enter() -> int +static OpenTelemetry.Trace.SamplingResult.operator !=(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool +static OpenTelemetry.Trace.SamplingResult.operator ==(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool +virtual OpenTelemetry.BaseExporter.Dispose(bool disposing) -> void +virtual OpenTelemetry.BaseExporter.OnForceFlush(int timeoutMilliseconds) -> bool +virtual OpenTelemetry.BaseExporter.OnShutdown(int timeoutMilliseconds) -> bool +virtual OpenTelemetry.BaseProcessor.Dispose(bool disposing) -> void +virtual OpenTelemetry.BaseProcessor.OnEnd(T data) -> void +virtual OpenTelemetry.BaseProcessor.OnForceFlush(int timeoutMilliseconds) -> bool +virtual OpenTelemetry.BaseProcessor.OnShutdown(int timeoutMilliseconds) -> bool +virtual OpenTelemetry.BaseProcessor.OnStart(T data) -> void +virtual OpenTelemetry.Metrics.MetricReader.Dispose(bool disposing) -> void +virtual OpenTelemetry.Metrics.MetricReader.OnCollect(int timeoutMilliseconds) -> bool +virtual OpenTelemetry.Metrics.MetricReader.OnShutdown(int timeoutMilliseconds) -> bool diff --git a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..a045a0ffb --- /dev/null +++ b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt @@ -0,0 +1,20 @@ +OpenTelemetry.Logs.LogRecord.CategoryName.set -> void +OpenTelemetry.Logs.LogRecord.EventId.set -> void +OpenTelemetry.Logs.LogRecord.Exception.set -> void +OpenTelemetry.Logs.LogRecord.LogLevel.set -> void +OpenTelemetry.Logs.LogRecord.SpanId.set -> void +OpenTelemetry.Logs.LogRecord.Timestamp.set -> void +OpenTelemetry.Logs.LogRecord.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecord.TraceId.set -> void +OpenTelemetry.Logs.LogRecord.TraceState.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ConfigureResource(System.Action! configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider() -> void +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void +*REMOVED*static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, bool disposeProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt index 56774a7fc..afa4f5a68 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -1,21 +1,21 @@ #nullable enable -~abstract OpenTelemetry.BaseExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult -~abstract OpenTelemetry.BaseExportProcessor.OnExport(T data) -> void -~OpenTelemetry.BaseExporter -~OpenTelemetry.BaseExporter.ParentProvider.get -> OpenTelemetry.BaseProvider -~OpenTelemetry.BaseExportProcessor -~OpenTelemetry.BaseExportProcessor.BaseExportProcessor(OpenTelemetry.BaseExporter exporter) -> void -~OpenTelemetry.BaseProcessor.ParentProvider.get -> OpenTelemetry.BaseProvider -~OpenTelemetry.Batch -~OpenTelemetry.Batch.Batch(T[] items, int count) -> void -~OpenTelemetry.Batch.Enumerator.Current.get -> T +abstract OpenTelemetry.BaseExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult +abstract OpenTelemetry.BaseExportProcessor.OnExport(T! data) -> void +OpenTelemetry.BaseExporter +OpenTelemetry.BaseExporter.ParentProvider.get -> OpenTelemetry.BaseProvider? +OpenTelemetry.BaseExportProcessor +OpenTelemetry.BaseExportProcessor.BaseExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void +OpenTelemetry.BaseProcessor.ParentProvider.get -> OpenTelemetry.BaseProvider? +OpenTelemetry.Batch +OpenTelemetry.Batch.Batch(T![]! items, int count) -> void +OpenTelemetry.Batch.Enumerator.Current.get -> T! ~OpenTelemetry.Batch.GetEnumerator() -> OpenTelemetry.Batch.Enumerator ~OpenTelemetry.BatchActivityExportProcessor.BatchActivityExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void -~OpenTelemetry.BatchExportProcessor -~OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +OpenTelemetry.BatchExportProcessor +OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void ~OpenTelemetry.BatchExportProcessorOptions -~OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.CompositeProcessor -~OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable> processors) -> void +OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.CompositeProcessor! +OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable!>! processors) -> void ~OpenTelemetry.Metrics.BaseExportingMetricReader.BaseExportingMetricReader(OpenTelemetry.BaseExporter exporter) -> void ~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.get -> double[] ~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.set -> void @@ -46,8 +46,8 @@ ~OpenTelemetry.Resources.ResourceBuilder.Build() -> OpenTelemetry.Resources.Resource ~OpenTelemetry.Resources.ResourceBuilder.Clear() -> OpenTelemetry.Resources.ResourceBuilder ~OpenTelemetry.SimpleActivityExportProcessor.SimpleActivityExportProcessor(OpenTelemetry.BaseExporter exporter) -> void -~OpenTelemetry.SimpleExportProcessor -~OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter exporter) -> void +OpenTelemetry.SimpleExportProcessor +OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void ~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler) -> void ~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler, OpenTelemetry.Trace.Sampler remoteParentSampled = null, OpenTelemetry.Trace.Sampler remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler localParentSampled = null, OpenTelemetry.Trace.Sampler localParentNotSampled = null) -> void ~OpenTelemetry.Trace.Sampler.Description.get -> string @@ -60,19 +60,19 @@ ~OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision, System.Collections.Generic.IEnumerable> attributes) -> void ~OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(string instrumentationName, string instrumentationVersion, System.Func instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder ~OpenTelemetry.Trace.TracerProviderBuilderBase.Build() -> OpenTelemetry.Trace.TracerProvider -~override OpenTelemetry.BaseExportProcessor.OnEnd(T data) -> void +override OpenTelemetry.BaseExportProcessor.OnEnd(T! data) -> void ~override OpenTelemetry.BatchActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void -~override OpenTelemetry.BatchExportProcessor.OnExport(T data) -> void +override OpenTelemetry.BatchExportProcessor.OnExport(T! data) -> void ~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Metrics.MeterProviderBuilder ~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddMeter(params string[] names) -> OpenTelemetry.Metrics.MeterProviderBuilder ~override OpenTelemetry.SimpleActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void -~override OpenTelemetry.SimpleExportProcessor.OnExport(T data) -> void +override OpenTelemetry.SimpleExportProcessor.OnExport(T! data) -> void ~override OpenTelemetry.Trace.SamplingResult.Equals(object obj) -> bool ~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder ~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddLegacySource(string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder ~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddSource(params string[] names) -> OpenTelemetry.Trace.TracerProviderBuilder -~override sealed OpenTelemetry.BaseExportProcessor.OnStart(T data) -> void -~readonly OpenTelemetry.BaseExportProcessor.exporter -> OpenTelemetry.BaseExporter +override sealed OpenTelemetry.BaseExportProcessor.OnStart(T! data) -> void +readonly OpenTelemetry.BaseExportProcessor.exporter -> OpenTelemetry.BaseExporter! ~readonly OpenTelemetry.Metrics.BaseExportingMetricReader.exporter -> OpenTelemetry.BaseExporter ~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddReader(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, OpenTelemetry.Metrics.MetricReader reader) -> OpenTelemetry.Metrics.MeterProviderBuilder ~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, string instrumentName, OpenTelemetry.Metrics.MetricStreamConfiguration metricStreamConfiguration) -> OpenTelemetry.Metrics.MeterProviderBuilder @@ -145,7 +145,7 @@ OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Failure = 1 -> OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Success = 0 -> OpenTelemetry.ExportResult OpenTelemetry.Logs.LogRecord -OpenTelemetry.Logs.LogRecord.CategoryName.get -> string! +OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? OpenTelemetry.Logs.LogRecord.EventId.get -> Microsoft.Extensions.Logging.EventId OpenTelemetry.Logs.LogRecord.Exception.get -> System.Exception? OpenTelemetry.Logs.LogRecord.ForEachScope(System.Action! callback, TState state) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index e69de29bb..791e1c4f8 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,20 @@ +OpenTelemetry.Logs.LogRecord.CategoryName.set -> void +OpenTelemetry.Logs.LogRecord.EventId.set -> void +OpenTelemetry.Logs.LogRecord.Exception.set -> void +OpenTelemetry.Logs.LogRecord.LogLevel.set -> void +OpenTelemetry.Logs.LogRecord.SpanId.set -> void +OpenTelemetry.Logs.LogRecord.Timestamp.set -> void +OpenTelemetry.Logs.LogRecord.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecord.TraceId.set -> void +OpenTelemetry.Logs.LogRecord.TraceState.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider() -> void +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ConfigureResource(System.Action! configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +*REMOVED*static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, bool disposeProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Shipped.txt new file mode 100644 index 000000000..afa4f5a68 --- /dev/null +++ b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Shipped.txt @@ -0,0 +1,359 @@ +#nullable enable +abstract OpenTelemetry.BaseExporter.Export(in OpenTelemetry.Batch batch) -> OpenTelemetry.ExportResult +abstract OpenTelemetry.BaseExportProcessor.OnExport(T! data) -> void +OpenTelemetry.BaseExporter +OpenTelemetry.BaseExporter.ParentProvider.get -> OpenTelemetry.BaseProvider? +OpenTelemetry.BaseExportProcessor +OpenTelemetry.BaseExportProcessor.BaseExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void +OpenTelemetry.BaseProcessor.ParentProvider.get -> OpenTelemetry.BaseProvider? +OpenTelemetry.Batch +OpenTelemetry.Batch.Batch(T![]! items, int count) -> void +OpenTelemetry.Batch.Enumerator.Current.get -> T! +~OpenTelemetry.Batch.GetEnumerator() -> OpenTelemetry.Batch.Enumerator +~OpenTelemetry.BatchActivityExportProcessor.BatchActivityExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +OpenTelemetry.BatchExportProcessor +OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +~OpenTelemetry.BatchExportProcessorOptions +OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.CompositeProcessor! +OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable!>! processors) -> void +~OpenTelemetry.Metrics.BaseExportingMetricReader.BaseExportingMetricReader(OpenTelemetry.BaseExporter exporter) -> void +~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.get -> double[] +~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.set -> void +~OpenTelemetry.Metrics.IPullMetricExporter.Collect.get -> System.Func +~OpenTelemetry.Metrics.IPullMetricExporter.Collect.set -> void +~OpenTelemetry.Metrics.MeterProviderBuilderBase.Build() -> OpenTelemetry.Metrics.MeterProvider +~OpenTelemetry.Metrics.Metric.Description.get -> string +~OpenTelemetry.Metrics.Metric.MeterName.get -> string +~OpenTelemetry.Metrics.Metric.MeterVersion.get -> string +~OpenTelemetry.Metrics.Metric.Name.get -> string +~OpenTelemetry.Metrics.Metric.Unit.get -> string +~OpenTelemetry.Metrics.MetricPoint.GetHistogramBuckets() -> OpenTelemetry.Metrics.HistogramBuckets +~OpenTelemetry.Metrics.MetricReaderOptions.PeriodicExportingMetricReaderOptions.get -> OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions +~OpenTelemetry.Metrics.MetricReaderOptions.PeriodicExportingMetricReaderOptions.set -> void +~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.get -> string +~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.set -> void +~OpenTelemetry.Metrics.MetricStreamConfiguration.Name.get -> string +~OpenTelemetry.Metrics.MetricStreamConfiguration.Name.set -> void +~OpenTelemetry.Metrics.MetricStreamConfiguration.TagKeys.get -> string[] +~OpenTelemetry.Metrics.MetricStreamConfiguration.TagKeys.set -> void +~OpenTelemetry.Metrics.PeriodicExportingMetricReader.PeriodicExportingMetricReader(OpenTelemetry.BaseExporter exporter, int exportIntervalMilliseconds = 60000, int exportTimeoutMilliseconds = 30000) -> void +~OpenTelemetry.ReadOnlyTagCollection.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair +~OpenTelemetry.Resources.IResourceDetector.Detect() -> OpenTelemetry.Resources.Resource +~OpenTelemetry.Resources.Resource.Attributes.get -> System.Collections.Generic.IEnumerable> +~OpenTelemetry.Resources.Resource.Merge(OpenTelemetry.Resources.Resource other) -> OpenTelemetry.Resources.Resource +~OpenTelemetry.Resources.Resource.Resource(System.Collections.Generic.IEnumerable> attributes) -> void +~OpenTelemetry.Resources.ResourceBuilder.AddDetector(OpenTelemetry.Resources.IResourceDetector resourceDetector) -> OpenTelemetry.Resources.ResourceBuilder +~OpenTelemetry.Resources.ResourceBuilder.Build() -> OpenTelemetry.Resources.Resource +~OpenTelemetry.Resources.ResourceBuilder.Clear() -> OpenTelemetry.Resources.ResourceBuilder +~OpenTelemetry.SimpleActivityExportProcessor.SimpleActivityExportProcessor(OpenTelemetry.BaseExporter exporter) -> void +OpenTelemetry.SimpleExportProcessor +OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void +~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler) -> void +~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler, OpenTelemetry.Trace.Sampler remoteParentSampled = null, OpenTelemetry.Trace.Sampler remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler localParentSampled = null, OpenTelemetry.Trace.Sampler localParentNotSampled = null) -> void +~OpenTelemetry.Trace.Sampler.Description.get -> string +~OpenTelemetry.Trace.Sampler.Description.set -> void +~OpenTelemetry.Trace.SamplingParameters.Links.get -> System.Collections.Generic.IEnumerable +~OpenTelemetry.Trace.SamplingParameters.Name.get -> string +~OpenTelemetry.Trace.SamplingParameters.SamplingParameters(System.Diagnostics.ActivityContext parentContext, System.Diagnostics.ActivityTraceId traceId, string name, System.Diagnostics.ActivityKind kind, System.Collections.Generic.IEnumerable> tags = null, System.Collections.Generic.IEnumerable links = null) -> void +~OpenTelemetry.Trace.SamplingParameters.Tags.get -> System.Collections.Generic.IEnumerable> +~OpenTelemetry.Trace.SamplingResult.Attributes.get -> System.Collections.Generic.IEnumerable> +~OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision, System.Collections.Generic.IEnumerable> attributes) -> void +~OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(string instrumentationName, string instrumentationVersion, System.Func instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder +~OpenTelemetry.Trace.TracerProviderBuilderBase.Build() -> OpenTelemetry.Trace.TracerProvider +override OpenTelemetry.BaseExportProcessor.OnEnd(T! data) -> void +~override OpenTelemetry.BatchActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void +override OpenTelemetry.BatchExportProcessor.OnExport(T! data) -> void +~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Metrics.MeterProviderBuilder +~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddMeter(params string[] names) -> OpenTelemetry.Metrics.MeterProviderBuilder +~override OpenTelemetry.SimpleActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void +override OpenTelemetry.SimpleExportProcessor.OnExport(T! data) -> void +~override OpenTelemetry.Trace.SamplingResult.Equals(object obj) -> bool +~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder +~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddLegacySource(string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder +~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddSource(params string[] names) -> OpenTelemetry.Trace.TracerProviderBuilder +override sealed OpenTelemetry.BaseExportProcessor.OnStart(T! data) -> void +readonly OpenTelemetry.BaseExportProcessor.exporter -> OpenTelemetry.BaseExporter! +~readonly OpenTelemetry.Metrics.BaseExportingMetricReader.exporter -> OpenTelemetry.BaseExporter +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddReader(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, OpenTelemetry.Metrics.MetricReader reader) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, string instrumentName, OpenTelemetry.Metrics.MetricStreamConfiguration metricStreamConfiguration) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, string instrumentName, string name) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddView(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Func viewConfig) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.Build(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder) -> OpenTelemetry.Metrics.MeterProvider +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetMaxMetricPointsPerMetricStream(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, int maxMetricPointsPerMetricStream) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetMaxMetricStreams(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, int maxMetricStreams) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetResourceBuilder(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, OpenTelemetry.Resources.ResourceBuilder resourceBuilder) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Metrics.MeterProviderExtensions.ForceFlush(this OpenTelemetry.Metrics.MeterProvider provider, int timeoutMilliseconds = -1) -> bool +~static OpenTelemetry.Metrics.MeterProviderExtensions.Shutdown(this OpenTelemetry.Metrics.MeterProvider provider, int timeoutMilliseconds = -1) -> bool +~static OpenTelemetry.Metrics.MetricStreamConfiguration.Drop.get -> OpenTelemetry.Metrics.MetricStreamConfiguration +~static OpenTelemetry.ProviderExtensions.GetDefaultResource(this OpenTelemetry.BaseProvider baseProvider) -> OpenTelemetry.Resources.Resource +~static OpenTelemetry.ProviderExtensions.GetResource(this OpenTelemetry.BaseProvider baseProvider) -> OpenTelemetry.Resources.Resource +~static OpenTelemetry.Resources.Resource.Empty.get -> OpenTelemetry.Resources.Resource +~static OpenTelemetry.Resources.ResourceBuilder.CreateDefault() -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Resources.ResourceBuilder.CreateEmpty() -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Resources.ResourceBuilderExtensions.AddAttributes(this OpenTelemetry.Resources.ResourceBuilder resourceBuilder, System.Collections.Generic.IEnumerable> attributes) -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Resources.ResourceBuilderExtensions.AddEnvironmentVariableDetector(this OpenTelemetry.Resources.ResourceBuilder resourceBuilder) -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Resources.ResourceBuilderExtensions.AddService(this OpenTelemetry.Resources.ResourceBuilder resourceBuilder, string serviceName, string serviceNamespace = null, string serviceVersion = null, bool autoGenerateServiceInstanceId = true, string serviceInstanceId = null) -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Resources.ResourceBuilderExtensions.AddTelemetrySdk(this OpenTelemetry.Resources.ResourceBuilder resourceBuilder) -> OpenTelemetry.Resources.ResourceBuilder +~static OpenTelemetry.Sdk.CreateMeterProviderBuilder() -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Sdk.CreateTracerProviderBuilder() -> OpenTelemetry.Trace.TracerProviderBuilder +~static OpenTelemetry.Sdk.SetDefaultTextMapPropagator(OpenTelemetry.Context.Propagation.TextMapPropagator textMapPropagator) -> void +~static OpenTelemetry.SuppressInstrumentationScope.Begin(bool value = true) -> System.IDisposable +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddProcessor(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.Trace.TracerProviderBuilder +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.Build(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder) -> OpenTelemetry.Trace.TracerProvider +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.SetErrorStatusOnException(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, bool enabled = true) -> OpenTelemetry.Trace.TracerProviderBuilder +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.SetResourceBuilder(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, OpenTelemetry.Resources.ResourceBuilder resourceBuilder) -> OpenTelemetry.Trace.TracerProviderBuilder +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.SetSampler(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, OpenTelemetry.Trace.Sampler sampler) -> OpenTelemetry.Trace.TracerProviderBuilder +~static OpenTelemetry.Trace.TracerProviderExtensions.AddProcessor(this OpenTelemetry.Trace.TracerProvider provider, OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.Trace.TracerProvider +~static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool +~static OpenTelemetry.Trace.TracerProviderExtensions.Shutdown(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool +abstract OpenTelemetry.Trace.Sampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult +Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions +OpenTelemetry.BaseExporter.BaseExporter() -> void +OpenTelemetry.BaseExporter.Dispose() -> void +OpenTelemetry.BaseExporter.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.BaseExporter.Shutdown(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.BaseProcessor +OpenTelemetry.BaseProcessor.BaseProcessor() -> void +OpenTelemetry.BaseProcessor.Dispose() -> void +OpenTelemetry.BaseProcessor.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.BaseProcessor.Shutdown(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Batch.Batch() -> void +OpenTelemetry.Batch.Count.get -> long +OpenTelemetry.Batch.Dispose() -> void +OpenTelemetry.Batch.Enumerator +OpenTelemetry.Batch.Enumerator.Dispose() -> void +OpenTelemetry.Batch.Enumerator.Enumerator() -> void +OpenTelemetry.Batch.Enumerator.MoveNext() -> bool +OpenTelemetry.Batch.Enumerator.Reset() -> void +OpenTelemetry.BatchActivityExportProcessor +OpenTelemetry.BatchExportProcessorOptions.BatchExportProcessorOptions() -> void +OpenTelemetry.BatchExportProcessorOptions.ExporterTimeoutMilliseconds.get -> int +OpenTelemetry.BatchExportProcessorOptions.ExporterTimeoutMilliseconds.set -> void +OpenTelemetry.BatchExportProcessorOptions.MaxExportBatchSize.get -> int +OpenTelemetry.BatchExportProcessorOptions.MaxExportBatchSize.set -> void +OpenTelemetry.BatchExportProcessorOptions.MaxQueueSize.get -> int +OpenTelemetry.BatchExportProcessorOptions.MaxQueueSize.set -> void +OpenTelemetry.BatchExportProcessorOptions.ScheduledDelayMilliseconds.get -> int +OpenTelemetry.BatchExportProcessorOptions.ScheduledDelayMilliseconds.set -> void +OpenTelemetry.BatchLogRecordExportProcessor +OpenTelemetry.BatchLogRecordExportProcessor.BatchLogRecordExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +OpenTelemetry.CompositeProcessor +OpenTelemetry.ExportProcessorType +OpenTelemetry.ExportProcessorType.Batch = 1 -> OpenTelemetry.ExportProcessorType +OpenTelemetry.ExportProcessorType.Simple = 0 -> OpenTelemetry.ExportProcessorType +OpenTelemetry.ExportResult +OpenTelemetry.ExportResult.Failure = 1 -> OpenTelemetry.ExportResult +OpenTelemetry.ExportResult.Success = 0 -> OpenTelemetry.ExportResult +OpenTelemetry.Logs.LogRecord +OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? +OpenTelemetry.Logs.LogRecord.EventId.get -> Microsoft.Extensions.Logging.EventId +OpenTelemetry.Logs.LogRecord.Exception.get -> System.Exception? +OpenTelemetry.Logs.LogRecord.ForEachScope(System.Action! callback, TState state) -> void +OpenTelemetry.Logs.LogRecord.FormattedMessage.get -> string? +OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void +OpenTelemetry.Logs.LogRecord.LogLevel.get -> Microsoft.Extensions.Logging.LogLevel +OpenTelemetry.Logs.LogRecord.SpanId.get -> System.Diagnostics.ActivitySpanId +OpenTelemetry.Logs.LogRecord.State.get -> object? +OpenTelemetry.Logs.LogRecord.State.set -> void +OpenTelemetry.Logs.LogRecord.StateValues.get -> System.Collections.Generic.IReadOnlyList>? +OpenTelemetry.Logs.LogRecord.StateValues.set -> void +OpenTelemetry.Logs.LogRecord.Timestamp.get -> System.DateTime +OpenTelemetry.Logs.LogRecord.TraceFlags.get -> System.Diagnostics.ActivityTraceFlags +OpenTelemetry.Logs.LogRecord.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Logs.LogRecord.TraceState.get -> string? +OpenTelemetry.Logs.LogRecordScope +OpenTelemetry.Logs.LogRecordScope.Enumerator +OpenTelemetry.Logs.LogRecordScope.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.Logs.LogRecordScope.Enumerator.Dispose() -> void +OpenTelemetry.Logs.LogRecordScope.Enumerator.Enumerator() -> void +OpenTelemetry.Logs.LogRecordScope.Enumerator.Enumerator(object? scope) -> void +OpenTelemetry.Logs.LogRecordScope.Enumerator.MoveNext() -> bool +OpenTelemetry.Logs.LogRecordScope.Enumerator.Reset() -> void +OpenTelemetry.Logs.LogRecordScope.GetEnumerator() -> OpenTelemetry.Logs.LogRecordScope.Enumerator +OpenTelemetry.Logs.LogRecordScope.LogRecordScope() -> void +OpenTelemetry.Logs.LogRecordScope.Scope.get -> object? +OpenTelemetry.Logs.OpenTelemetryLoggerOptions +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeFormattedMessage.get -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeFormattedMessage.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeScopes.get -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeScopes.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.OpenTelemetryLoggerOptions() -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ParseStateValues.get -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ParseStateValues.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.SetResourceBuilder(OpenTelemetry.Resources.ResourceBuilder! resourceBuilder) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +OpenTelemetry.Logs.OpenTelemetryLoggerProvider +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateLogger(string! categoryName) -> Microsoft.Extensions.Logging.ILogger! +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(Microsoft.Extensions.Options.IOptionsMonitor! options) -> void +OpenTelemetry.Metrics.AggregationTemporality +OpenTelemetry.Metrics.AggregationTemporality.Cumulative = 1 -> OpenTelemetry.Metrics.AggregationTemporality +OpenTelemetry.Metrics.AggregationTemporality.Delta = 2 -> OpenTelemetry.Metrics.AggregationTemporality +OpenTelemetry.Metrics.BaseExportingMetricReader +OpenTelemetry.Metrics.BaseExportingMetricReader.SupportedExportModes.get -> OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration +OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.ExplicitBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.ExportModes.Pull = 2 -> OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.ExportModes.Push = 1 -> OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.ExportModesAttribute +OpenTelemetry.Metrics.ExportModesAttribute.ExportModesAttribute(OpenTelemetry.Metrics.ExportModes supported) -> void +OpenTelemetry.Metrics.ExportModesAttribute.Supported.get -> OpenTelemetry.Metrics.ExportModes +OpenTelemetry.Metrics.HistogramBucket +OpenTelemetry.Metrics.HistogramBucket.BucketCount.get -> long +OpenTelemetry.Metrics.HistogramBucket.ExplicitBound.get -> double +OpenTelemetry.Metrics.HistogramBucket.HistogramBucket() -> void +OpenTelemetry.Metrics.HistogramBuckets +OpenTelemetry.Metrics.HistogramBuckets.Enumerator +OpenTelemetry.Metrics.HistogramBuckets.Enumerator.Current.get -> OpenTelemetry.Metrics.HistogramBucket +OpenTelemetry.Metrics.HistogramBuckets.Enumerator.Enumerator() -> void +OpenTelemetry.Metrics.HistogramBuckets.Enumerator.MoveNext() -> bool +OpenTelemetry.Metrics.HistogramBuckets.GetEnumerator() -> OpenTelemetry.Metrics.HistogramBuckets.Enumerator +OpenTelemetry.Metrics.IPullMetricExporter +OpenTelemetry.Metrics.MeterProviderBuilderBase +OpenTelemetry.Metrics.MeterProviderBuilderBase.MeterProviderBuilderBase() -> void +OpenTelemetry.Metrics.MeterProviderBuilderExtensions +OpenTelemetry.Metrics.MeterProviderExtensions +OpenTelemetry.Metrics.Metric +OpenTelemetry.Metrics.Metric.GetMetricPoints() -> OpenTelemetry.Metrics.MetricPointsAccessor +OpenTelemetry.Metrics.Metric.MetricType.get -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.Metric.Temporality.get -> OpenTelemetry.Metrics.AggregationTemporality +OpenTelemetry.Metrics.MetricPoint +OpenTelemetry.Metrics.MetricPoint.EndTime.get -> System.DateTimeOffset +OpenTelemetry.Metrics.MetricPoint.GetGaugeLastValueDouble() -> double +OpenTelemetry.Metrics.MetricPoint.GetGaugeLastValueLong() -> long +OpenTelemetry.Metrics.MetricPoint.GetHistogramCount() -> long +OpenTelemetry.Metrics.MetricPoint.GetHistogramSum() -> double +OpenTelemetry.Metrics.MetricPoint.GetSumDouble() -> double +OpenTelemetry.Metrics.MetricPoint.GetSumLong() -> long +OpenTelemetry.Metrics.MetricPoint.MetricPoint() -> void +OpenTelemetry.Metrics.MetricPoint.StartTime.get -> System.DateTimeOffset +OpenTelemetry.Metrics.MetricPoint.Tags.get -> OpenTelemetry.ReadOnlyTagCollection +OpenTelemetry.Metrics.MetricPointsAccessor +OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator +OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator.Current.get -> OpenTelemetry.Metrics.MetricPoint +OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator.Enumerator() -> void +OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator.MoveNext() -> bool +OpenTelemetry.Metrics.MetricPointsAccessor.GetEnumerator() -> OpenTelemetry.Metrics.MetricPointsAccessor.Enumerator +OpenTelemetry.Metrics.MetricPointsAccessor.MetricPointsAccessor() -> void +OpenTelemetry.Metrics.MetricReader +OpenTelemetry.Metrics.MetricReader.Collect(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Metrics.MetricReader.Dispose() -> void +OpenTelemetry.Metrics.MetricReader.MetricReader() -> void +OpenTelemetry.Metrics.MetricReader.Shutdown(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Metrics.MetricReader.TemporalityPreference.get -> OpenTelemetry.Metrics.MetricReaderTemporalityPreference +OpenTelemetry.Metrics.MetricReader.TemporalityPreference.set -> void +OpenTelemetry.Metrics.MetricReaderOptions +OpenTelemetry.Metrics.MetricReaderOptions.MetricReaderOptions() -> void +OpenTelemetry.Metrics.MetricReaderOptions.TemporalityPreference.get -> OpenTelemetry.Metrics.MetricReaderTemporalityPreference +OpenTelemetry.Metrics.MetricReaderOptions.TemporalityPreference.set -> void +OpenTelemetry.Metrics.MetricReaderTemporalityPreference +OpenTelemetry.Metrics.MetricReaderTemporalityPreference.Cumulative = 1 -> OpenTelemetry.Metrics.MetricReaderTemporalityPreference +OpenTelemetry.Metrics.MetricReaderTemporalityPreference.Delta = 2 -> OpenTelemetry.Metrics.MetricReaderTemporalityPreference +OpenTelemetry.Metrics.MetricStreamConfiguration +OpenTelemetry.Metrics.MetricStreamConfiguration.MetricStreamConfiguration() -> void +OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.DoubleGauge = 45 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.DoubleSum = 29 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.Histogram = 64 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.LongGauge = 42 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.LongSum = 26 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricTypeExtensions +OpenTelemetry.Metrics.PeriodicExportingMetricReader +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds.get -> int? +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds.set -> void +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds.get -> int? +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds.set -> void +OpenTelemetry.Metrics.PeriodicExportingMetricReaderOptions.PeriodicExportingMetricReaderOptions() -> void +OpenTelemetry.ProviderExtensions +OpenTelemetry.ReadOnlyTagCollection +OpenTelemetry.ReadOnlyTagCollection.Count.get -> int +OpenTelemetry.ReadOnlyTagCollection.Enumerator +OpenTelemetry.ReadOnlyTagCollection.Enumerator.Enumerator() -> void +OpenTelemetry.ReadOnlyTagCollection.Enumerator.MoveNext() -> bool +OpenTelemetry.ReadOnlyTagCollection.GetEnumerator() -> OpenTelemetry.ReadOnlyTagCollection.Enumerator +OpenTelemetry.ReadOnlyTagCollection.ReadOnlyTagCollection() -> void +OpenTelemetry.Resources.IResourceDetector +OpenTelemetry.Resources.Resource +OpenTelemetry.Resources.ResourceBuilder +OpenTelemetry.Resources.ResourceBuilderExtensions +OpenTelemetry.Sdk +OpenTelemetry.SimpleActivityExportProcessor +OpenTelemetry.SimpleLogRecordExportProcessor +OpenTelemetry.SimpleLogRecordExportProcessor.SimpleLogRecordExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void +OpenTelemetry.SuppressInstrumentationScope +OpenTelemetry.SuppressInstrumentationScope.Dispose() -> void +OpenTelemetry.Trace.AlwaysOffSampler +OpenTelemetry.Trace.AlwaysOffSampler.AlwaysOffSampler() -> void +OpenTelemetry.Trace.AlwaysOnSampler +OpenTelemetry.Trace.AlwaysOnSampler.AlwaysOnSampler() -> void +OpenTelemetry.Trace.BatchExportActivityProcessorOptions +OpenTelemetry.Trace.BatchExportActivityProcessorOptions.BatchExportActivityProcessorOptions() -> void +OpenTelemetry.Trace.ParentBasedSampler +OpenTelemetry.Trace.Sampler +OpenTelemetry.Trace.Sampler.Sampler() -> void +OpenTelemetry.Trace.SamplingDecision +OpenTelemetry.Trace.SamplingDecision.Drop = 0 -> OpenTelemetry.Trace.SamplingDecision +OpenTelemetry.Trace.SamplingDecision.RecordAndSample = 2 -> OpenTelemetry.Trace.SamplingDecision +OpenTelemetry.Trace.SamplingDecision.RecordOnly = 1 -> OpenTelemetry.Trace.SamplingDecision +OpenTelemetry.Trace.SamplingParameters +OpenTelemetry.Trace.SamplingParameters.Kind.get -> System.Diagnostics.ActivityKind +OpenTelemetry.Trace.SamplingParameters.ParentContext.get -> System.Diagnostics.ActivityContext +OpenTelemetry.Trace.SamplingParameters.SamplingParameters() -> void +OpenTelemetry.Trace.SamplingParameters.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Trace.SamplingResult +OpenTelemetry.Trace.SamplingResult.Decision.get -> OpenTelemetry.Trace.SamplingDecision +OpenTelemetry.Trace.SamplingResult.Equals(OpenTelemetry.Trace.SamplingResult other) -> bool +OpenTelemetry.Trace.SamplingResult.SamplingResult() -> void +OpenTelemetry.Trace.SamplingResult.SamplingResult(bool isSampled) -> void +OpenTelemetry.Trace.SamplingResult.SamplingResult(OpenTelemetry.Trace.SamplingDecision decision) -> void +OpenTelemetry.Trace.TraceIdRatioBasedSampler +OpenTelemetry.Trace.TraceIdRatioBasedSampler.TraceIdRatioBasedSampler(double probability) -> void +OpenTelemetry.Trace.TracerProviderBuilderBase +OpenTelemetry.Trace.TracerProviderBuilderBase.TracerProviderBuilderBase() -> void +OpenTelemetry.Trace.TracerProviderBuilderExtensions +OpenTelemetry.Trace.TracerProviderExtensions +override OpenTelemetry.BaseExportProcessor.Dispose(bool disposing) -> void +override OpenTelemetry.BaseExportProcessor.OnForceFlush(int timeoutMilliseconds) -> bool +override OpenTelemetry.BaseExportProcessor.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.BatchExportProcessor.Dispose(bool disposing) -> void +override OpenTelemetry.BatchExportProcessor.OnForceFlush(int timeoutMilliseconds) -> bool +override OpenTelemetry.BatchExportProcessor.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.BatchLogRecordExportProcessor.OnEnd(OpenTelemetry.Logs.LogRecord! data) -> void +override OpenTelemetry.CompositeProcessor.Dispose(bool disposing) -> void +override OpenTelemetry.CompositeProcessor.OnEnd(T data) -> void +override OpenTelemetry.CompositeProcessor.OnForceFlush(int timeoutMilliseconds) -> bool +override OpenTelemetry.CompositeProcessor.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.CompositeProcessor.OnStart(T data) -> void +override OpenTelemetry.Logs.OpenTelemetryLoggerProvider.Dispose(bool disposing) -> void +override OpenTelemetry.Metrics.BaseExportingMetricReader.Dispose(bool disposing) -> void +override OpenTelemetry.Metrics.BaseExportingMetricReader.OnCollect(int timeoutMilliseconds) -> bool +override OpenTelemetry.Metrics.BaseExportingMetricReader.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.Metrics.PeriodicExportingMetricReader.Dispose(bool disposing) -> void +override OpenTelemetry.Metrics.PeriodicExportingMetricReader.OnShutdown(int timeoutMilliseconds) -> bool +override OpenTelemetry.Trace.AlwaysOffSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult +override OpenTelemetry.Trace.AlwaysOnSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult +override OpenTelemetry.Trace.ParentBasedSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult +override OpenTelemetry.Trace.SamplingResult.GetHashCode() -> int +override OpenTelemetry.Trace.TraceIdRatioBasedSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static OpenTelemetry.Metrics.MetricTypeExtensions.IsDouble(this OpenTelemetry.Metrics.MetricType self) -> bool +static OpenTelemetry.Metrics.MetricTypeExtensions.IsGauge(this OpenTelemetry.Metrics.MetricType self) -> bool +static OpenTelemetry.Metrics.MetricTypeExtensions.IsHistogram(this OpenTelemetry.Metrics.MetricType self) -> bool +static OpenTelemetry.Metrics.MetricTypeExtensions.IsLong(this OpenTelemetry.Metrics.MetricType self) -> bool +static OpenTelemetry.Metrics.MetricTypeExtensions.IsSum(this OpenTelemetry.Metrics.MetricType self) -> bool +static OpenTelemetry.Sdk.SuppressInstrumentation.get -> bool +static OpenTelemetry.SuppressInstrumentationScope.Enter() -> int +static OpenTelemetry.Trace.SamplingResult.operator !=(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool +static OpenTelemetry.Trace.SamplingResult.operator ==(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool +virtual OpenTelemetry.BaseExporter.Dispose(bool disposing) -> void +virtual OpenTelemetry.BaseExporter.OnForceFlush(int timeoutMilliseconds) -> bool +virtual OpenTelemetry.BaseExporter.OnShutdown(int timeoutMilliseconds) -> bool +virtual OpenTelemetry.BaseProcessor.Dispose(bool disposing) -> void +virtual OpenTelemetry.BaseProcessor.OnEnd(T data) -> void +virtual OpenTelemetry.BaseProcessor.OnForceFlush(int timeoutMilliseconds) -> bool +virtual OpenTelemetry.BaseProcessor.OnShutdown(int timeoutMilliseconds) -> bool +virtual OpenTelemetry.BaseProcessor.OnStart(T data) -> void +virtual OpenTelemetry.Metrics.MetricReader.Dispose(bool disposing) -> void +virtual OpenTelemetry.Metrics.MetricReader.OnCollect(int timeoutMilliseconds) -> bool +virtual OpenTelemetry.Metrics.MetricReader.OnShutdown(int timeoutMilliseconds) -> bool diff --git a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..a045a0ffb --- /dev/null +++ b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt @@ -0,0 +1,20 @@ +OpenTelemetry.Logs.LogRecord.CategoryName.set -> void +OpenTelemetry.Logs.LogRecord.EventId.set -> void +OpenTelemetry.Logs.LogRecord.Exception.set -> void +OpenTelemetry.Logs.LogRecord.LogLevel.set -> void +OpenTelemetry.Logs.LogRecord.SpanId.set -> void +OpenTelemetry.Logs.LogRecord.Timestamp.set -> void +OpenTelemetry.Logs.LogRecord.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecord.TraceId.set -> void +OpenTelemetry.Logs.LogRecord.TraceState.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ConfigureResource(System.Action! configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider() -> void +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void +*REMOVED*static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, bool disposeProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! +~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry/AssemblyInfo.cs b/src/OpenTelemetry/AssemblyInfo.cs index 545b35a13..e74dcecc3 100644 --- a/src/OpenTelemetry/AssemblyInfo.cs +++ b/src/OpenTelemetry/AssemblyInfo.cs @@ -18,8 +18,13 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("OpenTelemetry.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.InMemory" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.EventSource" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Shared" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.AspNetCore" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.HttpListener" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Serilog" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] [assembly: InternalsVisibleTo("Benchmarks" + AssemblyInfo.PublicKey)] diff --git a/src/OpenTelemetry/BaseExportProcessor.cs b/src/OpenTelemetry/BaseExportProcessor.cs index 4bddbeff4..7b1b1bbfa 100644 --- a/src/OpenTelemetry/BaseExportProcessor.cs +++ b/src/OpenTelemetry/BaseExportProcessor.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System; using OpenTelemetry.Internal; diff --git a/src/OpenTelemetry/BaseExporter.cs b/src/OpenTelemetry/BaseExporter.cs index f188b0728..cb1d70d84 100644 --- a/src/OpenTelemetry/BaseExporter.cs +++ b/src/OpenTelemetry/BaseExporter.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System; using System.Threading; using OpenTelemetry.Internal; @@ -48,7 +50,7 @@ namespace OpenTelemetry /// /// Gets the parent . /// - public BaseProvider ParentProvider { get; internal set; } + public BaseProvider? ParentProvider { get; internal set; } /// /// Exports a batch of telemetry objects. diff --git a/src/OpenTelemetry/BaseProcessor.cs b/src/OpenTelemetry/BaseProcessor.cs index 4115b108e..02df50e39 100644 --- a/src/OpenTelemetry/BaseProcessor.cs +++ b/src/OpenTelemetry/BaseProcessor.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System; using System.Threading; using OpenTelemetry.Internal; @@ -26,12 +28,21 @@ namespace OpenTelemetry /// The type of object to be processed. public abstract class BaseProcessor : IDisposable { + private readonly string typeName; private int shutdownCount; + /// + /// Initializes a new instance of the class. + /// + public BaseProcessor() + { + this.typeName = this.GetType().Name; + } + /// /// Gets the parent . /// - public BaseProvider ParentProvider { get; private set; } + public BaseProvider? ParentProvider { get; private set; } /// /// Called synchronously when a telemetry object is started. @@ -86,7 +97,11 @@ namespace OpenTelemetry try { - return this.OnForceFlush(timeoutMilliseconds); + bool result = this.OnForceFlush(timeoutMilliseconds); + + OpenTelemetrySdkEventSource.Log.ProcessorForceFlushInvoked(this.typeName, result); + + return result; } catch (Exception ex) { diff --git a/src/OpenTelemetry/Batch.cs b/src/OpenTelemetry/Batch.cs index df54bb811..8ec2b9dc2 100644 --- a/src/OpenTelemetry/Batch.cs +++ b/src/OpenTelemetry/Batch.cs @@ -14,11 +14,15 @@ // limitations under the License. // +#nullable enable + using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; +using OpenTelemetry.Logs; namespace OpenTelemetry { @@ -29,9 +33,9 @@ namespace OpenTelemetry public readonly struct Batch : IDisposable where T : class { - private readonly T item; - private readonly CircularBuffer circularBuffer; - private readonly T[] items; + private readonly T? item = null; + private readonly CircularBuffer? circularBuffer = null; + private readonly T[]? items = null; private readonly long targetCount; /// @@ -44,8 +48,6 @@ namespace OpenTelemetry Guard.ThrowIfNull(items); Guard.ThrowIfOutOfRange(count, min: 0, max: items.Length); - this.item = null; - this.circularBuffer = null; this.items = items; this.Count = this.targetCount = count; } @@ -55,8 +57,6 @@ namespace OpenTelemetry Debug.Assert(item != null, $"{nameof(item)} was null."); this.item = item; - this.circularBuffer = null; - this.items = null; this.Count = this.targetCount = 1; } @@ -65,10 +65,8 @@ namespace OpenTelemetry Debug.Assert(maxSize > 0, $"{nameof(maxSize)} should be a positive number."); Debug.Assert(circularBuffer != null, $"{nameof(circularBuffer)} was null."); - this.item = null; - this.items = null; this.circularBuffer = circularBuffer; - this.Count = Math.Min(maxSize, circularBuffer.Count); + this.Count = Math.Min(maxSize, circularBuffer!.Count); this.targetCount = circularBuffer.RemovedCount + this.Count; } @@ -87,7 +85,11 @@ namespace OpenTelemetry // Drain anything left in the batch. while (this.circularBuffer.RemovedCount < this.targetCount) { - this.circularBuffer.Read(); + T item = this.circularBuffer.Read(); + if (typeof(T) == typeof(LogRecord)) + { + LogRecordSharedPool.Current.Return((LogRecord)(object)item); + } } } } @@ -115,7 +117,7 @@ namespace OpenTelemetry { if (enumerator.targetCount >= 0) { - enumerator.Current = null; + enumerator.current = null; return false; } @@ -127,13 +129,39 @@ namespace OpenTelemetry { var circularBuffer = enumerator.circularBuffer; - if (circularBuffer.RemovedCount < enumerator.targetCount) + if (circularBuffer!.RemovedCount < enumerator.targetCount) { - enumerator.Current = circularBuffer.Read(); + enumerator.current = circularBuffer.Read(); return true; } - enumerator.Current = null; + enumerator.current = null; + return false; + }; + + private static readonly BatchEnumeratorMoveNextFunc MoveNextCircularBufferLogRecord = (ref Enumerator enumerator) => + { + // Note: This type check here is to give the JIT a hint it can + // remove all of this code when T != LogRecord + if (typeof(T) == typeof(LogRecord)) + { + var circularBuffer = enumerator.circularBuffer; + + var currentItem = enumerator.Current; + if (currentItem != null) + { + LogRecordSharedPool.Current.Return((LogRecord)(object)currentItem); + } + + if (circularBuffer!.RemovedCount < enumerator.targetCount) + { + enumerator.current = circularBuffer.Read(); + return true; + } + + enumerator.current = null; + } + return false; }; @@ -143,23 +171,25 @@ namespace OpenTelemetry if (enumerator.itemIndex < enumerator.targetCount) { - enumerator.Current = items[enumerator.itemIndex++]; + enumerator.current = items![enumerator.itemIndex++]; return true; } - enumerator.Current = null; + enumerator.current = null; return false; }; - private readonly CircularBuffer circularBuffer; - private readonly T[] items; + private readonly CircularBuffer? circularBuffer; + private readonly T[]? items; private readonly BatchEnumeratorMoveNextFunc moveNextFunc; private long targetCount; private int itemIndex; + [AllowNull] + private T current; internal Enumerator(T item) { - this.Current = item; + this.current = item; this.circularBuffer = null; this.items = null; this.targetCount = -1; @@ -169,17 +199,17 @@ namespace OpenTelemetry internal Enumerator(CircularBuffer circularBuffer, long targetCount) { - this.Current = null; + this.current = null; this.items = null; this.circularBuffer = circularBuffer; this.targetCount = targetCount; this.itemIndex = 0; - this.moveNextFunc = MoveNextCircularBuffer; + this.moveNextFunc = typeof(T) == typeof(LogRecord) ? MoveNextCircularBufferLogRecord : MoveNextCircularBuffer; } internal Enumerator(T[] items, long targetCount) { - this.Current = null; + this.current = null; this.circularBuffer = null; this.items = items; this.targetCount = targetCount; @@ -188,14 +218,23 @@ namespace OpenTelemetry } /// - public T Current { get; private set; } + public readonly T Current => this.current; /// - object IEnumerator.Current => this.Current; + readonly object? IEnumerator.Current => this.current; /// public void Dispose() { + if (typeof(T) == typeof(LogRecord)) + { + var currentItem = this.current; + if (currentItem != null) + { + LogRecordSharedPool.Current.Return((LogRecord)(object)currentItem); + this.current = null; + } + } } /// @@ -205,7 +244,7 @@ namespace OpenTelemetry } /// - public void Reset() + public readonly void Reset() => throw new NotSupportedException(); } } diff --git a/src/OpenTelemetry/BatchExportProcessor.cs b/src/OpenTelemetry/BatchExportProcessor.cs index 943acf10a..5fdc2c889 100644 --- a/src/OpenTelemetry/BatchExportProcessor.cs +++ b/src/OpenTelemetry/BatchExportProcessor.cs @@ -14,8 +14,11 @@ // limitations under the License. // +#nullable enable + using System; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Threading; using OpenTelemetry.Internal; @@ -93,8 +96,8 @@ namespace OpenTelemetry /// internal long ProcessedCount => this.circularBuffer.RemovedCount; - /// - protected override void OnExport(T data) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryExport(T data) { if (this.circularBuffer.TryAdd(data, maxSpinCount: 50000)) { @@ -109,11 +112,19 @@ namespace OpenTelemetry } } - return; // enqueue succeeded + return true; // enqueue succeeded } // either the queue is full or exceeded the spin limit, drop the item on the floor Interlocked.Increment(ref this.droppedCount); + + return false; + } + + /// + protected override void OnExport(T data) + { + this.TryExport(data); } /// diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 9e5ed04df..0d17ce51a 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -2,14 +2,34 @@ ## Unreleased +## 1.4.0-alpha.1 + +Released 2022-Aug-02 + * `TracerProviderSDK` modified for spans with remote parent. For such spans activity will be created irrespective of SamplingResult, to maintain context propagation. ([#3329](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3329)) - * Fix issue where a measurement would be dropped when recording it with a null-valued tag. ([#3325](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3325)) +* `CompositeProcessor` will now ensure `ParentProvider` is set on its children + ([#3368](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3368)) +* Added `ForceFlush` and helper ctors on `OpenTelemetryLoggerProvider` + ([#3364](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3364)) +* `Timestamp`, `TraceId`, `SpanId`, `TraceFlags`, `TraceState`, `CategoryName`, + `LogLevel`, `EventId`, & `Exception` properties on `LogRecord` now expose + `set` methods + ([#3378](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3378)) +* Handle possible exception when initializing the default service name. + ([#3405](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3405)) +* `LogRecord` instances are now reused to reduce memory pressure + ([#3385](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3385)) +* Fix exact match of activity source name when `wildcard` is used. + ([#3446](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3446)) +* Added AddOpenTelemetry `ILoggingBuilder` extensions which accept + `OpenTelemetryLoggerProvider` directly + ([#3489](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3489)) ## 1.3.0 @@ -22,8 +42,12 @@ Released 2022-June-1 * Fix null reference exception when a metric view does not match an instrument. ([#3285](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3285)) * Swallow `ObjectDisposedException` in `BatchExportProcessor` and -* `PeriodicExportingMetricReader`. + `PeriodicExportingMetricReader`. ([#3291](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3291)) +* Add `ConfigureResource` which can replace SetResourceBuilder more succinctly + in most cases and has greater flexibility (applies to + TracerProvicerBuilder, MeterProviderBuilder, OpenTelemetryLoggingOptions). + ([#3307](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3307)) ## 1.3.0-beta.2 @@ -39,7 +63,6 @@ Released 2022-Apr-15 * Removes .NET Framework 4.6.1. The minimum .NET Framework version supported is .NET 4.6.2. ([#3190](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3190)) - * Bumped minimum required version of `Microsoft.Extensions.Logging` and `Microsoft.Extensions.Logging.Configuration` to 3.1.0 ([#2582](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3196)) @@ -68,13 +91,11 @@ Released 2022-Apr-12 [DiagnosticSource version 7.0 onwards](https://www.nuget.org/packages/System.Diagnostics.DiagnosticSource/7.0.0-preview.2.22152.2), they will be aggregated using `AggregationTemporality.Cumulative`. ([#3153](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3153)) - * Fix issue where `ExplicitBucketHistogramConfiguration` could be used to configure metric streams for instruments that are not histograms. Currently, it is not possible to change the aggregation of an instrument with views. This may be possible in the future. ([#3126](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3126)) - * Conformed to the specification to ensure that each view that an instrument matches results in a new metric stream. With this change it is possible for views to introduce conflicting metric streams. Any conflicts encountered will @@ -89,15 +110,12 @@ Released 2022-Mar-30 `ExportIntervalMilliseconds` of `-1` indicating an infinite export interval period. ([#2982](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2982)) - * Fix bug where multiple views selecting a single instrument can result in duplicate updates to a single metric point. ([#3006](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3006)) - * Added the `PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds` option. ([#3038](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3038)) - * Removed `MetricReaderType`. This enumeration was previously used when configuring a metric reader with an exporter to configure whether the export cycle would be periodic or manual (i.e., requiring a explicit call to flush @@ -106,11 +124,9 @@ Released 2022-Mar-30 by setting `PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds` to `-1`. ([#3038](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3038)) - * Marked members of the `MetricPoint` `struct` which do not mutate state as `readonly` ([#3065](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3065)) - * [Bug fix] OpenTelemetryLoggerProvider is now unaffected by changes to OpenTelemetryLoggerOptions after the LoggerFactory is built. ([#3055](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3055)) @@ -120,24 +136,18 @@ Released 2022-Mar-30 Released 2022-Mar-04 * Instantiating multiple metric instruments with the same name and also - identical in all other respects - same type, description, and unit - result - in a single metric stream aggregating measurements from all the identical - instruments. - - Instantiating multiple metric instruments with the same name but differ in - some respect - different type, description, or unit - will result in a - separate metric stream for each distinct instrument. - + identical in all other respects - same type, description, and unit - result in + a single metric stream aggregating measurements from all the identical + instruments. Instantiating multiple metric instruments with the same name but + differ in some respect - different type, description, or unit - will result in + a separate metric stream for each distinct instrument. ([#2916](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2916)) - * The `Meter` property on `OpenTelemetry.Metrics.Metric` has been removed. It now has `MeterName` and `MeterVersion` properties. ([#2916](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2916)) - * Added support for implementing custom `ResourceDetector`. ([#2949](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2949/) [#2897](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2897)) - * Perf improvement for Histogram and HistogramSumCount by implementing lock-free updates. ([#2951](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2951) @@ -149,14 +159,11 @@ Released 2022-Feb-02 * Make `MetricPoint` of `MetricPointAccessor` readonly. ([#2736](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2736)) - * Fail-fast when using AddView with guaranteed conflict. ([#2751](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2751)) - * Swallow `ObjectDisposedException` from the `BatchExportProcessor` worker thread. ([#2844](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2844)) - * Performance improvement: when emitting metrics, users are strongly advised to provide tags with same Key order, to achieve maximum performance. ([#2805](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2805)) @@ -168,34 +175,27 @@ Released 2021-Nov-29 * Prevent accessing activity Id before sampler runs in case of legacy activities. ([#2659](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2659)) - * Added `ReadOnlyTagCollection` and expose `Tags` on `MetricPoint` instead of `Keys`+`Values` ([#2642](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2642)) - * Refactored `MetricPoint` and added public methods: `GetBucketCounts`, `GetExplicitBounds`, `GetHistogramCount`, and `GetHistogramSum` ([#2657](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2657)) - * Remove MetricStreamConfiguration.Aggregation, as the feature to customize aggregation is not implemented yet. ([#2660](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2660)) - * Removed the public property `HistogramMeasurements` and added a public method `GetHistogramBuckets` instead. Renamed the class `HistogramMeasurements` to `HistogramBuckets` and added an enumerator of type `HistogramBucket` for enumerating `BucketCounts` and `ExplicitBounds`. Removed `GetBucketCounts` and `GetExplicitBounds` methods from `MetricPoint`. ([#2664](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2664)) - * Refactored temporality setting to align with the latest spec. ([#2666](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2666)) - * Removed the public properties `LongValue`, `DoubleValue`, in favor of their counterpart public methods `GetSumLong`, `GetSumDouble`, `GetGaugeLastValueLong`, `GetGaugeLastValueDouble`. ([#2667](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2667)) - * MetricType modified to reserve bits for future types. ([#2693](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2693)) @@ -206,32 +206,23 @@ Released 2021-Nov-19 * Renamed `HistogramConfiguration` to `ExplicitBucketHistogramConfiguration` and changed its member `BucketBounds` to `Boundaries`. ([#2638](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2638)) - * Metrics with the same name but from different meters are allowed. ([#2634](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2634)) - * Metrics SDK will not provide inactive Metrics to delta exporter. ([#2629](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2629)) - * Histogram bounds are validated when added to a View. ([#2573](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2573)) - * Changed `BatchExportActivityProcessorOptions` constructor to throw `FormatException` if it fails to parse any of the supported environment variables. - * Added `BaseExporter.ForceFlush`. ([#2525](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2525)) - * Exposed public `Batch(T[] items, int count)` constructor on `Batch` struct ([#2542](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2542)) - * Added wildcard support for AddMeter. ([#2459](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2459)) - * Add support for multiple Metric readers ([#2596](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2596)) - * Add ability to configure MaxMetricStreams, MaxMetricPointsPerMetricStream ([#2635](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2635)) @@ -241,12 +232,9 @@ Released 2021-Oct-08 * Exception from Observable instrument callbacks does not result in entire metrics being lost. - * SDK is allocation-free on recording of measurements with up to 8 tags. - * TracerProviderBuilder.AddLegacySource now supports wildcard activity names. ([#2183](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2183)) - * Instrument and View names are validated [according with the spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument). ([#2470](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2470)) @@ -270,7 +258,6 @@ Released 2021-Sep-13 * Metrics perf improvements, bug fixes. Replace MetricProcessor with MetricReader. ([#2306](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2306)) - * Add `BatchExportActivityProcessorOptions` which supports field value overriding using `OTEL_BSP_SCHEDULE_DELAY`, `OTEL_BSP_EXPORT_TIMEOUT`, `OTEL_BSP_MAX_QUEUE_SIZE`, `OTEL_BSP_MAX_EXPORT_BATCH_SIZE` environmental @@ -284,19 +271,15 @@ Released 2021-Aug-24 * More Metrics features. All instrument types, push/pull exporters, Delta/Cumulative temporality supported. - * `ResourceBuilder.CreateDefault` has detectors for `OTEL_RESOURCE_ATTRIBUTES`, `OTEL_SERVICE_NAME` environment variables so that explicit `AddEnvironmentVariableDetector` call is not needed. ([#2247](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2247)) - * `ResourceBuilder.AddEnvironmentVariableDetector` handles `OTEL_SERVICE_NAME` environmental variable. ([#2209](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2209)) - * Removes upper constraint for Microsoft.Extensions.Logging dependencies. ([#2179](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2179)) - * OpenTelemetryLogger modified to not throw, when the formatter supplied in ILogger.Log call is null. ([#2200](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2200)) @@ -308,7 +291,6 @@ Released 2021-Jul-23 * Add basic Metrics support with a single pipeline, and supporting Counter (sync) instrument. Push and Pull exporters are supported. ([#2174](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2174)) - * Removes .NET Framework 4.5.2, .NET 4.6 support. The minimum .NET Framework version supported is .NET 4.6.1. ([#2138](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2138)) @@ -335,7 +317,6 @@ Released 2021-May-11 * `AddLegacySource()` moved out of `TracerProviderBuilderExtensions` and into public API ([#2019](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2019)) - * Fixed an issue causing inconsistent log scopes when using `BatchLogRecordExportProcessor`. To make parsing scopes easier the `LogRecord.ForEachScope` signature has been changed to receive instances of @@ -358,47 +339,37 @@ Released 2021-Mar-19 * Removed SuppressScope Increment/Decrement from DiagnosticSourceListeners. ([1893](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1893)) - * Added `TracerProviderBuilder.SetErrorStatusOnException` which automatically sets the activity status to `Error` when exception happened. ([#1858](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1858) [#1875](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1875)) - * Added `ForceFlush` to `TracerProvider`. ([#1837](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1837)) - * Added a TracerProviderBuilder extension method called `AddLegacySource` which is used by instrumentation libraries that use DiagnosticSource to get activities processed without ActivitySourceAdapter. [#1836](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1836) [#1860](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1860) - * Added new constructor with optional parameters to allow customization of `ParentBasedSampler` behavior. ([#1727](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1727)) - * The application base directory is now tested after the current directory when searching for the [self diagnostic configuration file](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry/README.md#troubleshooting). ([#1865](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1865)) - * Resource Attributes now accept primitive arrays as values. ([#1852](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1852)) - * Fixed [#1846](https://github.com/open-telemetry/opentelemetry-dotnet/issues/1846): `ParentBasedSampler` will no longer explicitly consider Activity links. ([#1851](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1851)) - * Added `IncludeScopes`, `IncludeFormattedMessage`, & `ParseStateValues` on `OpenTelemetryLoggerOptions`. Added `FormattedMessage`, `StateValues`, & `ForEachScope` on `LogRecord`. - ([#1869](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1869) & + ([#1869](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1869) [#1883](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1883)) - * Added `SetResourceBuilder` support to `OpenTelemetryLoggerOptions`. ([#1913](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1913)) - * Added `IDeferredTracerProviderBuilder` and `TracerProviderBuilderBase` to support dependency injection through OpenTelemetry.Extensions.Hosting. ([#1889](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1889)) diff --git a/src/OpenTelemetry/CompositeProcessor.cs b/src/OpenTelemetry/CompositeProcessor.cs index 49c2258bd..38a2babf9 100644 --- a/src/OpenTelemetry/CompositeProcessor.cs +++ b/src/OpenTelemetry/CompositeProcessor.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System; using System.Collections.Generic; using System.Diagnostics; @@ -24,7 +26,7 @@ namespace OpenTelemetry { public class CompositeProcessor : BaseProcessor { - private readonly DoublyLinkedListNode head; + internal readonly DoublyLinkedListNode Head; private DoublyLinkedListNode tail; private bool disposed; @@ -38,8 +40,8 @@ namespace OpenTelemetry throw new ArgumentException($"'{iter}' is null or empty", nameof(iter)); } - this.head = new DoublyLinkedListNode(iter.Current); - this.tail = this.head; + this.Head = new DoublyLinkedListNode(iter.Current); + this.tail = this.Head; while (iter.MoveNext()) { @@ -64,7 +66,7 @@ namespace OpenTelemetry /// public override void OnEnd(T data) { - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { cur.Value.OnEnd(data); } @@ -73,12 +75,22 @@ namespace OpenTelemetry /// public override void OnStart(T data) { - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { cur.Value.OnStart(data); } } + internal override void SetParentProvider(BaseProvider parentProvider) + { + base.SetParentProvider(parentProvider); + + for (var cur = this.Head; cur != null; cur = cur.Next) + { + cur.Value.SetParentProvider(parentProvider); + } + } + /// protected override bool OnForceFlush(int timeoutMilliseconds) { @@ -87,7 +99,7 @@ namespace OpenTelemetry ? null : Stopwatch.StartNew(); - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { if (sw == null) { @@ -113,7 +125,7 @@ namespace OpenTelemetry ? null : Stopwatch.StartNew(); - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { if (sw == null) { @@ -138,7 +150,7 @@ namespace OpenTelemetry { if (disposing) { - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { try { @@ -157,7 +169,7 @@ namespace OpenTelemetry base.Dispose(disposing); } - private class DoublyLinkedListNode + internal sealed class DoublyLinkedListNode { public readonly BaseProcessor Value; @@ -166,9 +178,9 @@ namespace OpenTelemetry this.Value = value; } - public DoublyLinkedListNode Previous { get; set; } + public DoublyLinkedListNode? Previous { get; set; } - public DoublyLinkedListNode Next { get; set; } + public DoublyLinkedListNode? Next { get; set; } } } } diff --git a/src/OpenTelemetry/DiagnosticSourceInstrumentation/PropertyFetcher.cs b/src/OpenTelemetry/DiagnosticSourceInstrumentation/PropertyFetcher.cs index dc0e1028f..d451e9b8d 100644 --- a/src/OpenTelemetry/DiagnosticSourceInstrumentation/PropertyFetcher.cs +++ b/src/OpenTelemetry/DiagnosticSourceInstrumentation/PropertyFetcher.cs @@ -84,7 +84,7 @@ namespace OpenTelemetry.Instrumentation { public static PropertyFetch Create(TypeInfo type, string propertyName) { - var property = type.DeclaredProperties.FirstOrDefault(p => string.Equals(p.Name, propertyName, StringComparison.InvariantCultureIgnoreCase)); + var property = type.DeclaredProperties.FirstOrDefault(p => string.Equals(p.Name, propertyName, StringComparison.OrdinalIgnoreCase)); if (property == null) { property = type.GetProperty(propertyName); diff --git a/src/OpenTelemetry/Internal/CircularBuffer.cs b/src/OpenTelemetry/Internal/CircularBuffer.cs index 16614d002..069ff70cf 100644 --- a/src/OpenTelemetry/Internal/CircularBuffer.cs +++ b/src/OpenTelemetry/Internal/CircularBuffer.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System.Runtime.CompilerServices; using System.Threading; @@ -23,10 +25,10 @@ namespace OpenTelemetry.Internal /// Lock-free implementation of single-reader multi-writer circular buffer. /// /// The type of the underlying value. - internal class CircularBuffer + internal sealed class CircularBuffer where T : class { - private readonly T[] trait; + private readonly T?[] trait; private long head; private long tail; @@ -54,20 +56,20 @@ namespace OpenTelemetry.Internal { get { - var tailSnapshot = this.tail; - return (int)(this.head - tailSnapshot); + var tailSnapshot = Volatile.Read(ref this.tail); + return (int)(Volatile.Read(ref this.head) - tailSnapshot); } } /// /// Gets the number of items added to the . /// - public long AddedCount => this.head; + public long AddedCount => Volatile.Read(ref this.head); /// /// Gets the number of items removed from the . /// - public long RemovedCount => this.tail; + public long RemovedCount => Volatile.Read(ref this.tail); /// /// Adds the specified item to the buffer. @@ -83,22 +85,21 @@ namespace OpenTelemetry.Internal while (true) { - var tailSnapshot = this.tail; - var headSnapshot = this.head; + var tailSnapshot = Volatile.Read(ref this.tail); + var headSnapshot = Volatile.Read(ref this.head); if (headSnapshot - tailSnapshot >= this.Capacity) { return false; // buffer is full } - var head = Interlocked.CompareExchange(ref this.head, headSnapshot + 1, headSnapshot); - if (head != headSnapshot) + if (Interlocked.CompareExchange(ref this.head, headSnapshot + 1, headSnapshot) != headSnapshot) { continue; } - var index = (int)(head % this.Capacity); - this.trait[index] = value; + Volatile.Write(ref this.trait[headSnapshot % this.Capacity], value); + return true; } } @@ -125,16 +126,15 @@ namespace OpenTelemetry.Internal while (true) { - var tailSnapshot = this.tail; - var headSnapshot = this.head; + var tailSnapshot = Volatile.Read(ref this.tail); + var headSnapshot = Volatile.Read(ref this.head); if (headSnapshot - tailSnapshot >= this.Capacity) { return false; // buffer is full } - var head = Interlocked.CompareExchange(ref this.head, headSnapshot + 1, headSnapshot); - if (head != headSnapshot) + if (Interlocked.CompareExchange(ref this.head, headSnapshot + 1, headSnapshot) != headSnapshot) { if (spinCountDown-- == 0) { @@ -144,8 +144,8 @@ namespace OpenTelemetry.Internal continue; } - var index = (int)(head % this.Capacity); - this.trait[index] = value; + Volatile.Write(ref this.trait[headSnapshot % this.Capacity], value); + return true; } } @@ -161,19 +161,19 @@ namespace OpenTelemetry.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public T Read() { - var index = (int)(this.tail % this.Capacity); + var tail = Volatile.Read(ref this.tail); + var index = (int)(tail % this.Capacity); while (true) { - var value = this.trait[index]; - if (value == null) + var previous = Interlocked.Exchange(ref this.trait[index], null); + if (previous == null) { // If we got here it means a writer isn't done. continue; } - this.trait[index] = null; - this.tail++; - return value; + Volatile.Write(ref this.tail, tail + 1); + return previous; } } } diff --git a/src/OpenTelemetry/Internal/EnvironmentVariableHelper.cs b/src/OpenTelemetry/Internal/EnvironmentVariableHelper.cs index df3f89df0..f6ea97835 100644 --- a/src/OpenTelemetry/Internal/EnvironmentVariableHelper.cs +++ b/src/OpenTelemetry/Internal/EnvironmentVariableHelper.cs @@ -111,5 +111,33 @@ namespace OpenTelemetry.Internal return true; } + + /// + /// Reads an environment variable and parses it as a . + /// + /// The name of the environment variable. + /// The parsed value of the environment variable. + /// + /// Returns true when a non-empty value was read; otherwise, false. + /// + /// + /// Thrown when failed to parse the non-empty value. + /// + public static bool LoadBoolean(string envVarKey, out bool result) + { + result = default; + + if (!LoadString(envVarKey, out string value)) + { + return false; + } + + if (!bool.TryParse(value, out result)) + { + throw new FormatException($"{envVarKey} environment variable has an invalid value: '${value}'"); + } + + return true; + } } } diff --git a/src/OpenTelemetry/Internal/MathHelper.cs b/src/OpenTelemetry/Internal/MathHelper.cs new file mode 100644 index 000000000..a3ffa316e --- /dev/null +++ b/src/OpenTelemetry/Internal/MathHelper.cs @@ -0,0 +1,138 @@ +// +// 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.Runtime.CompilerServices; + +namespace OpenTelemetry.Internal; + +internal static class MathHelper +{ + // https://en.wikipedia.org/wiki/Leading_zero + private static readonly byte[] LeadingZeroLookupTable = new byte[] + { + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeadingZero8(byte value) + { + return LeadingZeroLookupTable[value]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeadingZero16(short value) + { + unchecked + { + var high8 = (byte)(value >> 8); + + if (high8 != 0) + { + return LeadingZero8(high8); + } + + return LeadingZero8((byte)value) + 8; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeadingZero32(int value) + { + unchecked + { + var high16 = (short)(value >> 16); + + if (high16 != 0) + { + return LeadingZero16(high16); + } + + return LeadingZero16((short)value) + 16; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeadingZero64(long value) + { + unchecked + { + var high32 = (int)(value >> 32); + + if (high32 != 0) + { + return LeadingZero32(high32); + } + + return LeadingZero32((int)value) + 32; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int PositiveModulo32(int value, int divisor) + { + Debug.Assert(divisor > 0, $"{nameof(divisor)} must be a positive integer."); + + value %= divisor; + + if (value < 0) + { + value += divisor; + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long PositiveModulo64(long value, long divisor) + { + Debug.Assert(divisor > 0, $"{nameof(divisor)} must be a positive integer."); + + value %= divisor; + + if (value < 0) + { + value += divisor; + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFinite(double value) + { +#if NETCOREAPP3_1_OR_GREATER + return double.IsFinite(value); +#else + return !double.IsInfinity(value) && !double.IsNaN(value); +#endif + } +} diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs index 0db72d4cb..1dcd65fd9 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -390,6 +390,12 @@ namespace OpenTelemetry.Internal this.WriteEvent(42, type.ToString(), key); } + [Event(43, Message = "ForceFlush invoked for processor type '{0}' returned result '{1}'.", Level = EventLevel.Verbose)] + public void ProcessorForceFlushInvoked(string processorType, bool result) + { + this.WriteEvent(43, processorType, result); + } + #if DEBUG public class OpenTelemetryEventListener : EventListener { diff --git a/src/OpenTelemetry/Internal/PeriodicExportingMetricReaderHelper.cs b/src/OpenTelemetry/Internal/PeriodicExportingMetricReaderHelper.cs index 0d04984eb..0fa1fc905 100644 --- a/src/OpenTelemetry/Internal/PeriodicExportingMetricReaderHelper.cs +++ b/src/OpenTelemetry/Internal/PeriodicExportingMetricReaderHelper.cs @@ -14,23 +14,32 @@ // limitations under the License. // +using OpenTelemetry.Internal; + namespace OpenTelemetry.Metrics; internal static class PeriodicExportingMetricReaderHelper { + internal const string OTelMetricExportIntervalEnvVarKey = "OTEL_METRIC_EXPORT_INTERVAL"; + internal const int DefaultExportIntervalMilliseconds = 60000; + internal const string OTelMetricExportTimeoutEnvVarKey = "OTEL_METRIC_EXPORT_TIMEOUT"; + internal const int DefaultExportTimeoutMilliseconds = 30000; + internal static PeriodicExportingMetricReader CreatePeriodicExportingMetricReader( BaseExporter exporter, MetricReaderOptions options, - int defaultExportIntervalMilliseconds, - int defaultExportTimeoutMilliseconds) + int defaultExportIntervalMilliseconds = DefaultExportIntervalMilliseconds, + int defaultExportTimeoutMilliseconds = DefaultExportTimeoutMilliseconds) { - var exportInterval = - options.PeriodicExportingMetricReaderOptions?.ExportIntervalMilliseconds - ?? defaultExportIntervalMilliseconds; + var exportInterval = GetValue( + options.PeriodicExportingMetricReaderOptions?.ExportIntervalMilliseconds, + OTelMetricExportIntervalEnvVarKey, + defaultExportIntervalMilliseconds); - var exportTimeout = - options.PeriodicExportingMetricReaderOptions?.ExportTimeoutMilliseconds - ?? defaultExportTimeoutMilliseconds; + var exportTimeout = GetValue( + options.PeriodicExportingMetricReaderOptions?.ExportTimeoutMilliseconds, + OTelMetricExportTimeoutEnvVarKey, + defaultExportTimeoutMilliseconds); var metricReader = new PeriodicExportingMetricReader(exporter, exportInterval, exportTimeout) { @@ -39,4 +48,19 @@ internal static class PeriodicExportingMetricReaderHelper return metricReader; } + + private static int GetValue(int? optionsValue, string envVarKey, int defaultValue) + { + if (optionsValue.HasValue) + { + return optionsValue.Value; + } + + if (EnvironmentVariableHelper.LoadNumeric(envVarKey, out var envVarValue)) + { + return envVarValue; + } + + return defaultValue; + } } diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/EventSourceTest.cs b/src/OpenTelemetry/Internal/Shims/IsExternalInit.cs similarity index 56% rename from test/OpenTelemetry.Instrumentation.AspNet.Tests/EventSourceTest.cs rename to src/OpenTelemetry/Internal/Shims/IsExternalInit.cs index 521e857bb..de19a976d 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/EventSourceTest.cs +++ b/src/OpenTelemetry/Internal/Shims/IsExternalInit.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,18 +14,12 @@ // limitations under the License. // -using OpenTelemetry.Instrumentation.AspNet.Implementation; -using OpenTelemetry.Tests; -using Xunit; - -namespace OpenTelemetry.Instrumentation.AspNet.Tests +#if NETFRAMEWORK || NETSTANDARD2_0_OR_GREATER +namespace System.Runtime.CompilerServices { - public class EventSourceTest + // This enabled "init" keyword in net462 + netstandard2.0 targets. + internal sealed class IsExternalInit { - [Fact] - public void EventSourceTest_AspNetInstrumentationEventSource() - { - EventSourceTestHelper.MethodsAreImplementedConsistentlyWithTheirAttributes(AspNetInstrumentationEventSource.Log); - } } } +#endif diff --git a/src/OpenTelemetry/Internal/Shims/NullableAttributes.cs b/src/OpenTelemetry/Internal/Shims/NullableAttributes.cs new file mode 100644 index 000000000..78bcfeab9 --- /dev/null +++ b/src/OpenTelemetry/Internal/Shims/NullableAttributes.cs @@ -0,0 +1,51 @@ +// +// 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. +// + +// Source: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs + +#pragma warning disable SA1649 // File name should match first type name +#pragma warning disable SA1402 // File may only contain a single type + +#if NETFRAMEWORK || NETSTANDARD2_0 +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class AllowNullAttribute : Attribute + { + } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class MaybeNullAttribute : Attribute + { + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } +} +#endif diff --git a/src/OpenTelemetry/Internal/TagTransformer.cs b/src/OpenTelemetry/Internal/TagTransformer.cs index e63ccd623..f2a0cee9c 100644 --- a/src/OpenTelemetry/Internal/TagTransformer.cs +++ b/src/OpenTelemetry/Internal/TagTransformer.cs @@ -16,12 +16,13 @@ using System; using System.Collections.Generic; +using System.Linq; namespace OpenTelemetry.Internal; internal abstract class TagTransformer { - public bool TryTransformTag(KeyValuePair tag, out T result) + public bool TryTransformTag(KeyValuePair tag, out T result, int? maxLength = null) { if (tag.Value == null) { @@ -33,7 +34,7 @@ internal abstract class TagTransformer { case char: case string: - result = this.TransformStringTag(tag.Key, Convert.ToString(tag.Value)); + result = this.TransformStringTag(tag.Key, TruncateString(Convert.ToString(tag.Value), maxLength)); break; case bool b: result = this.TransformBooleanTag(tag.Key, b); @@ -54,7 +55,7 @@ internal abstract class TagTransformer case Array array: try { - result = this.TransformArrayTagInternal(tag.Key, array); + result = this.TransformArrayTagInternal(tag.Key, array, maxLength); } catch { @@ -77,7 +78,7 @@ internal abstract class TagTransformer default: try { - result = this.TransformStringTag(tag.Key, tag.Value.ToString()); + result = this.TransformStringTag(tag.Key, TruncateString(tag.Value.ToString(), maxLength)); } catch { @@ -103,13 +104,20 @@ internal abstract class TagTransformer protected abstract T TransformArrayTag(string key, Array array); - private T TransformArrayTagInternal(string key, Array array) + private static string TruncateString(string value, int? maxLength) + { + return maxLength.HasValue && value?.Length > maxLength + ? value.Substring(0, maxLength.Value) + : value; + } + + private T TransformArrayTagInternal(string key, Array array, int? maxStringValueLength) { // This switch ensures the values of the resultant array-valued tag are of the same type. return array switch { char[] => this.TransformArrayTag(key, array), - string[] => this.TransformArrayTag(key, array), + string[] => this.ConvertToStringArrayThenTransformArrayTag(key, array, maxStringValueLength), bool[] => this.TransformArrayTag(key, array), byte[] => this.TransformArrayTag(key, array), sbyte[] => this.TransformArrayTag(key, array), @@ -120,17 +128,25 @@ internal abstract class TagTransformer long[] => this.TransformArrayTag(key, array), float[] => this.TransformArrayTag(key, array), double[] => this.TransformArrayTag(key, array), - _ => this.ConvertToStringArrayThenTransformArrayTag(key, array), + _ => this.ConvertToStringArrayThenTransformArrayTag(key, array, maxStringValueLength), }; } - private T ConvertToStringArrayThenTransformArrayTag(string key, Array array) + private T ConvertToStringArrayThenTransformArrayTag(string key, Array array, int? maxStringValueLength) { - var stringArray = new string[array.Length]; + string[] stringArray; - for (var i = 0; i < array.Length; ++i) + if (array is string[] arrayAsStringArray && (!maxStringValueLength.HasValue || !arrayAsStringArray.Any(s => s?.Length > maxStringValueLength))) { - stringArray[i] = array.GetValue(i)?.ToString(); + stringArray = arrayAsStringArray; + } + else + { + stringArray = new string[array.Length]; + for (var i = 0; i < array.Length; ++i) + { + stringArray[i] = TruncateString(array.GetValue(i)?.ToString(), maxStringValueLength); + } } return this.TransformArrayTag(key, stringArray); diff --git a/src/OpenTelemetry/Internal/WildcardHelper.cs b/src/OpenTelemetry/Internal/WildcardHelper.cs index 778d2d883..8432286f4 100644 --- a/src/OpenTelemetry/Internal/WildcardHelper.cs +++ b/src/OpenTelemetry/Internal/WildcardHelper.cs @@ -42,6 +42,6 @@ internal static class WildcardHelper var convertedPattern = string.Join( "|", from p in patterns select "(?:" + Regex.Escape(p).Replace("\\*", ".*").Replace("\\?", ".") + ')'); - return new Regex('^' + convertedPattern + '$', RegexOptions.Compiled | RegexOptions.IgnoreCase); + return new Regex("^(?:" + convertedPattern + ")$", RegexOptions.Compiled | RegexOptions.IgnoreCase); } } diff --git a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs index 8eb8cebe5..644f160db 100644 --- a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs +++ b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs @@ -57,9 +57,14 @@ namespace OpenTelemetry // happen here. Debug.Assert(data != null, "LogRecord was null."); - data!.BufferLogScopes(); + data!.Buffer(); - base.OnEnd(data); + data.AddReference(); + + if (!this.TryExport(data)) + { + LogRecordSharedPool.Current.Return(data); + } } } } diff --git a/src/OpenTelemetry/Logs/LogEmitter.cs b/src/OpenTelemetry/Logs/LogEmitter.cs new file mode 100644 index 000000000..49e633604 --- /dev/null +++ b/src/OpenTelemetry/Logs/LogEmitter.cs @@ -0,0 +1,67 @@ +// +// 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. +// + +#nullable enable + +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Logs +{ + /// + /// LogEmitter implementation. + /// + /// + /// Spec reference: LogEmitter. + /// + internal sealed class LogEmitter + { + private readonly OpenTelemetryLoggerProvider loggerProvider; + + internal LogEmitter(OpenTelemetryLoggerProvider loggerProvider) + { + Guard.ThrowIfNull(loggerProvider); + + this.loggerProvider = loggerProvider; + } + + /// + /// Emit a . + /// + /// . + /// . + public void Emit(in LogRecordData data, in LogRecordAttributeList attributes = default) + { + var provider = this.loggerProvider; + var processor = provider.Processor; + if (processor != null) + { + var pool = provider.LogRecordPool; + + var logRecord = pool.Rent(); + + logRecord.Data = data; + + attributes.ApplyToLogRecord(logRecord); + + processor.OnEnd(logRecord); + + // Attempt to return the LogRecord to the pool. This will no-op + // if a batch exporter has added a reference. + pool.Return(logRecord); + } + } + } +} diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index 94722fd4c..61c819939 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -19,6 +19,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; using Microsoft.Extensions.Logging; using OpenTelemetry.Internal; @@ -29,13 +31,22 @@ namespace OpenTelemetry.Logs /// public sealed class LogRecord { + internal LogRecordData Data; + internal List>? AttributeStorage; + internal List? BufferedScopes; + internal int PoolReferenceCount = int.MaxValue; + private static readonly Action> AddScopeToBufferedList = (object? scope, List state) => { state.Add(scope); }; - private List? bufferedScopes; + internal LogRecord() + { + } + // Note: Some users are calling this with reflection. Try not to change the signature to be nice. + [Obsolete("Call LogRecordPool.Rent instead.")] internal LogRecord( IExternalScopeProvider? scopeProvider, DateTime timestamp, @@ -47,71 +58,107 @@ namespace OpenTelemetry.Logs Exception? exception, IReadOnlyList>? stateValues) { - this.ScopeProvider = scopeProvider; - - var activity = Activity.Current; - if (activity != null) + this.Data = new(Activity.Current) { - this.TraceId = activity.TraceId; - this.SpanId = activity.SpanId; - this.TraceState = activity.TraceStateString; - this.TraceFlags = activity.ActivityTraceFlags; - } + TimestampBacking = timestamp, - this.Timestamp = timestamp; - this.CategoryName = categoryName; - this.LogLevel = logLevel; - this.EventId = eventId; - this.FormattedMessage = formattedMessage; - this.State = state; + CategoryName = categoryName, + LogLevel = logLevel, + EventId = eventId, + Message = formattedMessage, + Exception = exception, + }; + + this.ScopeProvider = scopeProvider; this.StateValues = stateValues; - this.Exception = exception; + this.State = state; } /// - /// Gets the log timestamp. + /// Gets or sets the log timestamp. /// - public DateTime Timestamp { get; } + /// + /// Note: If is set to a value with it will be automatically converted to + /// UTC using . + /// + public DateTime Timestamp + { + get => this.Data.Timestamp; + set => this.Data.Timestamp = value; + } /// - /// Gets the log . + /// Gets or sets the log . /// - public ActivityTraceId TraceId { get; } + public ActivityTraceId TraceId + { + get => this.Data.TraceId; + set => this.Data.TraceId = value; + } /// - /// Gets the log . + /// Gets or sets the log . /// - public ActivitySpanId SpanId { get; } + public ActivitySpanId SpanId + { + get => this.Data.SpanId; + set => this.Data.SpanId = value; + } /// - /// Gets the log . + /// Gets or sets the log . /// - public ActivityTraceFlags TraceFlags { get; } + public ActivityTraceFlags TraceFlags + { + get => this.Data.TraceFlags; + set => this.Data.TraceFlags = value; + } /// - /// Gets the log trace state. + /// Gets or sets the log trace state. /// - public string? TraceState { get; } + public string? TraceState + { + get => this.Data.TraceState; + set => this.Data.TraceState = value; + } /// - /// Gets the log category name. + /// Gets or sets the log category name. /// - public string CategoryName { get; } + public string? CategoryName + { + get => this.Data.CategoryName; + set => this.Data.CategoryName = value; + } /// - /// Gets the log . + /// Gets or sets the log . /// - public LogLevel LogLevel { get; } + public LogLevel LogLevel + { + get => this.Data.LogLevel; + set => this.Data.LogLevel = value; + } /// - /// Gets the log . + /// Gets or sets the log . /// - public EventId EventId { get; } + public EventId EventId + { + get => this.Data.EventId; + set => this.Data.EventId = value; + } /// /// Gets or sets the log formatted message. /// - public string? FormattedMessage { get; set; } + public string? FormattedMessage + { + get => this.Data.Message; + set => this.Data.Message = value; + } /// /// Gets or sets the raw state attached to the log. Set to >? StateValues { get; set; } /// - /// Gets the log . + /// Gets or sets the log . /// - public Exception? Exception { get; } + public Exception? Exception + { + get => this.Data.Exception; + set => this.Data.Exception = value; + } internal IExternalScopeProvider? ScopeProvider { get; set; } @@ -139,13 +190,6 @@ namespace OpenTelemetry.Logs /// of creation. All callbacks are guaranteed to be called inline from /// this method. /// - /// - /// Note: Scopes are only available during the lifecycle of the log - /// message being written. If you need to capture scopes to be used - /// later (for example in batching scenarios), call to safely capture the values (incurs - /// allocation). - /// /// State. /// The callback to be executed for every scope object. /// The state object to be passed into the callback. @@ -155,9 +199,9 @@ namespace OpenTelemetry.Logs var forEachScopeState = new ScopeForEachState(callback, state); - if (this.bufferedScopes != null) + if (this.BufferedScopes != null) { - foreach (object? scope in this.bufferedScopes) + foreach (object? scope in this.BufferedScopes) { ScopeForEachState.ForEachScope(scope, forEachScopeState); } @@ -169,21 +213,107 @@ namespace OpenTelemetry.Logs } /// - /// Buffers the scopes attached to the log into a list so that they can - /// be safely processed after the log message lifecycle has ended. + /// Gets a reference to the for the log message. /// - internal void BufferLogScopes() + /// . + internal ref LogRecordData GetDataRef() { - if (this.ScopeProvider == null || this.bufferedScopes != null) + return ref this.Data; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void ResetReferenceCount() + { + this.PoolReferenceCount = 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AddReference() + { + Interlocked.Increment(ref this.PoolReferenceCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal int RemoveReference() + { + return Interlocked.Decrement(ref this.PoolReferenceCount); + } + + // Note: Typically called when LogRecords are added into a batch so they + // can be safely processed outside of the log call chain. + internal void Buffer() + { + // Note: State values are buffered because some states are not safe + // to access outside of the log call chain. See: + // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2905 + this.BufferLogStateValues(); + + this.BufferLogScopes(); + + // Note: There is no buffering of "State" only "StateValues". We + // don't inspect "object State" at all. It is undefined what + // exporters will do with "State". Some might ignore it, some might + // attempt to access it as a list. That is potentially dangerous. + // TODO: Investigate what to do here. Should we obsolete State and + // just use the StateValues design? + } + + internal LogRecord Copy() + { + // Note: We only buffer scopes here because state values are copied + // directly below. + this.BufferLogScopes(); + + return new() + { + Data = this.Data, + State = this.State, + StateValues = this.StateValues == null ? null : new List>(this.StateValues), + BufferedScopes = this.BufferedScopes == null ? null : new List(this.BufferedScopes), + }; + } + + /// + /// Buffers the state values attached to the log into a list so that + /// they can be safely processed after the log message lifecycle has + /// ended. + /// + private void BufferLogStateValues() + { + var stateValues = this.StateValues; + if (stateValues == null || stateValues == this.AttributeStorage) { return; } - List scopes = new List(); + var attributeStorage = this.AttributeStorage ??= new List>(stateValues.Count); - this.ScopeProvider?.ForEachScope(AddScopeToBufferedList, scopes); + // Note: AddRange here will copy all of the KeyValuePairs from + // stateValues to AttributeStorage. This "captures" the state and + // fixes issues where the values are generated at enumeration time + // like + // https://github.com/open-telemetry/opentelemetry-dotnet/issues/2905. + attributeStorage.AddRange(stateValues); - this.bufferedScopes = scopes; + this.StateValues = attributeStorage; + } + + /// + /// Buffers the scopes attached to the log into a list so that they can + /// be safely processed after the log message lifecycle has ended. + /// + private void BufferLogScopes() + { + if (this.ScopeProvider == null) + { + return; + } + + List scopes = this.BufferedScopes ??= new List(LogRecordPoolHelper.DefaultMaxNumberOfScopes); + + this.ScopeProvider.ForEachScope(AddScopeToBufferedList, scopes); + + this.ScopeProvider = null; } private readonly struct ScopeForEachState diff --git a/src/OpenTelemetry/Logs/LogRecordAttributeList.cs b/src/OpenTelemetry/Logs/LogRecordAttributeList.cs new file mode 100644 index 000000000..385ba8f10 --- /dev/null +++ b/src/OpenTelemetry/Logs/LogRecordAttributeList.cs @@ -0,0 +1,326 @@ +// +// 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. +// + +#nullable enable + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Logs +{ + /// + /// Stores attributes to be added to a log message. + /// + internal struct LogRecordAttributeList : IReadOnlyList> + { + internal const int OverflowAdditionalCapacity = 8; + internal List>? OverflowAttributes; + private KeyValuePair attribute1; + private KeyValuePair attribute2; + private KeyValuePair attribute3; + private KeyValuePair attribute4; + private KeyValuePair attribute5; + private KeyValuePair attribute6; + private KeyValuePair attribute7; + private KeyValuePair attribute8; + private int count; + + /// + public readonly int Count => this.count; + + /// + public KeyValuePair this[int index] + { + readonly get + { + if (this.OverflowAttributes is not null) + { + Debug.Assert(index < this.OverflowAttributes.Count, "Invalid index accessed."); + return this.OverflowAttributes[index]; + } + + if ((uint)index >= (uint)this.count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return index switch + { + 0 => this.attribute1, + 1 => this.attribute2, + 2 => this.attribute3, + 3 => this.attribute4, + 4 => this.attribute5, + 5 => this.attribute6, + 6 => this.attribute7, + 7 => this.attribute8, + _ => default, // we shouldn't come here anyway. + }; + } + + set + { + if (this.OverflowAttributes is not null) + { + Debug.Assert(index < this.OverflowAttributes.Count, "Invalid index accessed."); + this.OverflowAttributes[index] = value; + return; + } + + if ((uint)index >= (uint)this.count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + switch (index) + { + case 0: this.attribute1 = value; break; + case 1: this.attribute2 = value; break; + case 2: this.attribute3 = value; break; + case 3: this.attribute4 = value; break; + case 4: this.attribute5 = value; break; + case 5: this.attribute6 = value; break; + case 6: this.attribute7 = value; break; + case 7: this.attribute8 = value; break; + default: + Debug.Assert(false, "Unreachable code executed."); + break; + } + } + } + + /// + /// Add an attribute. + /// + /// Attribute name. + /// Attribute value. + [EditorBrowsable(EditorBrowsableState.Never)] + public object? this[string key] + { + // Note: This only exists to enable collection initializer syntax + // like { ["key"] = value }. + set => this.Add(new KeyValuePair(key, value)); + } + + /// + /// Create a collection from an enumerable. + /// + /// Source attributes. + /// . + public static LogRecordAttributeList CreateFromEnumerable(IEnumerable> attributes) + { + Guard.ThrowIfNull(attributes); + + LogRecordAttributeList logRecordAttributes = default; + logRecordAttributes.OverflowAttributes = new(attributes); + logRecordAttributes.count = logRecordAttributes.OverflowAttributes.Count; + return logRecordAttributes; + } + + /// + /// Add an attribute. + /// + /// Attribute name. + /// Attribute value. + public void Add(string key, object? value) + => this.Add(new KeyValuePair(key, value)); + + /// + /// Add an attribute. + /// + /// Attribute. + public void Add(KeyValuePair attribute) + { + if (this.OverflowAttributes is not null) + { + this.OverflowAttributes.Add(attribute); + this.count++; + return; + } + + Debug.Assert(this.count <= 8, "Item added beyond struct capacity."); + + switch (this.count) + { + case 0: this.attribute1 = attribute; break; + case 1: this.attribute2 = attribute; break; + case 2: this.attribute3 = attribute; break; + case 3: this.attribute4 = attribute; break; + case 4: this.attribute5 = attribute; break; + case 5: this.attribute6 = attribute; break; + case 6: this.attribute7 = attribute; break; + case 7: this.attribute8 = attribute; break; + case 8: + Debug.Assert(this.OverflowAttributes is null, "Overflow attributes already created."); + this.MoveAttributesToTheOverflowList(); + Debug.Assert(this.OverflowAttributes is not null, "Overflow attributes creation failure."); + this.OverflowAttributes!.Add(attribute); + break; + default: + // We shouldn't come here. + Debug.Assert(this.OverflowAttributes is null, "Unreachable code executed."); + return; + } + + this.count++; + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// . + public readonly Enumerator GetEnumerator() + => new(in this); + + /// + readonly IEnumerator> IEnumerable>.GetEnumerator() => this.GetEnumerator(); + + /// + readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + internal readonly void ApplyToLogRecord(LogRecord logRecord) + { + int count = this.count; + if (count <= 0) + { + logRecord.StateValues = null; + return; + } + + var overflowAttributes = this.OverflowAttributes; + if (overflowAttributes != null) + { + // An allocation has already occurred, just use the buffer. + logRecord.StateValues = overflowAttributes; + return; + } + + Debug.Assert(count <= 8, "Invalid size detected."); + + var attributeStorage = logRecord.AttributeStorage ??= new List>(OverflowAdditionalCapacity); + + try + { + // TODO: Perf test this, adjust as needed. + + attributeStorage.Add(this.attribute1); + if (count == 1) + { + return; + } + + attributeStorage.Add(this.attribute2); + if (count == 2) + { + return; + } + + attributeStorage.Add(this.attribute3); + if (count == 3) + { + return; + } + + attributeStorage.Add(this.attribute4); + if (count == 4) + { + return; + } + + attributeStorage.Add(this.attribute5); + if (count == 5) + { + return; + } + + attributeStorage.Add(this.attribute6); + if (count == 6) + { + return; + } + + attributeStorage.Add(this.attribute7); + if (count == 7) + { + return; + } + + attributeStorage.Add(this.attribute8); + } + finally + { + logRecord.StateValues = attributeStorage; + } + } + + private void MoveAttributesToTheOverflowList() + { + this.OverflowAttributes = new(16) + { + { this.attribute1 }, + { this.attribute2 }, + { this.attribute3 }, + { this.attribute4 }, + { this.attribute5 }, + { this.attribute6 }, + { this.attribute7 }, + { this.attribute8 }, + }; + } + + /// + /// Enumerates the elements of a . + /// + public struct Enumerator : IEnumerator>, IEnumerator + { + private LogRecordAttributeList attributes; + private int index; + + internal Enumerator(in LogRecordAttributeList attributes) + { + this.index = -1; + this.attributes = attributes; + } + + /// + public readonly KeyValuePair Current + => this.attributes[this.index]; + + /// + readonly object IEnumerator.Current => this.Current; + + /// + public bool MoveNext() + { + this.index++; + return this.index < this.attributes.Count; + } + + /// + public readonly void Dispose() + { + } + + /// + readonly void IEnumerator.Reset() + => throw new NotSupportedException(); + } + } +} diff --git a/src/OpenTelemetry/Logs/LogRecordData.cs b/src/OpenTelemetry/Logs/LogRecordData.cs new file mode 100644 index 000000000..538bf0a36 --- /dev/null +++ b/src/OpenTelemetry/Logs/LogRecordData.cs @@ -0,0 +1,143 @@ +// +// 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. +// + +#nullable enable + +using System; +using System.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace OpenTelemetry.Logs +{ + /// + /// Stores details about a log message. + /// + internal struct LogRecordData + { + internal DateTime TimestampBacking = DateTime.UtcNow; + + /// + /// Initializes a new instance of the struct. + /// + public LogRecordData() + : this(activity: null) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// Note: The property is initialized to automatically. + /// + /// Optional used to populate context fields. + public LogRecordData(Activity? activity) + { + if (activity != null) + { + this.TraceId = activity.TraceId; + this.SpanId = activity.SpanId; + this.TraceState = activity.TraceStateString; + this.TraceFlags = activity.ActivityTraceFlags; + } + else + { + this.TraceId = default; + this.SpanId = default; + this.TraceState = null; + this.TraceFlags = ActivityTraceFlags.None; + } + } + + /// + /// Gets or sets the log timestamp. + /// + /// + /// Note: If is set to a value with it will be automatically converted to + /// UTC using . + /// + public DateTime Timestamp + { + readonly get => this.TimestampBacking; + set { this.TimestampBacking = value.Kind == DateTimeKind.Local ? value.ToUniversalTime() : value; } + } + + /// + /// Gets or sets the log . + /// + public ActivityTraceId TraceId { get; set; } + + /// + /// Gets or sets the log . + /// + public ActivitySpanId SpanId { get; set; } + + /// + /// Gets or sets the log . + /// + public ActivityTraceFlags TraceFlags { get; set; } + + /// + /// Gets or sets the log trace state. + /// + public string? TraceState { get; set; } + + /// + /// Gets or sets the log category name. + /// + public string? CategoryName { get; set; } = null; + + /// + /// Gets or sets the log . + /// + public LogLevel LogLevel { get; set; } = LogLevel.Trace; + + /// + /// Gets or sets the log . + /// + public EventId EventId { get; set; } = default; + + /// + /// Gets or sets the log message. + /// + public string? Message { get; set; } = null; + + /// + /// Gets or sets the log . + /// + public Exception? Exception { get; set; } = null; + + internal static void SetActivityContext(ref LogRecordData data, Activity? activity = null) + { + if (activity != null) + { + data.TraceId = activity.TraceId; + data.SpanId = activity.SpanId; + data.TraceState = activity.TraceStateString; + data.TraceFlags = activity.ActivityTraceFlags; + } + else + { + data.TraceId = default; + data.SpanId = default; + data.TraceState = null; + data.TraceFlags = ActivityTraceFlags.None; + } + } + } +} diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index 8a128682d..91698d112 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; using OpenTelemetry.Internal; @@ -52,20 +53,32 @@ namespace OpenTelemetry.Logs var processor = provider.Processor; if (processor != null) { - var record = new LogRecord( - provider.IncludeScopes ? this.ScopeProvider : null, - DateTime.UtcNow, - this.categoryName, - logLevel, - eventId, - provider.IncludeFormattedMessage ? formatter?.Invoke(state, exception) : null, - provider.ParseStateValues ? null : state, - exception, - provider.ParseStateValues ? this.ParseState(state) : null); + var pool = provider.LogRecordPool; + + var record = pool.Rent(); + + record.ScopeProvider = provider.IncludeScopes ? this.ScopeProvider : null; + record.State = provider.ParseStateValues ? null : state; + record.StateValues = provider.ParseStateValues ? ParseState(record, state) : null; + + ref LogRecordData data = ref record.Data; + + data.TimestampBacking = DateTime.UtcNow; + data.CategoryName = this.categoryName; + data.LogLevel = logLevel; + data.EventId = eventId; + data.Message = provider.IncludeFormattedMessage ? formatter?.Invoke(state, exception) : null; + data.Exception = exception; + + LogRecordData.SetActivityContext(ref data, Activity.Current); processor.OnEnd(record); record.ScopeProvider = null; + + // Attempt to return the LogRecord to the pool. This will no-op + // if a batch exporter has added a reference. + pool.Return(record); } } @@ -77,22 +90,37 @@ namespace OpenTelemetry.Logs public IDisposable BeginScope(TState state) => this.ScopeProvider?.Push(state) ?? NullScope.Instance; - private IReadOnlyList> ParseState(TState state) + private static IReadOnlyList> ParseState(LogRecord logRecord, TState state) { + /* TODO: Enable this if/when LogRecordAttributeList becomes public. + if (state is LogRecordAttributeList logRecordAttributes) + { + logRecordAttributes.ApplyToLogRecord(logRecord); + return logRecord.AttributeStorage!; + } + else*/ if (state is IReadOnlyList> stateList) { return stateList; } else if (state is IEnumerable> stateValues) { - return new List>(stateValues); + var attributeStorage = logRecord.AttributeStorage; + if (attributeStorage == null) + { + return logRecord.AttributeStorage = new List>(stateValues); + } + else + { + attributeStorage.AddRange(stateValues); + return attributeStorage; + } } else { - return new List> - { - new KeyValuePair(string.Empty, state), - }; + var attributeStorage = logRecord.AttributeStorage ??= new List>(LogRecordPoolHelper.DefaultMaxNumberOfAttributes); + attributeStorage.Add(new KeyValuePair(string.Empty, state)); + return attributeStorage; } } diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggerOptions.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggerOptions.cs index 1a779ac63..d20fcce2b 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggerOptions.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggerOptions.cs @@ -16,6 +16,7 @@ #nullable enable +using System; using System.Collections.Generic; using OpenTelemetry.Internal; using OpenTelemetry.Resources; @@ -73,6 +74,8 @@ namespace OpenTelemetry.Logs /// /// Sets the from which the Resource associated with /// this provider is built from. Overwrites currently set ResourceBuilder. + /// You should usually use instead + /// (call if desired). /// /// from which Resource will be built. /// Returns for chaining. @@ -83,5 +86,18 @@ namespace OpenTelemetry.Logs this.ResourceBuilder = resourceBuilder; return this; } + + /// + /// Modify the from which the Resource associated with + /// this provider is built from in-place. + /// + /// An action which modifies the provided in-place. + /// Returns for chaining. + public OpenTelemetryLoggerOptions ConfigureResource(Action configure) + { + Guard.ThrowIfNull(configure, nameof(configure)); + configure(this.ResourceBuilder); + return this; + } } } diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs index 0c33a7268..d94769b7f 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs @@ -16,7 +16,9 @@ #nullable enable +using System; using System.Collections; +using System.Threading; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OpenTelemetry.Internal; @@ -36,6 +38,7 @@ namespace OpenTelemetry.Logs internal BaseProcessor? Processor; internal Resource Resource; private readonly Hashtable loggers = new(); + private ILogRecordPool? threadStaticPool = LogRecordThreadStaticPool.Instance; private bool disposed; static OpenTelemetryLoggerProvider() @@ -50,7 +53,24 @@ namespace OpenTelemetry.Logs /// /// . public OpenTelemetryLoggerProvider(IOptionsMonitor options) - : this(options?.CurrentValue!) + : this(options?.CurrentValue ?? throw new ArgumentNullException(nameof(options))) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// configuration callback. + public OpenTelemetryLoggerProvider(Action configure) + : this(BuildOptions(configure ?? throw new ArgumentNullException(nameof(configure)))) + { + } + + /// + /// Initializes a new instance of the class. + /// + public OpenTelemetryLoggerProvider() + : this(BuildOptions(configure: null)) { } @@ -72,6 +92,8 @@ namespace OpenTelemetry.Logs internal IExternalScopeProvider? ScopeProvider { get; private set; } + internal ILogRecordPool LogRecordPool => this.threadStaticPool ?? LogRecordSharedPool.Current; + /// void ISupportExternalScope.SetScopeProvider(IExternalScopeProvider scopeProvider) { @@ -112,12 +134,46 @@ namespace OpenTelemetry.Logs return logger; } + /// + /// Flushes all the processors registered under , blocks the current thread + /// until flush completed, shutdown signaled or timed out. + /// + /// + /// The number (non-negative) of milliseconds to wait, or + /// Timeout.Infinite to wait indefinitely. + /// + /// + /// Returns true when force flush succeeded; otherwise, false. + /// + /// + /// Thrown when the timeoutMilliseconds is smaller than -1. + /// + /// + /// This function guarantees thread-safety. + /// + public bool ForceFlush(int timeoutMilliseconds = Timeout.Infinite) + { + return this.Processor?.ForceFlush(timeoutMilliseconds) ?? true; + } + + /// + /// Create a . + /// + /// . + internal LogEmitter CreateEmitter() => new(this); + internal OpenTelemetryLoggerProvider AddProcessor(BaseProcessor processor) { Guard.ThrowIfNull(processor); processor.SetParentProvider(this); + if (this.threadStaticPool != null && this.ContainsBatchProcessor(processor)) + { + this.threadStaticPool = null; + } + if (this.Processor == null) { this.Processor = processor; @@ -128,16 +184,41 @@ namespace OpenTelemetry.Logs } else { - this.Processor = new CompositeProcessor(new[] + var newCompositeProcessor = new CompositeProcessor(new[] { this.Processor, - processor, }); + newCompositeProcessor.SetParentProvider(this); + newCompositeProcessor.AddProcessor(processor); + this.Processor = newCompositeProcessor; } return this; } + internal bool ContainsBatchProcessor(BaseProcessor processor) + { + if (processor is BatchExportProcessor) + { + return true; + } + else if (processor is CompositeProcessor compositeProcessor) + { + var current = compositeProcessor.Head; + while (current != null) + { + if (this.ContainsBatchProcessor(current.Value)) + { + return true; + } + + current = current.Next; + } + } + + return false; + } + /// protected override void Dispose(bool disposing) { @@ -156,5 +237,12 @@ namespace OpenTelemetry.Logs base.Dispose(disposing); } + + private static OpenTelemetryLoggerOptions BuildOptions(Action? configure) + { + OpenTelemetryLoggerOptions options = new(); + configure?.Invoke(options); + return options; + } } } diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs index 000542b2f..9687d32eb 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs @@ -32,12 +32,20 @@ namespace Microsoft.Extensions.Logging public static class OpenTelemetryLoggingExtensions { /// - /// Adds a OpenTelemetry logger named 'OpenTelemetry' to the factory. + /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . + /// + /// The to use. + /// The supplied for call chaining. + public static ILoggingBuilder AddOpenTelemetry(this ILoggingBuilder builder) + => AddOpenTelemetry(builder, configure: 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 = null) + public static ILoggingBuilder AddOpenTelemetry(this ILoggingBuilder builder, Action? configure) { Guard.ThrowIfNull(builder); @@ -51,5 +59,52 @@ namespace Microsoft.Extensions.Logging return builder; } + + /// + /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . + /// + /// + /// Note: The supplied will + /// automatically be disposed when the + /// built from is disposed. + /// + /// The to use. + /// . + /// The supplied for call chaining. + public static ILoggingBuilder AddOpenTelemetry(this ILoggingBuilder builder, OpenTelemetryLoggerProvider openTelemetryLoggerProvider) + => AddOpenTelemetry(builder, openTelemetryLoggerProvider, disposeProvider: true); + + /// + /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . + /// + /// The to use. + /// . + /// Controls whether or not the supplied + /// will be disposed when + /// the is disposed. + /// The supplied for call chaining. + public static ILoggingBuilder AddOpenTelemetry( + this ILoggingBuilder builder, + OpenTelemetryLoggerProvider openTelemetryLoggerProvider, + bool disposeProvider) + { + Guard.ThrowIfNull(builder); + Guard.ThrowIfNull(openTelemetryLoggerProvider); + + // Note: Currently if multiple OpenTelemetryLoggerProvider instances + // are added to the same ILoggingBuilder everything after the first + // is silently ignored. + + if (disposeProvider) + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(sp => openTelemetryLoggerProvider)); + } + else + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(openTelemetryLoggerProvider)); + } + + return builder; + } } } diff --git a/examples/AspNet/Models/WeatherForecast.cs b/src/OpenTelemetry/Logs/Pool/ILogRecordPool.cs similarity index 63% rename from examples/AspNet/Models/WeatherForecast.cs rename to src/OpenTelemetry/Logs/Pool/ILogRecordPool.cs index 71e8983c6..14d12cc6e 100644 --- a/examples/AspNet/Models/WeatherForecast.cs +++ b/src/OpenTelemetry/Logs/Pool/ILogRecordPool.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,18 +14,14 @@ // limitations under the License. // -using System; +#nullable enable -namespace Examples.AspNet.Models +namespace OpenTelemetry.Logs { - public class WeatherForecast + internal interface ILogRecordPool { - public DateTime Date { get; set; } + LogRecord Rent(); - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(this.TemperatureC / 0.5556); - - public string Summary { get; set; } + void Return(LogRecord logRecord); } } diff --git a/src/OpenTelemetry/Logs/Pool/LogRecordPoolHelper.cs b/src/OpenTelemetry/Logs/Pool/LogRecordPoolHelper.cs new file mode 100644 index 000000000..b63028b92 --- /dev/null +++ b/src/OpenTelemetry/Logs/Pool/LogRecordPoolHelper.cs @@ -0,0 +1,61 @@ +// +// 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. +// + +#nullable enable + +namespace OpenTelemetry.Logs +{ + internal static class LogRecordPoolHelper + { + public const int DefaultMaxNumberOfAttributes = 64; + public const int DefaultMaxNumberOfScopes = 16; + + public static void Clear(LogRecord logRecord) + { + var attributeStorage = logRecord.AttributeStorage; + if (attributeStorage != null) + { + if (attributeStorage.Count > DefaultMaxNumberOfAttributes) + { + // Don't allow the pool to grow unconstained. + logRecord.AttributeStorage = null; + } + else + { + /* List.Clear sets the count/size to 0 but it maintains the + underlying array (capacity). */ + attributeStorage.Clear(); + } + } + + var bufferedScopes = logRecord.BufferedScopes; + if (bufferedScopes != null) + { + if (bufferedScopes.Count > DefaultMaxNumberOfScopes) + { + // Don't allow the pool to grow unconstained. + logRecord.BufferedScopes = null; + } + else + { + /* List.Clear sets the count/size to 0 but it maintains the + underlying array (capacity). */ + bufferedScopes.Clear(); + } + } + } + } +} diff --git a/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs b/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs new file mode 100644 index 000000000..57234057a --- /dev/null +++ b/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs @@ -0,0 +1,150 @@ +// +// 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. +// + +#nullable enable + +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Logs +{ + internal sealed class LogRecordSharedPool : ILogRecordPool + { + public const int DefaultMaxPoolSize = 2048; + + public static LogRecordSharedPool Current = new(DefaultMaxPoolSize); + + public readonly int Capacity; + private readonly LogRecord?[] pool; + private long rentIndex; + private long returnIndex; + + public LogRecordSharedPool(int capacity) + { + this.Capacity = capacity; + this.pool = new LogRecord?[capacity]; + } + + public int Count => (int)(Volatile.Read(ref this.returnIndex) - Volatile.Read(ref this.rentIndex)); + + // Note: It might make sense to expose this (somehow) in the future. + // Ideal config is shared pool capacity == max batch size. + public static void Resize(int capacity) + { + Guard.ThrowIfOutOfRange(capacity, min: 1); + + Current = new(capacity); + } + + public LogRecord Rent() + { + while (true) + { + var rentSnapshot = Volatile.Read(ref this.rentIndex); + var returnSnapshot = Volatile.Read(ref this.returnIndex); + + if (rentSnapshot >= returnSnapshot) + { + break; // buffer is empty + } + + if (Interlocked.CompareExchange(ref this.rentIndex, rentSnapshot + 1, rentSnapshot) == rentSnapshot) + { + var logRecord = Interlocked.Exchange(ref this.pool[rentSnapshot % this.Capacity], null); + if (logRecord == null && !this.TryRentCoreRare(rentSnapshot, out logRecord)) + { + continue; + } + + logRecord.ResetReferenceCount(); + return logRecord; + } + } + + var newLogRecord = new LogRecord(); + newLogRecord.ResetReferenceCount(); + return newLogRecord; + } + + public void Return(LogRecord logRecord) + { + if (logRecord.RemoveReference() != 0) + { + return; + } + + LogRecordPoolHelper.Clear(logRecord); + + while (true) + { + var rentSnapshot = Volatile.Read(ref this.rentIndex); + var returnSnapshot = Volatile.Read(ref this.returnIndex); + + if (returnSnapshot - rentSnapshot >= this.Capacity) + { + return; // buffer is full + } + + if (Interlocked.CompareExchange(ref this.returnIndex, returnSnapshot + 1, returnSnapshot) == returnSnapshot) + { + // If many threads are hammering rent/return it is possible + // for two threads to write to the same index. In that case + // only one of the logRecords will make it back into the + // pool. Anything lost in the race will collected by the GC + // and the pool will issue new instances as needed. This + // could be abated by an Interlocked.CompareExchange here + // but for the general use case of an exporter returning + // records one-by-one, better to keep this fast and not pay + // for Interlocked.CompareExchange. The race is more + // theoretical. + this.pool[returnSnapshot % this.Capacity] = logRecord; + return; + } + } + } + + private bool TryRentCoreRare(long rentSnapshot, [NotNullWhen(true)] out LogRecord? logRecord) + { + SpinWait wait = default; + while (true) + { + if (wait.NextSpinWillYield) + { + // Super rare case. If many threads are hammering + // rent/return it is possible a read was issued an index and + // then yielded while other threads caused the pointers to + // wrap around. When the yielded thread wakes up its read + // index could have been stolen by another thread. To + // prevent deadlock, bail out of read after spinning. This + // will cause either a successful rent from another index, + // or a new record to be created + logRecord = null; + return false; + } + + wait.SpinOnce(); + + logRecord = Interlocked.Exchange(ref this.pool[rentSnapshot % this.Capacity], null); + if (logRecord != null) + { + // Rare case where the write was still working when the read came in + return true; + } + } + } + } +} diff --git a/src/OpenTelemetry/Logs/Pool/LogRecordThreadStaticPool.cs b/src/OpenTelemetry/Logs/Pool/LogRecordThreadStaticPool.cs new file mode 100644 index 000000000..28b417f13 --- /dev/null +++ b/src/OpenTelemetry/Logs/Pool/LogRecordThreadStaticPool.cs @@ -0,0 +1,55 @@ +// +// 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. +// + +#nullable enable + +using System; + +namespace OpenTelemetry.Logs +{ + internal sealed class LogRecordThreadStaticPool : ILogRecordPool + { + [ThreadStatic] + public static LogRecord? Storage; + + private LogRecordThreadStaticPool() + { + } + + public static LogRecordThreadStaticPool Instance { get; } = new(); + + public LogRecord Rent() + { + var logRecord = Storage; + if (logRecord != null) + { + Storage = null; + return logRecord; + } + + return new(); + } + + public void Return(LogRecord logRecord) + { + if (Storage == null) + { + LogRecordPoolHelper.Clear(logRecord); + Storage = logRecord; + } + } + } +} diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs new file mode 100644 index 000000000..14d5eaddc --- /dev/null +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -0,0 +1,273 @@ +// +// 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.Runtime.CompilerServices; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Metrics; + +/// +/// A histogram buckets implementation based on circular buffer. +/// +internal sealed class CircularBufferBuckets +{ + private long[] trait; + private int begin = 0; + private int end = -1; + + public CircularBufferBuckets(int capacity) + { + Guard.ThrowIfOutOfRange(capacity, min: 1); + + this.Capacity = capacity; + } + + /// + /// Gets the capacity of the . + /// + public int Capacity { get; } + + /// + /// Gets the offset of the start index for the . + /// + public int Offset => this.begin; + + /// + /// Gets the size of the . + /// + public int Size => this.end - this.begin + 1; + + /// + /// Returns the value of Bucket[index]. + /// + /// The index of the bucket. + /// + /// The "index" value can be positive, zero or negative. + /// This method does not validate if "index" falls into [begin, end], + /// the caller is responsible for the validation. + /// + public long this[int index] + { + get => this.trait[this.ModuloIndex(index)]; + } + + /// + /// Attempts to increment the value of Bucket[index] by value. + /// + /// The index of the bucket. + /// The increment. + /// + /// Returns 0 if the increment attempt succeeded; + /// Returns a positive integer indicating the minimum scale reduction level + /// if the increment attempt failed. + /// + /// + /// The "index" value can be positive, zero or negative. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int TryIncrement(int index, long value = 1) + { + var capacity = this.Capacity; + + if (this.trait == null) + { + this.trait = new long[capacity]; + + this.begin = index; + this.end = index; + this.trait[this.ModuloIndex(index)] += value; + + return 0; + } + + var begin = this.begin; + var end = this.end; + + if (index > end) + { + end = index; + } + else if (index < begin) + { + begin = index; + } + else + { + this.trait[this.ModuloIndex(index)] += value; + + return 0; + } + + var diff = end - begin; + + if (diff >= capacity || diff < 0) + { + return CalculateScaleReduction(begin, end, capacity); + } + + this.begin = begin; + this.end = end; + + this.trait[this.ModuloIndex(index)] += value; + + return 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int CalculateScaleReduction(int begin, int end, int capacity) + { + Debug.Assert(capacity >= 2, "The capacity must be at least 2."); + + var retval = 0; + var diff = end - begin; + while (diff >= capacity || diff < 0) + { + begin >>= 1; + end >>= 1; + diff = end - begin; + retval++; + } + + return retval; + } + } + + public void ScaleDown(int level = 1) + { + Debug.Assert(level > 0, "The scale down level must be a positive integer."); + + if (this.trait == null) + { + return; + } + + // 0 <= offset < capacity <= 2147483647 + uint capacity = (uint)this.Capacity; + var offset = (uint)this.ModuloIndex(this.begin); + + var currentBegin = this.begin; + var currentEnd = this.end; + + for (int i = 0; i < level; i++) + { + var newBegin = currentBegin >> 1; + var newEnd = currentEnd >> 1; + + if (currentBegin != currentEnd) + { + if (currentBegin % 2 == 0) + { + ScaleDownInternal(this.trait, offset, currentBegin, currentEnd, capacity); + } + else + { + currentBegin++; + + if (currentBegin != currentEnd) + { + ScaleDownInternal(this.trait, offset + 1, currentBegin, currentEnd, capacity); + } + } + } + + currentBegin = newBegin; + currentEnd = newEnd; + } + + this.begin = currentBegin; + this.end = currentEnd; + + if (capacity > 1) + { + AdjustPosition(this.trait, offset, (uint)this.ModuloIndex(currentBegin), (uint)(currentEnd - currentBegin + 1), capacity); + } + + return; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ScaleDownInternal(long[] array, uint offset, int begin, int end, uint capacity) + { + for (var index = begin + 1; index < end; index++) + { + Consolidate(array, (offset + (uint)(index - begin)) % capacity, (offset + (uint)((index >> 1) - (begin >> 1))) % capacity); + } + + Consolidate(array, (offset + (uint)(end - begin)) % capacity, (offset + (uint)((end >> 1) - (begin >> 1))) % capacity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void AdjustPosition(long[] array, uint src, uint dst, uint size, uint capacity) + { + var advancement = (dst + capacity - src) % capacity; + + if (advancement == 0) + { + return; + } + + if (size - 1 == advancement && advancement << 1 == capacity) + { + Exchange(array, src++, dst++); + size -= 2; + } + else if (advancement < size) + { + src = src + size - 1; + dst = dst + size - 1; + + while (size-- != 0) + { + Move(array, src-- % capacity, dst-- % capacity); + } + + return; + } + + while (size-- != 0) + { + Move(array, src++ % capacity, dst++ % capacity); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Consolidate(long[] array, uint src, uint dst) + { + array[dst] += array[src]; + array[src] = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Exchange(long[] array, uint src, uint dst) + { + var value = array[dst]; + array[dst] = array[src]; + array[src] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Move(long[] array, uint src, uint dst) + { + array[dst] = array[src]; + array[src] = 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ModuloIndex(int value) + { + return MathHelper.PositiveModulo32(value, this.Capacity); + } +} diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs new file mode 100644 index 000000000..b05a4c443 --- /dev/null +++ b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs @@ -0,0 +1,206 @@ +// +// 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. +// + +#if NET6_0_OR_GREATER + +using System; +using System.Diagnostics; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Metrics; + +/// +/// Represents an exponential bucket histogram with base = 2 ^ (2 ^ (-scale)). +/// An exponential bucket histogram has infinite number of buckets, which are +/// identified by Bucket[index] = ( base ^ index, base ^ (index + 1) ], +/// where index is an integer. +/// +internal class ExponentialBucketHistogram +{ + private static readonly double Log2E = Math.Log2(Math.E); // 1 / Math.Log(2) + + private int scale; + private double scalingFactor; // 2 ^ scale / log(2) + + /// + /// Initializes a new instance of the class. + /// + /// + /// The maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket. The default value is 160. + /// + public ExponentialBucketHistogram(int maxBuckets = 160) + : this(maxBuckets, 20) + { + } + + internal ExponentialBucketHistogram(int maxBuckets, int scale) + { + /* + The following table is calculated based on [ MapToIndex(double.Epsilon), MapToIndex(double.MaxValue) ]: + + | Scale | Index Range | + | ----- | ------------------------- | + | < -11 | [-1, 0] | + | -11 | [-1, 0] | + | -10 | [-2, 0] | + | -9 | [-3, 1] | + | -8 | [-5, 3] | + | -7 | [-9, 7] | + | -6 | [-17, 15] | + | -5 | [-34, 31] | + | -4 | [-68, 63] | + | -3 | [-135, 127] | + | -2 | [-269, 255] | + | -1 | [-538, 511] | + | 0 | [-1075, 1023] | + | 1 | [-2149, 2047] | + | 2 | [-4297, 4095] | + | 3 | [-8593, 8191] | + | 4 | [-17185, 16383] | + | 5 | [-34369, 32767] | + | 6 | [-68737, 65535] | + | 7 | [-137473, 131071] | + | 8 | [-274945, 262143] | + | 9 | [-549889, 524287] | + | 10 | [-1099777, 1048575] | + | 11 | [-2199553, 2097151] | + | 12 | [-4399105, 4194303] | + | 13 | [-8798209, 8388607] | + | 14 | [-17596417, 16777215] | + | 15 | [-35192833, 33554431] | + | 16 | [-70385665, 67108863] | + | 17 | [-140771329, 134217727] | + | 18 | [-281542657, 268435455] | + | 19 | [-563085313, 536870911] | + | 20 | [-1126170625, 1073741823] | + | 21 | [underflow, 2147483647] | + | > 21 | [underflow, overflow] | + */ + Guard.ThrowIfOutOfRange(scale, min: -11, max: 20); + + /* + Regardless of the scale, MapToIndex(1) will always be -1, so we need two buckets at minimum: + bucket[-1] = (1/base, 1] + bucket[0] = (1, base] + */ + Guard.ThrowIfOutOfRange(maxBuckets, min: 2); + + this.Scale = scale; + this.PositiveBuckets = new CircularBufferBuckets(maxBuckets); + this.NegativeBuckets = new CircularBufferBuckets(maxBuckets); + } + + internal int Scale + { + get => this.scale; + + private set + { + this.scale = value; + this.scalingFactor = Math.ScaleB(Log2E, value); + } + } + + internal CircularBufferBuckets PositiveBuckets { get; } + + internal long ZeroCount { get; private set; } + + internal CircularBufferBuckets NegativeBuckets { get; } + + /// + /// Maps a finite positive IEEE 754 double-precision floating-point + /// number to Bucket[index] = ( base ^ index, base ^ (index + 1) ], + /// where index is an integer. + /// + /// + /// The value to be bucketized. Must be a finite positive number. + /// + /// + /// Returns the index of the bucket. + /// + public int MapToIndex(double value) + { + Debug.Assert(MathHelper.IsFinite(value), "IEEE-754 +Inf, -Inf and NaN should be filtered out before calling this method."); + Debug.Assert(value != 0, "IEEE-754 zero values should be handled by ZeroCount."); + Debug.Assert(!double.IsNegative(value), "IEEE-754 negative values should be normalized before calling this method."); + + var bits = BitConverter.DoubleToInt64Bits(value); + var fraction = bits & 0xFFFFFFFFFFFFFL /* fraction mask */; + + if (this.Scale > 0) + { + // TODO: do we really need this given the lookup table is needed for scale>0 anyways? + if (fraction == 0) + { + var exp = (int)((bits & 0x7FF0000000000000L /* exponent mask */) >> 52 /* fraction width */); + return ((exp - 1023 /* exponent bias */) << this.Scale) - 1; + } + + // TODO: due to precision issue, the values that are close to the bucket + // boundaries should be closely examined to avoid off-by-one. + + return (int)Math.Ceiling(Math.Log(value) * this.scalingFactor) - 1; + } + else + { + var exp = (int)((bits & 0x7FF0000000000000L /* exponent mask */) >> 52 /* fraction width */); + + if (exp == 0) + { + exp -= MathHelper.LeadingZero64(fraction - 1) - 12 /* 64 - fraction width */; + } + else if (fraction == 0) + { + exp--; + } + + return (exp - 1023 /* exponent bias */) >> -this.Scale; + } + } + + public void Record(double value) + { + if (!MathHelper.IsFinite(value)) + { + return; + } + + var c = value.CompareTo(0); + + if (c == 0) + { + this.ZeroCount++; + return; + } + + var index = this.MapToIndex(c > 0 ? value : -value); + var buckets = c > 0 ? this.PositiveBuckets : this.NegativeBuckets; + var n = buckets.TryIncrement(index); + + if (n == 0) + { + return; + } + + this.PositiveBuckets.ScaleDown(n); + this.NegativeBuckets.ScaleDown(n); + this.Scale -= n; + n = buckets.TryIncrement(index >> n); + Debug.Assert(n == 0, "Increment should always succeed after scale down."); + } +} + +#endif diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketHistogramConfiguration.cs b/src/OpenTelemetry/Metrics/ExponentialBucketHistogramConfiguration.cs new file mode 100644 index 000000000..389b2e0e4 --- /dev/null +++ b/src/OpenTelemetry/Metrics/ExponentialBucketHistogramConfiguration.cs @@ -0,0 +1,31 @@ +// +// 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. +// + +namespace OpenTelemetry.Metrics; + +/// +/// Stores configuration for a histogram metric stream with exponential bucket boundaries. +/// +internal class ExponentialBucketHistogramConfiguration : MetricStreamConfiguration +{ + /// + /// Gets or sets the maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket. + /// + /// + /// The default value is 160. + /// + public int MaxSize { get; set; } = 160; +} diff --git a/src/OpenTelemetry/Metrics/MeterProviderBuilderBase.cs b/src/OpenTelemetry/Metrics/MeterProviderBuilderBase.cs index e3614abb4..aa5a5ace8 100644 --- a/src/OpenTelemetry/Metrics/MeterProviderBuilderBase.cs +++ b/src/OpenTelemetry/Metrics/MeterProviderBuilderBase.cs @@ -45,6 +45,16 @@ namespace OpenTelemetry.Metrics internal List MetricReaders { get; } = new List(); + internal ResourceBuilder ResourceBuilder + { + get => this.resourceBuilder; + set + { + Debug.Assert(value != null, $"{nameof(this.ResourceBuilder)} must not be set to null"); + this.resourceBuilder = value; + } + } + /// public override MeterProviderBuilder AddInstrumentation(Func instrumentationFactory) { @@ -126,14 +136,6 @@ namespace OpenTelemetry.Metrics return this; } - internal MeterProviderBuilder SetResourceBuilder(ResourceBuilder resourceBuilder) - { - Debug.Assert(resourceBuilder != null, $"{nameof(resourceBuilder)} must not be null"); - - this.resourceBuilder = resourceBuilder; - return this; - } - /// /// Run the configured actions to initialize the . /// diff --git a/src/OpenTelemetry/Metrics/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry/Metrics/MeterProviderBuilderExtensions.cs index bda417b07..cd4797487 100644 --- a/src/OpenTelemetry/Metrics/MeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry/Metrics/MeterProviderBuilderExtensions.cs @@ -137,7 +137,7 @@ namespace OpenTelemetry.Metrics /// Sets the maximum number of Metric streams supported by the MeterProvider. /// When no Views are configured, every instrument will result in one metric stream, /// so this control the numbers of instruments supported. - /// When Views are configued, a single instrument can result in multiple metric streams, + /// When Views are configured, a single instrument can result in multiple metric streams, /// so this control the number of streams. /// /// MeterProviderBuilder instance. @@ -183,6 +183,8 @@ namespace OpenTelemetry.Metrics /// /// Sets the from which the Resource associated with /// this provider is built from. Overwrites currently set ResourceBuilder. + /// You should usually use instead + /// (call if desired). /// /// MeterProviderBuilder instance. /// from which Resource will be built. @@ -191,7 +193,27 @@ namespace OpenTelemetry.Metrics { if (meterProviderBuilder is MeterProviderBuilderBase meterProviderBuilderBase) { - meterProviderBuilderBase.SetResourceBuilder(resourceBuilder); + meterProviderBuilderBase.ResourceBuilder = resourceBuilder; + } + + return meterProviderBuilder; + } + + /// + /// Modify the from which the Resource associated with + /// this provider is built from in-place. + /// + /// MeterProviderBuilder instance. + /// An action which modifies the provided in-place. + /// Returns for chaining. + public static MeterProviderBuilder ConfigureResource(this MeterProviderBuilder meterProviderBuilder, Action configure) + { + Guard.ThrowIfNull(meterProviderBuilder, nameof(meterProviderBuilder)); + Guard.ThrowIfNull(configure, nameof(configure)); + + if (meterProviderBuilder is MeterProviderBuilderBase meterProviderBuilderBase) + { + configure(meterProviderBuilderBase.ResourceBuilder); } return meterProviderBuilder; diff --git a/src/OpenTelemetry/Metrics/PeriodicExportingMetricReader.cs b/src/OpenTelemetry/Metrics/PeriodicExportingMetricReader.cs index 6b464f02b..d008d2028 100644 --- a/src/OpenTelemetry/Metrics/PeriodicExportingMetricReader.cs +++ b/src/OpenTelemetry/Metrics/PeriodicExportingMetricReader.cs @@ -32,7 +32,7 @@ namespace OpenTelemetry.Metrics internal const int DefaultExportTimeoutMilliseconds = 30000; internal readonly int ExportIntervalMilliseconds; - private readonly int exportTimeoutMilliseconds; + internal readonly int ExportTimeoutMilliseconds; private readonly Thread exporterThread; private readonly AutoResetEvent exportTrigger = new(false); private readonly ManualResetEvent shutdownTrigger = new(false); @@ -60,7 +60,7 @@ namespace OpenTelemetry.Metrics } this.ExportIntervalMilliseconds = exportIntervalMilliseconds; - this.exportTimeoutMilliseconds = exportTimeoutMilliseconds; + this.ExportTimeoutMilliseconds = exportTimeoutMilliseconds; this.exporterThread = new Thread(new ThreadStart(this.ExporterProc)) { @@ -141,15 +141,15 @@ namespace OpenTelemetry.Metrics { case 0: // export OpenTelemetrySdkEventSource.Log.MetricReaderEvent("PeriodicExportingMetricReader calling MetricReader.Collect because Export was triggered."); - this.Collect(this.exportTimeoutMilliseconds); + this.Collect(this.ExportTimeoutMilliseconds); break; case 1: // shutdown OpenTelemetrySdkEventSource.Log.MetricReaderEvent("PeriodicExportingMetricReader calling MetricReader.Collect because Shutdown was triggered."); - this.Collect(this.exportTimeoutMilliseconds); // TODO: do we want to use the shutdown timeout here? + this.Collect(this.ExportTimeoutMilliseconds); // TODO: do we want to use the shutdown timeout here? return; case WaitHandle.WaitTimeout: // timer OpenTelemetrySdkEventSource.Log.MetricReaderEvent("PeriodicExportingMetricReader calling MetricReader.Collect because the export interval has elapsed."); - this.Collect(this.exportTimeoutMilliseconds); + this.Collect(this.ExportTimeoutMilliseconds); break; } } diff --git a/src/OpenTelemetry/OpenTelemetry.csproj b/src/OpenTelemetry/OpenTelemetry.csproj index a1f2cf594..542fcc9e4 100644 --- a/src/OpenTelemetry/OpenTelemetry.csproj +++ b/src/OpenTelemetry/OpenTelemetry.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net462 + net6.0;netstandard2.1;netstandard2.0;net462 OpenTelemetry .NET SDK + + false + + - - + diff --git a/src/OpenTelemetry/README.md b/src/OpenTelemetry/README.md index 0646d6852..e51346cd8 100644 --- a/src/OpenTelemetry/README.md +++ b/src/OpenTelemetry/README.md @@ -79,7 +79,7 @@ Once built, changes to its configuration is not allowed, with the exception of adding more processors. In most cases, a single `TracerProvider` is created at the application startup, and is disposed when application shuts down. -// TODO: Add Asp.Net Core, Asp.Net notes showing where this code should go. +// TODO: Add Asp.Net Core notes showing where this code should go. The snippet below shows how to build a basic `TracerProvider`. This will create a provider with default configuration, and is not particularly useful. The @@ -238,15 +238,14 @@ 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 SDK](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions#telemetry-sdk). -`SetResourceBuilder` method on `TracerProviderBuilder` can be used to set a -`ResourceBuilder` on the provider. When the provider is built, it automatically +The `ConfigureResource` method on `TracerProviderBuilder` 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`. As with -samplers, there can only be a single `Resource` associated with a provider. If -multiple `SetResourceBuilder` is called, the last one wins. Also, it is not -possible to change the resource builder *after* the provider is built, by -calling the `Build()` method on the `TracerProviderBuilder`. `ResourceBuilder` -offers various methods to construct resource comprising of multiple attributes -from various sources. +samplers, there can only be a single `Resource` associated with a provider. +It is not possible to change the resource builder *after* the provider is +built, by calling the `Build()` method on the `TracerProviderBuilder`. +`ResourceBuilder` offers various methods to construct resource comprising +of multiple attributes from various sources. The snippet below shows configuring a custom `ResourceBuilder` to the provider. @@ -256,7 +255,7 @@ using OpenTelemetry.Resources; using OpenTelemetry.Trace; using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyServiceName")) + .ConfigureResource(r => r.AddService("MyServiceName")) .Build(); ``` diff --git a/src/OpenTelemetry/Resources/ResourceBuilder.cs b/src/OpenTelemetry/Resources/ResourceBuilder.cs index c4c8ac15a..3abb89ea7 100644 --- a/src/OpenTelemetry/Resources/ResourceBuilder.cs +++ b/src/OpenTelemetry/Resources/ResourceBuilder.cs @@ -15,6 +15,7 @@ // using System.Collections.Generic; +using System.Diagnostics; using OpenTelemetry.Internal; namespace OpenTelemetry.Resources @@ -26,17 +27,34 @@ namespace OpenTelemetry.Resources { private readonly List resources = new(); + static ResourceBuilder() + { + var defaultServiceName = "unknown_service"; + + try + { + var processName = Process.GetCurrentProcess().ProcessName; + if (!string.IsNullOrWhiteSpace(processName)) + { + defaultServiceName = $"{defaultServiceName}:{processName}"; + } + } + catch + { + // GetCurrentProcess can throw PlatformNotSupportedException + } + + DefaultResource = new Resource(new Dictionary + { + [ResourceSemanticConventions.AttributeServiceName] = defaultServiceName, + }); + } + private ResourceBuilder() { } - private static Resource DefaultResource { get; } = new Resource(new Dictionary - { - [ResourceSemanticConventions.AttributeServiceName] = "unknown_service" - + (string.IsNullOrWhiteSpace(System.Diagnostics.Process.GetCurrentProcess().ProcessName) - ? string.Empty : - ":" + System.Diagnostics.Process.GetCurrentProcess().ProcessName), - }); + private static Resource DefaultResource { get; } /// /// Creates a instance with Default diff --git a/src/OpenTelemetry/SimpleExportProcessor.cs b/src/OpenTelemetry/SimpleExportProcessor.cs index e3c129fa0..b7ada68a7 100644 --- a/src/OpenTelemetry/SimpleExportProcessor.cs +++ b/src/OpenTelemetry/SimpleExportProcessor.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System; using OpenTelemetry.Internal; diff --git a/src/OpenTelemetry/Trace/TracerProviderBuilderBase.cs b/src/OpenTelemetry/Trace/TracerProviderBuilderBase.cs index 7ab0f53a7..708c7bc45 100644 --- a/src/OpenTelemetry/Trace/TracerProviderBuilderBase.cs +++ b/src/OpenTelemetry/Trace/TracerProviderBuilderBase.cs @@ -38,6 +38,20 @@ namespace OpenTelemetry.Trace { } + /// + /// Gets or sets the from which the Resource associated with + /// this provider is built from. Setting this overwrites currently set ResourceBuilder. + /// + internal ResourceBuilder ResourceBuilder + { + get => this.resourceBuilder; + set + { + Guard.ThrowIfNull(value); + this.resourceBuilder = value; + } + } + /// public override TracerProviderBuilder AddInstrumentation( Func instrumentationFactory) @@ -135,20 +149,6 @@ namespace OpenTelemetry.Trace return this; } - /// - /// Sets the from which the Resource associated with - /// this provider is built from. Overwrites currently set ResourceBuilder. - /// - /// from which Resource will be built. - /// Returns for chaining. - internal TracerProviderBuilder SetResourceBuilder(ResourceBuilder resourceBuilder) - { - Guard.ThrowIfNull(resourceBuilder); - - this.resourceBuilder = resourceBuilder; - return this; - } - /// /// Adds processor to the provider. /// diff --git a/src/OpenTelemetry/Trace/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry/Trace/TracerProviderBuilderExtensions.cs index 2c1bef0ef..24f26e98e 100644 --- a/src/OpenTelemetry/Trace/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry/Trace/TracerProviderBuilderExtensions.cs @@ -16,6 +16,7 @@ using System; using System.Diagnostics; +using OpenTelemetry.Internal; using OpenTelemetry.Resources; namespace OpenTelemetry.Trace @@ -61,6 +62,8 @@ namespace OpenTelemetry.Trace /// /// Sets the from which the Resource associated with /// this provider is built from. Overwrites currently set ResourceBuilder. + /// You should usually use instead + /// (call if desired). /// /// TracerProviderBuilder instance. /// from which Resource will be built. @@ -69,7 +72,27 @@ namespace OpenTelemetry.Trace { if (tracerProviderBuilder is TracerProviderBuilderBase tracerProviderBuilderBase) { - tracerProviderBuilderBase.SetResourceBuilder(resourceBuilder); + tracerProviderBuilderBase.ResourceBuilder = resourceBuilder; + } + + return tracerProviderBuilder; + } + + /// + /// Modify the from which the Resource associated with + /// this provider is built from in-place. + /// + /// TracerProviderBuilder instance. + /// An action which modifies the provided in-place. + /// Returns for chaining. + public static TracerProviderBuilder ConfigureResource(this TracerProviderBuilder tracerProviderBuilder, Action configure) + { + Guard.ThrowIfNull(tracerProviderBuilder, nameof(tracerProviderBuilder)); + Guard.ThrowIfNull(configure, nameof(configure)); + + if (tracerProviderBuilder is TracerProviderBuilderBase tracerProviderBuilderBase) + { + configure(tracerProviderBuilderBase.ResourceBuilder); } return tracerProviderBuilder; diff --git a/src/OpenTelemetry/Trace/TracerProviderSdk.cs b/src/OpenTelemetry/Trace/TracerProviderSdk.cs index cd760e295..88f4e15e2 100644 --- a/src/OpenTelemetry/Trace/TracerProviderSdk.cs +++ b/src/OpenTelemetry/Trace/TracerProviderSdk.cs @@ -277,11 +277,13 @@ namespace OpenTelemetry.Trace } else { - this.processor = new CompositeProcessor(new[] + var newCompositeProcessor = new CompositeProcessor(new[] { this.processor, - processor, }); + newCompositeProcessor.SetParentProvider(this); + newCompositeProcessor.AddProcessor(processor); + this.processor = newCompositeProcessor; } return this; diff --git a/test/Benchmarks/Benchmarks.csproj b/test/Benchmarks/Benchmarks.csproj index f488c7c29..6cb7f6277 100644 --- a/test/Benchmarks/Benchmarks.csproj +++ b/test/Benchmarks/Benchmarks.csproj @@ -4,7 +4,6 @@ Exe net6.0;net462 - false @@ -27,9 +26,9 @@ - + diff --git a/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs b/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs index 8538b3666..c08d069bb 100644 --- a/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs +++ b/test/Benchmarks/Exporter/PrometheusSerializerBenchmarks.cs @@ -14,15 +14,13 @@ // limitations under the License. // -extern alias Prometheus; - using System.Collections.Generic; using System.Diagnostics.Metrics; using BenchmarkDotNet.Attributes; using OpenTelemetry; +using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Metrics; using OpenTelemetry.Tests; -using Prometheus::OpenTelemetry.Exporter.Prometheus; namespace Benchmarks.Exporter { diff --git a/test/Benchmarks/Instrumentation/InstrumentedHttpClientBenchmark.cs b/test/Benchmarks/Instrumentation/InstrumentedHttpClientBenchmark.cs index 7f719db01..ff2555eae 100644 --- a/test/Benchmarks/Instrumentation/InstrumentedHttpClientBenchmark.cs +++ b/test/Benchmarks/Instrumentation/InstrumentedHttpClientBenchmark.cs @@ -52,7 +52,7 @@ namespace Benchmarks.Instrumentation this.tracerProvider = Sdk.CreateTracerProviderBuilder() .AddHttpClientInstrumentation() - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(ServiceName)) + .ConfigureResource(r => r.AddService(ServiceName)) .AddSource(SourceName) .Build(); diff --git a/test/Benchmarks/Logs/LogScopeBenchmarks.cs b/test/Benchmarks/Logs/LogScopeBenchmarks.cs index 7c21f970e..9cc2347d6 100644 --- a/test/Benchmarks/Logs/LogScopeBenchmarks.cs +++ b/test/Benchmarks/Logs/LogScopeBenchmarks.cs @@ -56,6 +56,7 @@ namespace Benchmarks.Logs new KeyValuePair("item5", "value5"), })); +#pragma warning disable CS0618 // Type or member is obsolete this.logRecord = new LogRecord( this.scopeProvider, DateTime.UtcNow, @@ -66,6 +67,7 @@ namespace Benchmarks.Logs null, null, null); +#pragma warning restore CS0618 // Type or member is obsolete } [Benchmark] diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/OpenTelemetry.Exporter.Jaeger.Tests.csproj b/test/OpenTelemetry.Exporter.Jaeger.Tests/OpenTelemetry.Exporter.Jaeger.Tests.csproj index 32705554e..5531e9421 100644 --- a/test/OpenTelemetry.Exporter.Jaeger.Tests/OpenTelemetry.Exporter.Jaeger.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/OpenTelemetry.Exporter.Jaeger.Tests.csproj @@ -4,8 +4,6 @@ net6.0 $(TargetFrameworks);net462 - - false diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Configuration/SdkConfigurationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Configuration/SdkConfigurationTests.cs new file mode 100644 index 000000000..dbbc9e436 --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Configuration/SdkConfigurationTests.cs @@ -0,0 +1,133 @@ +// +// 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; +using Xunit; + +namespace OpenTelemetry.Configuration.Tests +{ + [Collection("xUnitCollectionPreventingTestsThatDependOnSdkConfigurationFromRunningInParallel")] + public class SdkConfigurationTests : IDisposable + { + public SdkConfigurationTests() + { + ClearEnvVars(); + SdkConfiguration.Reset(); + } + + public void Dispose() + { + ClearEnvVars(); + SdkConfiguration.Reset(); + } + + [Fact] + public void SdkConfigurationDefaults() + { + var config = SdkConfiguration.Instance; + + Assert.Null(config.AttributeValueLengthLimit); + Assert.Null(config.AttributeCountLimit); + Assert.Null(config.SpanAttributeValueLengthLimit); + Assert.Null(config.SpanAttributeCountLimit); + Assert.Null(config.SpanEventCountLimit); + Assert.Null(config.SpanLinkCountLimit); + Assert.Null(config.EventAttributeCountLimit); + Assert.Null(config.LinkAttributeCountLimit); + } + + [Fact] + public void SdkConfigurationIsInitializedFromEnvironment() + { + Environment.SetEnvironmentVariable("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "10"); + Environment.SetEnvironmentVariable("OTEL_ATTRIBUTE_COUNT_LIMIT", "10"); + Environment.SetEnvironmentVariable("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", "20"); + Environment.SetEnvironmentVariable("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "20"); + Environment.SetEnvironmentVariable("OTEL_SPAN_EVENT_COUNT_LIMIT", "10"); + Environment.SetEnvironmentVariable("OTEL_SPAN_LINK_COUNT_LIMIT", "10"); + Environment.SetEnvironmentVariable("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT", "30"); + Environment.SetEnvironmentVariable("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", "30"); + + SdkConfiguration.Reset(); + var config = SdkConfiguration.Instance; + + Assert.Equal(10, config.AttributeValueLengthLimit); + Assert.Equal(10, config.AttributeCountLimit); + Assert.Equal(20, config.SpanAttributeValueLengthLimit); + Assert.Equal(20, config.SpanAttributeCountLimit); + Assert.Equal(10, config.SpanEventCountLimit); + Assert.Equal(10, config.SpanLinkCountLimit); + Assert.Equal(30, config.EventAttributeCountLimit); + Assert.Equal(30, config.LinkAttributeCountLimit); + } + + [Fact] + public void SpanAttributeValueLengthLimitFallback() + { + var config = SdkConfiguration.Instance; + + config.AttributeValueLengthLimit = 10; + Assert.Equal(10, config.AttributeValueLengthLimit); + Assert.Equal(10, config.SpanAttributeValueLengthLimit); + + config.SpanAttributeValueLengthLimit = 20; + Assert.Equal(10, config.AttributeValueLengthLimit); + Assert.Equal(20, config.SpanAttributeValueLengthLimit); + } + + [Fact] + public void SpanAttributeCountLimitFallback() + { + var config = SdkConfiguration.Instance; + + config.AttributeCountLimit = 10; + Assert.Equal(10, config.AttributeCountLimit); + Assert.Equal(10, config.SpanAttributeCountLimit); + Assert.Equal(10, config.EventAttributeCountLimit); + Assert.Equal(10, config.LinkAttributeCountLimit); + + config.SpanAttributeCountLimit = 20; + Assert.Equal(10, config.AttributeCountLimit); + Assert.Equal(20, config.SpanAttributeCountLimit); + Assert.Equal(20, config.EventAttributeCountLimit); + Assert.Equal(20, config.LinkAttributeCountLimit); + + config.EventAttributeCountLimit = 30; + Assert.Equal(10, config.AttributeCountLimit); + Assert.Equal(20, config.SpanAttributeCountLimit); + Assert.Equal(30, config.EventAttributeCountLimit); + Assert.Equal(20, config.LinkAttributeCountLimit); + + config.LinkAttributeCountLimit = 40; + Assert.Equal(10, config.AttributeCountLimit); + Assert.Equal(20, config.SpanAttributeCountLimit); + Assert.Equal(30, config.EventAttributeCountLimit); + Assert.Equal(40, config.LinkAttributeCountLimit); + } + + private static void ClearEnvVars() + { + Environment.SetEnvironmentVariable("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_ATTRIBUTE_COUNT_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_SPAN_EVENT_COUNT_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_SPAN_LINK_COUNT_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", null); + } + } +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTests.cs index 91466b012..b8206f039 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTests.cs @@ -15,6 +15,7 @@ // using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Metrics; using System.Diagnostics.Tracing; @@ -79,7 +80,8 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests }, }; - DelegatingTestExporter delegatingExporter = null; + DelegatingExporter delegatingExporter = null; + var exportResults = new List(); var activitySourceName = "otlp.collector.test"; @@ -93,7 +95,16 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests serviceProvider: null, configureExporterInstance: otlpExporter => { - delegatingExporter = new DelegatingTestExporter(otlpExporter, onExportAction: () => handle.Set()); + delegatingExporter = new DelegatingExporter + { + OnExportFunc = (batch) => + { + var result = otlpExporter.Export(batch); + exportResults.Add(result); + handle.Set(); + return result; + }, + }; return delegatingExporter; }); @@ -108,21 +119,21 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests if (forceFlush) { Assert.True(tracerProvider.ForceFlush()); - Assert.Single(delegatingExporter.ExportResults); - Assert.Equal(ExportResult.Success, delegatingExporter.ExportResults[0]); + Assert.Single(exportResults); + Assert.Equal(ExportResult.Success, exportResults[0]); } else if (exporterOptions.ExportProcessorType == ExportProcessorType.Batch) { Assert.True(handle.WaitOne(ExportIntervalMilliseconds * 2)); - Assert.Single(delegatingExporter.ExportResults); - Assert.Equal(ExportResult.Success, delegatingExporter.ExportResults[0]); + Assert.Single(exportResults); + Assert.Equal(ExportResult.Success, exportResults[0]); } } if (!forceFlush && exportProcessorType == ExportProcessorType.Simple) { - Assert.Single(delegatingExporter.ExportResults); - Assert.Equal(ExportResult.Success, delegatingExporter.ExportResults[0]); + Assert.Single(exportResults); + Assert.Equal(ExportResult.Success, exportResults[0]); } } @@ -155,7 +166,8 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests Protocol = protocol, }; - DelegatingTestExporter delegatingExporter = null; + DelegatingExporter delegatingExporter = null; + var exportResults = new List(); var meterName = "otlp.collector.test"; @@ -174,7 +186,16 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests serviceProvider: null, configureExporterInstance: otlpExporter => { - delegatingExporter = new DelegatingTestExporter(otlpExporter, onExportAction: () => handle.Set()); + delegatingExporter = new DelegatingExporter + { + OnExportFunc = (batch) => + { + var result = otlpExporter.Export(batch); + exportResults.Add(result); + handle.Set(); + return result; + }, + }; return delegatingExporter; }); @@ -191,21 +212,21 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests if (forceFlush) { Assert.True(meterProvider.ForceFlush()); - Assert.Single(delegatingExporter.ExportResults); - Assert.Equal(ExportResult.Success, delegatingExporter.ExportResults[0]); + Assert.Single(exportResults); + Assert.Equal(ExportResult.Success, exportResults[0]); } else if (!useManualExport) { Assert.True(handle.WaitOne(ExportIntervalMilliseconds * 2)); - Assert.Single(delegatingExporter.ExportResults); - Assert.Equal(ExportResult.Success, delegatingExporter.ExportResults[0]); + Assert.Single(exportResults); + Assert.Equal(ExportResult.Success, exportResults[0]); } } if (!forceFlush && useManualExport) { - Assert.Single(delegatingExporter.ExportResults); - Assert.Equal(ExportResult.Success, delegatingExporter.ExportResults[0]); + Assert.Single(exportResults); + Assert.Equal(ExportResult.Success, exportResults[0]); } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj index 95b2de799..3bc3fee04 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj @@ -5,7 +5,6 @@ net6.0 $(TargetFrameworks);net462 $(TARGET_FRAMEWORK) - false @@ -28,7 +27,7 @@ - + diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs index 4b4dd25fd..bc68e4349 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs @@ -30,7 +30,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests [InlineData("key1 = value1, key2=value2 ", new string[] { "key1", "key2" }, new string[] { "value1", "value2" })] [InlineData("key==value", new string[] { "key" }, new string[] { "=value" })] [InlineData("access-token=abc=/123,timeout=1234", new string[] { "access-token", "timeout" }, new string[] { "abc=/123", "1234" })] - [InlineData("key1=value1;key2=value2", new string[] { "key1" }, new string[] { "value1;key2=value2" })] // semicolon is not treated as a delimeter (https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables) + [InlineData("key1=value1;key2=value2", new string[] { "key1" }, new string[] { "value1;key2=value2" })] // semicolon is not treated as a delimiter (https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables) public void GetMetadataFromHeadersWorksCorrectFormat(string headers, string[] keys, string[] values) { var options = new OtlpExporterOptions @@ -73,7 +73,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests [Theory] [InlineData("")] [InlineData(null)] - public void GetHeaders_NoOptionHeaders_ReturnsEmptyHeadres(string optionHeaders) + public void GetHeaders_NoOptionHeaders_ReturnsEmptyHeaders(string optionHeaders) { var options = new OtlpExporterOptions { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs index 362045da1..03006ecfd 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs @@ -33,7 +33,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests public class OtlpLogExporterTests : Http2UnencryptedSupportTests { [Fact] - public void AddOtlpLogExporterOptionstest() + public void AddOtlpLogExporterOptionsTest() { AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); var loggerOptions = new OpenTelemetryLoggerOptions(); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs index 202b1212d..a3a21e45c 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs @@ -18,8 +18,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Google.Protobuf.Collections; using Microsoft.Extensions.DependencyInjection; using Moq; +using OpenTelemetry.Configuration; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; using OpenTelemetry.Resources; @@ -33,6 +35,7 @@ using Status = OpenTelemetry.Trace.Status; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests { + [Collection("xUnitCollectionPreventingTestsThatDependOnSdkConfigurationFromRunningInParallel")] public class OtlpTraceExporterTests : Http2UnencryptedSupportTests { static OtlpTraceExporterTests() @@ -140,15 +143,15 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests resourceBuilder.AddService("service-name", "ns1"); } + var exportedItems = new List(); var builder = Sdk.CreateTracerProviderBuilder() .SetResourceBuilder(resourceBuilder) .AddSource(sources[0].Name) - .AddSource(sources[1].Name); + .AddSource(sources[1].Name) + .AddProcessor(new SimpleActivityExportProcessor(new InMemoryExporter(exportedItems))); using var openTelemetrySdk = builder.Build(); - var exportedItems = new List(); - var processor = new BatchActivityExportProcessor(new InMemoryExporter(exportedItems)); const int numOfSpans = 10; bool isEven; for (var i = 0; i < numOfSpans; i++) @@ -161,8 +164,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests using Activity activity = source.StartActivity($"span-{i}", activityKind, parentContext: default, activityTags); } - processor.Shutdown(); - + Assert.Equal(10, exportedItems.Count); var batch = new Batch(exportedItems.ToArray(), exportedItems.Count); RunTest(batch); @@ -184,7 +186,9 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests Assert.Contains(otlpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:")); } - foreach (var scope in request.ResourceSpans.First().ScopeSpans) + var scopeSpans = request.ResourceSpans.First().ScopeSpans; + Assert.Equal(2, scopeSpans.Count); + foreach (var scope in scopeSpans) { Assert.Equal(numOfSpans / 2, scope.Spans.Count); Assert.NotNull(scope.Scope); @@ -215,6 +219,79 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests } } + [Fact] + public void SpanLimitsTest() + { + SdkConfiguration.Instance.AttributeValueLengthLimit = 4; + SdkConfiguration.Instance.AttributeCountLimit = 3; + SdkConfiguration.Instance.SpanEventCountLimit = 1; + SdkConfiguration.Instance.SpanLinkCountLimit = 1; + + var tags = new ActivityTagsCollection() + { + new KeyValuePair("TruncatedTag", "12345"), + new KeyValuePair("TruncatedStringArray", new string[] { "12345", "1234", string.Empty, null }), + new KeyValuePair("TruncatedObjectTag", new object()), + new KeyValuePair("OneTagTooMany", 1), + }; + + var links = new[] + { + new ActivityLink(default, tags), + new ActivityLink(default, tags), + }; + + using var activitySource = new ActivitySource(nameof(this.SpanLimitsTest)); + using var activity = activitySource.StartActivity("root", ActivityKind.Server, default(ActivityContext), tags, links); + + var event1 = new ActivityEvent("Event", DateTime.UtcNow, tags); + var event2 = new ActivityEvent("OneEventTooMany", DateTime.Now, tags); + + activity.AddEvent(event1); + activity.AddEvent(event2); + + var otlpSpan = activity.ToOtlpSpan(); + + Assert.NotNull(otlpSpan); + Assert.Equal(3, otlpSpan.Attributes.Count); + Assert.Equal("1234", otlpSpan.Attributes[0].Value.StringValue); + ArrayValueAsserts(otlpSpan.Attributes[1].Value.ArrayValue.Values); + Assert.Equal(new object().ToString().Substring(0, 4), otlpSpan.Attributes[2].Value.StringValue); + + Assert.Single(otlpSpan.Events); + Assert.Equal(3, otlpSpan.Events[0].Attributes.Count); + Assert.Equal("1234", otlpSpan.Events[0].Attributes[0].Value.StringValue); + ArrayValueAsserts(otlpSpan.Events[0].Attributes[1].Value.ArrayValue.Values); + Assert.Equal(new object().ToString().Substring(0, 4), otlpSpan.Events[0].Attributes[2].Value.StringValue); + + Assert.Single(otlpSpan.Links); + Assert.Equal(3, otlpSpan.Links[0].Attributes.Count); + Assert.Equal("1234", otlpSpan.Links[0].Attributes[0].Value.StringValue); + ArrayValueAsserts(otlpSpan.Links[0].Attributes[1].Value.ArrayValue.Values); + Assert.Equal(new object().ToString().Substring(0, 4), otlpSpan.Links[0].Attributes[2].Value.StringValue); + + void ArrayValueAsserts(RepeatedField values) + { + var expectedStringArray = new string[] { "1234", "1234", string.Empty, null }; + for (var i = 0; i < expectedStringArray.Length; ++i) + { + var expectedValue = expectedStringArray[i]; + var expectedValueCase = expectedValue != null + ? OtlpCommon.AnyValue.ValueOneofCase.StringValue + : OtlpCommon.AnyValue.ValueOneofCase.None; + + var actual = values[i]; + Assert.Equal(expectedValueCase, actual.ValueCase); + if (expectedValueCase != OtlpCommon.AnyValue.ValueOneofCase.None) + { + Assert.Equal(expectedValue, actual.StringValue); + } + } + } + + SdkConfiguration.Reset(); + } + [Fact] public void ToOtlpSpanTest() { @@ -374,7 +451,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests } [Theory] - [InlineData(StatusCode.Unset, "Unset", "Description will be ingored if status is Unset.")] + [InlineData(StatusCode.Unset, "Unset", "Description will be ignored if status is Unset.")] [InlineData(StatusCode.Ok, "Ok", "Description must only be used with the Error StatusCode.")] [InlineData(StatusCode.Error, "Error", "Error description.")] public void ToOtlpSpanStatusTagTest(StatusCode expectedStatusCode, string statusCodeTagValue, string statusDescription) diff --git a/test/OpenTelemetry.Exporter.Prometheus.Tests/OpenTelemetry.Exporter.Prometheus.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj similarity index 85% rename from test/OpenTelemetry.Exporter.Prometheus.Tests/OpenTelemetry.Exporter.Prometheus.Tests.csproj rename to test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj index 9d8b6e138..bf33cabbc 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.Tests/OpenTelemetry.Exporter.Prometheus.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj @@ -1,9 +1,8 @@ - Unit test project for Prometheus Exporter for OpenTelemetry + Unit test project for Prometheus Exporter AspNetCore for OpenTelemetry net6.0 - $(TargetFrameworks);net462 false @@ -21,8 +20,8 @@ - + diff --git a/test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusCollectionManagerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs similarity index 98% rename from test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusCollectionManagerTests.cs rename to test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs index 60945c6f9..6f739cbc1 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusCollectionManagerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusCollectionManagerTests.cs @@ -109,7 +109,7 @@ namespace OpenTelemetry.Exporter.Prometheus.Tests exporter.CollectionManager.ExitCollect(); } - Thread.Sleep(exporter.Options.ScrapeResponseCacheDurationMilliseconds); + Thread.Sleep(exporter.ScrapeResponseCacheDurationMilliseconds); counter.Add(100); diff --git a/test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs similarity index 97% rename from test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusExporterMiddlewareTests.cs rename to test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index a11ea4304..ff8b0bdc4 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -31,7 +31,7 @@ using OpenTelemetry.Metrics; using OpenTelemetry.Tests; using Xunit; -namespace OpenTelemetry.Exporter.Prometheus.Tests +namespace OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests { public sealed class PrometheusExporterMiddlewareTests { @@ -224,10 +224,6 @@ namespace OpenTelemetry.Exporter.Prometheus.Tests .AddPrometheusExporter(o => { configureOptions?.Invoke(o); - if (o.StartHttpListener) - { - throw new InvalidOperationException("StartHttpListener should be false on .NET Core 3.1+."); - } })); } diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/OpenTelemetry.Instrumentation.AspNet.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj similarity index 53% rename from test/OpenTelemetry.Instrumentation.AspNet.Tests/OpenTelemetry.Instrumentation.AspNet.Tests.csproj rename to test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj index 857fb41d1..4588d2168 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/OpenTelemetry.Instrumentation.AspNet.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj @@ -1,15 +1,17 @@ - Unit test project for OpenTelemetry ASP.NET instrumentation + Unit test project for Prometheus Exporter HttpListener for OpenTelemetry net462 + + false - + all runtime; build; native; contentfiles; analyzers @@ -17,18 +19,12 @@ - - + - + - - - - - diff --git a/test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusExporterHttpServerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs similarity index 54% rename from test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusExporterHttpServerTests.cs rename to test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index 6ba169505..fb281a356 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusExporterHttpServerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,10 +26,59 @@ using Xunit; namespace OpenTelemetry.Exporter.Prometheus.Tests { - public class PrometheusExporterHttpServerTests + public class PrometheusHttpListenerTests { private readonly string meterName = Utils.GetCurrentMethodName(); + [Theory] + [InlineData("http://+:9184")] + [InlineData("http://*:9184")] + [InlineData("http://+:9184/")] + [InlineData("http://*:9184/")] + [InlineData("https://example.com")] + [InlineData("http://127.0.0.1")] + [InlineData("http://example.com", "https://example.com", "http://127.0.0.1")] + [InlineData("http://example.com")] + public void UriPrefixesPositiveTest(params string[] uriPrefixes) + { + using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() + .AddPrometheusHttpListener(options => options.UriPrefixes = uriPrefixes) + .Build(); + } + + [Fact] + public void UriPrefixesNull() + { + Assert.Throws(() => + { + using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() + .AddPrometheusHttpListener(options => options.UriPrefixes = null) + .Build(); + }); + } + + [Fact] + public void UriPrefixesEmptyList() + { + Assert.Throws(() => + { + using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() + .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { }) + .Build(); + }); + } + + [Fact] + public void UriPrefixesInvalid() + { + Assert.Throws(() => + { + using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() + .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { "ftp://example.com" }) + .Build(); + }); + } + [Fact] public async Task PrometheusExporterHttpServerIntegration() { @@ -42,103 +91,25 @@ namespace OpenTelemetry.Exporter.Prometheus.Tests await this.RunPrometheusExporterHttpServerIntegrationTest(skipMetrics: true); } - [Theory] - [InlineData("http://example.com")] - [InlineData("https://example.com")] - [InlineData("http://127.0.0.1")] - [InlineData("http://example.com", "https://example.com", "http://127.0.0.1")] - public void ServerEndpointSanityCheckPositiveTest(params string[] uris) - { - using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() - .AddPrometheusExporter(opt => - { - opt.HttpListenerPrefixes = uris; - }) - .Build(); - } - - [Theory] - [InlineData("")] - [InlineData(null)] - [InlineData("ftp://example.com")] - [InlineData("http://example.com", "https://example.com", "ftp://example.com")] - public void ServerEndpointSanityCheckNegativeTest(params string[] uris) - { - try - { - using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() - .AddPrometheusExporter(opt => - { - opt.HttpListenerPrefixes = uris; - }) - .Build(); - } - catch (Exception ex) - { - if (ex is not ArgumentNullException) - { - Assert.Equal("System.ArgumentException", ex.GetType().ToString()); -#if NETFRAMEWORK - Assert.Equal("Prometheus server path should be a valid URI with http/https scheme.\r\nParameter name: httpListenerPrefixes", ex.Message); -#else - Assert.Equal("Prometheus server path should be a valid URI with http/https scheme. (Parameter 'httpListenerPrefixes')", ex.Message); -#endif - } - } - } - private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false) { Random random = new Random(); + int retryAttempts = 5; int port = 0; - int retryCount = 5; - MeterProvider provider; string address = null; + MeterProvider provider; using var meter = new Meter(this.meterName); - while (true) + while (retryAttempts-- != 0) { - try - { - port = random.Next(2000, 5000); - provider = Sdk.CreateMeterProviderBuilder() - .AddMeter(meter.Name) - .AddPrometheusExporter(o => - { -#if NETFRAMEWORK - bool expectedDefaultState = true; -#else - bool expectedDefaultState = false; -#endif - if (o.StartHttpListener != expectedDefaultState) - { - throw new InvalidOperationException("StartHttpListener value is unexpected."); - } + port = random.Next(2000, 5000); + address = $"http://localhost:{port}/"; - if (!o.StartHttpListener) - { - o.StartHttpListener = true; - } - - address = $"http://localhost:{port}/"; - o.HttpListenerPrefixes = new string[] { address }; - }) - .Build(); - break; - } - catch (Exception ex) - { - if (ex.Message != PrometheusExporter.HttpListenerStartFailureExceptionMessage) - { - throw; - } - - if (retryCount-- <= 0) - { - throw new InvalidOperationException("HttpListener could not be started."); - } - } + provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { address }) + .Build(); } var tags = new KeyValuePair[] @@ -155,7 +126,6 @@ namespace OpenTelemetry.Exporter.Prometheus.Tests } using HttpClient client = new HttpClient(); - using var response = await client.GetAsync($"{address}metrics").ConfigureAwait(false); if (!skipMetrics) diff --git a/test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs similarity index 100% rename from test/OpenTelemetry.Exporter.Prometheus.Tests/PrometheusSerializerTests.cs rename to test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs diff --git a/test/OpenTelemetry.Exporter.ZPages.Tests/OpenTelemetry.Exporter.ZPages.Tests.csproj b/test/OpenTelemetry.Exporter.ZPages.Tests/OpenTelemetry.Exporter.ZPages.Tests.csproj index 6fad21338..dca1ff0d2 100644 --- a/test/OpenTelemetry.Exporter.ZPages.Tests/OpenTelemetry.Exporter.ZPages.Tests.csproj +++ b/test/OpenTelemetry.Exporter.ZPages.Tests/OpenTelemetry.Exporter.ZPages.Tests.csproj @@ -4,7 +4,6 @@ net6.0 $(TargetFrameworks);net462 - false diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj b/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj index 84e82aec2..7649dc09d 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj @@ -4,7 +4,6 @@ net6.0 $(TargetFrameworks);net462 - false diff --git a/test/OpenTelemetry.Extensions.EventSource.Tests/AssemblyInfo.cs b/test/OpenTelemetry.Extensions.EventSource.Tests/AssemblyInfo.cs new file mode 100644 index 000000000..11bfd5a20 --- /dev/null +++ b/test/OpenTelemetry.Extensions.EventSource.Tests/AssemblyInfo.cs @@ -0,0 +1,19 @@ +// +// 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; + +[assembly: CLSCompliant(false)] diff --git a/test/OpenTelemetry.Extensions.EventSource.Tests/OpenTelemetry.Extensions.EventSource.Tests.csproj b/test/OpenTelemetry.Extensions.EventSource.Tests/OpenTelemetry.Extensions.EventSource.Tests.csproj new file mode 100644 index 000000000..855502dd5 --- /dev/null +++ b/test/OpenTelemetry.Extensions.EventSource.Tests/OpenTelemetry.Extensions.EventSource.Tests.csproj @@ -0,0 +1,27 @@ + + + Unit test project for OpenTelemetry EventSource extensions + + net6.0 + $(TargetFrameworks);net462 + enable + AllEnabledByDefault + latest + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + diff --git a/test/OpenTelemetry.Extensions.EventSource.Tests/OpenTelemetryEventSourceLogEmitterTests.cs b/test/OpenTelemetry.Extensions.EventSource.Tests/OpenTelemetryEventSourceLogEmitterTests.cs new file mode 100644 index 000000000..e6a53aa63 --- /dev/null +++ b/test/OpenTelemetry.Extensions.EventSource.Tests/OpenTelemetryEventSourceLogEmitterTests.cs @@ -0,0 +1,391 @@ +// +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Globalization; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using Xunit; + +namespace OpenTelemetry.Extensions.EventSource.Tests +{ + public class OpenTelemetryEventSourceLogEmitterTests + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void OpenTelemetryEventSourceLogEmitterDisposesProviderTests(bool dispose) + { + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => null, + disposeProvider: dispose)) + { + } + + Assert.Equal(dispose, openTelemetryLoggerProvider.Disposed); + + if (!dispose) + { + openTelemetryLoggerProvider.Dispose(); + } + + Assert.True(openTelemetryLoggerProvider.Disposed); + } + + [Theory] + [InlineData("OpenTelemetry.Extensions.EventSource.Tests", EventLevel.LogAlways, 2)] + [InlineData("OpenTelemetry.Extensions.EventSource.Tests", EventLevel.Warning, 1)] + [InlineData("_invalid_", EventLevel.LogAlways, 0)] + public void OpenTelemetryEventSourceLogEmitterFilterTests(string sourceName, EventLevel? eventLevel, int expectedNumberOfLogRecords) + { + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => name == sourceName ? eventLevel : null)) + { + TestEventSource.Log.SimpleEvent(); + TestEventSource.Log.ComplexEvent("Test_Message", 18); + } + + Assert.Equal(expectedNumberOfLogRecords, exportedItems.Count); + } + + [Fact] + public void OpenTelemetryEventSourceLogEmitterCapturesExistingSourceTest() + { + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + TestEventSource.Log.SimpleEvent(); + + using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => name == "OpenTelemetry.Extensions.EventSource.Tests" ? EventLevel.LogAlways : null)) + { + TestEventSource.Log.SimpleEvent(); + } + + Assert.Single(exportedItems); + } + + [Fact] + public void OpenTelemetryEventSourceLogEmitterSimpleEventTest() + { + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => name == "OpenTelemetry.Extensions.EventSource.Tests" ? EventLevel.LogAlways : null)) + { + TestEventSource.Log.SimpleEvent(); + } + + Assert.Single(exportedItems); + + var logRecord = exportedItems[0]; + + Assert.NotEqual(DateTime.MinValue, logRecord.Timestamp); + Assert.Equal(TestEventSource.SimpleEventMessage, logRecord.FormattedMessage); + Assert.Equal(TestEventSource.SimpleEventId, logRecord.EventId.Id); + Assert.Equal(nameof(TestEventSource.SimpleEvent), logRecord.EventId.Name); + Assert.Equal(LogLevel.Warning, logRecord.LogLevel); + Assert.Null(logRecord.CategoryName); + Assert.Null(logRecord.Exception); + + Assert.Equal(default, logRecord.TraceId); + Assert.Equal(default, logRecord.SpanId); + Assert.Null(logRecord.TraceState); + Assert.Equal(ActivityTraceFlags.None, logRecord.TraceFlags); + + Assert.NotNull(logRecord.StateValues); + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "event_source.name" && (string?)kvp.Value == "OpenTelemetry.Extensions.EventSource.Tests"); + } + + [Fact] + public void OpenTelemetryEventSourceLogEmitterSimpleEventWithActivityTest() + { + using var activity = new Activity("Test"); + activity.Start(); + + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => name == "OpenTelemetry.Extensions.EventSource.Tests" ? EventLevel.LogAlways : null)) + { + TestEventSource.Log.SimpleEvent(); + } + + Assert.Single(exportedItems); + + var logRecord = exportedItems[0]; + + Assert.NotEqual(default, logRecord.TraceId); + + Assert.Equal(activity.TraceId, logRecord.TraceId); + Assert.Equal(activity.SpanId, logRecord.SpanId); + Assert.Equal(activity.TraceStateString, logRecord.TraceState); + Assert.Equal(activity.ActivityTraceFlags, logRecord.TraceFlags); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void OpenTelemetryEventSourceLogEmitterComplexEventTest(bool formatMessage) + { + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options => + { + options.IncludeFormattedMessage = formatMessage; + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => name == "OpenTelemetry.Extensions.EventSource.Tests" ? EventLevel.LogAlways : null)) + { + TestEventSource.Log.ComplexEvent("Test_Message", 18); + } + + Assert.Single(exportedItems); + + var logRecord = exportedItems[0]; + + Assert.NotEqual(DateTime.MinValue, logRecord.Timestamp); + if (!formatMessage) + { + Assert.Equal(TestEventSource.ComplexEventMessageStructured, logRecord.FormattedMessage); + } + else + { + string expectedMessage = string.Format(CultureInfo.InvariantCulture, TestEventSource.ComplexEventMessage, "Test_Message", 18); + Assert.Equal(expectedMessage, logRecord.FormattedMessage); + } + + Assert.Equal(TestEventSource.ComplexEventId, logRecord.EventId.Id); + Assert.Equal(nameof(TestEventSource.ComplexEvent), logRecord.EventId.Name); + Assert.Equal(LogLevel.Information, logRecord.LogLevel); + Assert.Null(logRecord.CategoryName); + Assert.Null(logRecord.Exception); + + Assert.Equal(default, logRecord.TraceId); + Assert.Equal(default, logRecord.SpanId); + Assert.Null(logRecord.TraceState); + Assert.Equal(ActivityTraceFlags.None, logRecord.TraceFlags); + + Assert.NotNull(logRecord.StateValues); + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "event_source.name" && (string?)kvp.Value == "OpenTelemetry.Extensions.EventSource.Tests"); + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "arg1" && (string?)kvp.Value == "Test_Message"); + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "arg2" && (int?)kvp.Value == 18); + } + + [Theory(Skip = "Not runnable in CI, see note.")] + [InlineData(true)] + [InlineData(false)] + public void OpenTelemetryEventSourceLogEmitterActivityIdTest(bool enableTplListener) + { + /* + * Note: + * + * To enable Activity ID the 'System.Threading.Tasks.TplEventSource' + * source must be enabled see: + * https://docs.microsoft.com/en-us/dotnet/core/diagnostics/eventsource-activity-ids#tracking-work-using-an-activity-id + * + * Once enabled, it cannot be turned off: + * https://github.com/dotnet/runtime/blob/0fbdb1ed6e076829e4693a61ae5d11c4cb23e7ee/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs#L208 + * + * That behavior makes testing it difficult. + */ + using var tplListener = enableTplListener ? new TplEventSourceListener() : null; + + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => name == "OpenTelemetry.Extensions.EventSource.Tests" ? EventLevel.LogAlways : null)) + { + TestEventSource.Log.WorkStart(); + + TestEventSource.Log.SubworkStart(); + + TestEventSource.Log.SubworkStop(); + + TestEventSource.Log.WorkStop(); + } + + Assert.Equal(4, exportedItems.Count); + + var logRecord = exportedItems[1]; + + if (enableTplListener) + { + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "event_source.activity_id"); + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "event_source.related_activity_id"); + } + else + { + Assert.DoesNotContain(logRecord.StateValues, kvp => kvp.Key == "event_source.activity_id"); + Assert.DoesNotContain(logRecord.StateValues, kvp => kvp.Key == "event_source.related_activity_id"); + } + } + + private sealed class WrappedOpenTelemetryLoggerProvider : OpenTelemetryLoggerProvider + { + public WrappedOpenTelemetryLoggerProvider(Action configure) + : base(configure) + { + } + + public bool Disposed { get; private set; } + + protected override void Dispose(bool disposing) + { + this.Disposed = true; + + base.Dispose(disposing); + } + } + + [EventSource(Name = "OpenTelemetry.Extensions.EventSource.Tests")] + private sealed class TestEventSource : System.Diagnostics.Tracing.EventSource + { + public const int SimpleEventId = 1; + public const string SimpleEventMessage = "Warning event with no arguments."; + + public const int ComplexEventId = 2; + public const string ComplexEventMessage = "Information event with two arguments: '{0}' & '{1}'."; + public const string ComplexEventMessageStructured = "Information event with two arguments: '{arg1}' & '{arg2}'."; + + public static TestEventSource Log { get; } = new(); + + [Event(SimpleEventId, Message = SimpleEventMessage, Level = EventLevel.Warning)] + public void SimpleEvent() + { + this.WriteEvent(SimpleEventId); + } + + [Event(ComplexEventId, Message = ComplexEventMessage, Level = EventLevel.Informational)] + public void ComplexEvent(string arg1, int arg2) + { + this.WriteEvent(ComplexEventId, arg1, arg2); + } + + [Event(3, Level = EventLevel.Verbose)] + public void WorkStart() + { + this.WriteEvent(3); + } + + [Event(4, Level = EventLevel.Verbose)] + public void WorkStop() + { + this.WriteEvent(4); + } + + [Event(5, Level = EventLevel.Verbose)] + public void SubworkStart() + { + this.WriteEvent(5); + } + + [Event(6, Level = EventLevel.Verbose)] + public void SubworkStop() + { + this.WriteEvent(6); + } + } + + private sealed class TplEventSourceListener : EventListener + { + private readonly List eventSources = new(); + + /// + public override void Dispose() + { + foreach (System.Diagnostics.Tracing.EventSource eventSource in this.eventSources) + { + this.DisableEvents(eventSource); + } + + this.eventSources.Clear(); + + base.Dispose(); + } + + protected override void OnEventSourceCreated(System.Diagnostics.Tracing.EventSource eventSource) + { + if (eventSource.Name == "System.Threading.Tasks.TplEventSource") + { + // Activity IDs aren't enabled by default. + // Enabling Keyword 0x80 on the TplEventSource turns them on + this.EnableEvents(eventSource, EventLevel.LogAlways, (EventKeywords)0x80); + this.eventSources.Add(eventSource); + } + } + } + } +} diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs new file mode 100644 index 000000000..eb39eef62 --- /dev/null +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/InMemoryExporterMetricsExtensionsTests.cs @@ -0,0 +1,112 @@ +// +// 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. +// + +#if NETCOREAPP3_1_OR_GREATER + +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Net; +using System.Threading.Tasks; + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +using OpenTelemetry.Metrics; +using OpenTelemetry.Tests; + +using Xunit; + +namespace OpenTelemetry.Extensions.Hosting.Tests +{ + /// + /// These tests verify that works with . + /// + public class InMemoryExporterMetricsExtensionsTests + { + [Fact] + public async Task DeferredMeterProviderBuilder_WithMetric() + { + var meterName = Utils.GetCurrentMethodName(); + var exportedItems = new List(); + + await RunMetricsTest( + configure: builder => builder + .AddMeter(meterName) + .AddInMemoryExporter(exportedItems), + testAction: () => + { + using var meter = new Meter(meterName); + var counter = meter.CreateCounter("meter"); + counter.Add(10); + }); + + Assert.Single(exportedItems); + var metricPointsEnumerator = exportedItems[0].GetMetricPoints().GetEnumerator(); + Assert.True(metricPointsEnumerator.MoveNext()); + Assert.Equal(10, metricPointsEnumerator.Current.GetSumLong()); + } + + [Fact] + public async Task DeferredMeterProviderBuilder_WithMetricSnapshot() + { + var meterName = Utils.GetCurrentMethodName(); + var exportedItems = new List(); + + await RunMetricsTest( + configure: builder => builder + .AddMeter(meterName) + .AddInMemoryExporter(exportedItems), + testAction: () => + { + using var meter = new Meter(meterName); + var counter = meter.CreateCounter("meter"); + counter.Add(10); + }); + + Assert.Single(exportedItems); + Assert.Equal(10, exportedItems[0].MetricPoints[0].GetSumLong()); + } + + private static async Task RunMetricsTest(Action configure, Action testAction) + { + using var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => webBuilder + .UseTestServer() + .ConfigureServices(services => services.AddOpenTelemetryMetrics(configure)) + .Configure(app => app.Run(httpContext => + { + testAction.Invoke(); + + var meterProvider = app.ApplicationServices.GetRequiredService(); + meterProvider.ForceFlush(); + + return Task.CompletedTask; + }))) + .StartAsync(); + + using var response = await host.GetTestClient().GetAsync($"/{nameof(RunMetricsTest)}").ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + await host.StopAsync().ConfigureAwait(false); + } + } +} +#endif diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj index a17774b6b..c56dfc18e 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj @@ -8,8 +8,11 @@ - - + + + + + @@ -23,4 +26,12 @@ + + + + + + + + diff --git a/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTest.cs b/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTest.cs new file mode 100644 index 000000000..05f174561 --- /dev/null +++ b/test/OpenTelemetry.Extensions.Propagators.Tests/JaegerPropagatorTest.cs @@ -0,0 +1,235 @@ +// +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using OpenTelemetry.Context.Propagation; +using Xunit; + +namespace OpenTelemetry.Extensions.Propagators.Tests +{ + public class JaegerPropagatorTest + { + private const string JaegerHeader = "uber-trace-id"; + private const string JaegerDelimiter = ":"; + private const string JaegerDelimiterEncoded = "%3A"; + + private const string TraceId = "0007651916cd43dd8448eb211c803177"; + private const string TraceIdShort = "7651916cd43dd8448eb211c803177"; + private const string SpanId = "0007c989f9791877"; + private const string SpanIdShort = "7c989f9791877"; + private const string ParentSpanId = "0"; + private const string FlagSampled = "1"; + private const string FlagNotSampled = "0"; + + private static readonly Func, string, IEnumerable> Getter = (headers, name) => + { + if (headers.TryGetValue(name, out var value)) + { + return value; + } + + return Array.Empty(); + }; + + private static readonly Action, string, string> Setter = (carrier, name, value) => + { + carrier[name] = value; + }; + + [Fact] + public void ExtractReturnsOriginalContextIfContextIsAlreadyValid() + { + // arrange + var traceId = ActivityTraceId.CreateFromString(TraceId.AsSpan()); + var spanId = ActivitySpanId.CreateFromString(SpanId.AsSpan()); + var propagationContext = new PropagationContext( + new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, isRemote: true), + default); + + var headers = new Dictionary(); + + // act + var result = new JaegerPropagator().Extract(propagationContext, headers, Getter); + + // assert + Assert.Equal(propagationContext, result); + } + + [Fact] + public void ExtractReturnsOriginalContextIfCarrierIsNull() + { + // arrange + var propagationContext = default(PropagationContext); + + // act + var result = new JaegerPropagator().Extract(propagationContext, null, Getter!); + + // assert + Assert.Equal(propagationContext, result); + } + + [Fact] + public void ExtractReturnsOriginalContextIfGetterIsNull() + { + // arrange + var propagationContext = default(PropagationContext); + + var headers = new Dictionary(); + + // act + var result = new JaegerPropagator().Extract(propagationContext, headers, null); + + // assert + Assert.Equal(propagationContext, result); + } + + [Theory] + [InlineData("", SpanId, ParentSpanId, FlagSampled, JaegerDelimiter)] + [InlineData(TraceId, "", ParentSpanId, FlagSampled, JaegerDelimiter)] + [InlineData(TraceId, SpanId, "", FlagSampled, JaegerDelimiter)] + [InlineData(TraceId, SpanId, ParentSpanId, "", JaegerDelimiter)] + [InlineData(TraceId, SpanId, ParentSpanId, FlagSampled, "")] + [InlineData("invalid trace id", SpanId, ParentSpanId, FlagSampled, JaegerDelimiter)] + [InlineData(TraceId, "invalid span id", ParentSpanId, FlagSampled, JaegerDelimiter)] + [InlineData(TraceId, SpanId, $"too many {JaegerDelimiter} records", FlagSampled, JaegerDelimiter)] + public void ExtractReturnsOriginalContextIfHeaderIsNotValid(string traceId, string spanId, string parentSpanId, string flags, string delimiter) + { + // arrange + var propagationContext = default(PropagationContext); + + var formattedHeader = string.Join( + delimiter, + traceId, + spanId, + parentSpanId, + flags); + + var headers = new Dictionary { { JaegerHeader, new[] { formattedHeader } } }; + + // act + var result = new JaegerPropagator().Extract(propagationContext, headers, Getter); + + // assert + Assert.Equal(propagationContext, result); + } + + [Theory] + [InlineData(TraceId, SpanId, ParentSpanId, FlagSampled, JaegerDelimiter)] + [InlineData(TraceIdShort, SpanIdShort, ParentSpanId, FlagNotSampled, JaegerDelimiterEncoded)] + public void ExtractReturnsNewContextIfHeaderIsValid(string traceId, string spanId, string parentSpanId, string flags, string delimiter) + { + // arrange + var propagationContext = default(PropagationContext); + + var formattedHeader = string.Join( + delimiter, + traceId, + spanId, + parentSpanId, + flags); + + var headers = new Dictionary { { JaegerHeader, new[] { formattedHeader } } }; + + // act + var result = new JaegerPropagator().Extract(propagationContext, headers, Getter); + + // assert + Assert.Equal(traceId.PadLeft(TraceId.Length, '0'), result.ActivityContext.TraceId.ToString()); + Assert.Equal(spanId.PadLeft(SpanId.Length, '0'), result.ActivityContext.SpanId.ToString()); + Assert.Equal(flags == "1" ? ActivityTraceFlags.Recorded : ActivityTraceFlags.None, result.ActivityContext.TraceFlags); + } + + [Fact] + public void InjectDoesNoopIfContextIsInvalid() + { + // arrange + var propagationContext = default(PropagationContext); + + var headers = new Dictionary(); + + // act + new JaegerPropagator().Inject(propagationContext, headers, Setter); + + // assert + Assert.Empty(headers); + } + + [Fact] + public void InjectDoesNoopIfCarrierIsNull() + { + // arrange + var traceId = ActivityTraceId.CreateFromString(TraceId.AsSpan()); + var spanId = ActivitySpanId.CreateFromString(SpanId.AsSpan()); + var propagationContext = new PropagationContext( + new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, isRemote: true), + default); + + // act + new JaegerPropagator().Inject(propagationContext, null, Setter!); + + // assert + } + + [Fact] + public void InjectDoesNoopIfSetterIsNull() + { + // arrange + var traceId = ActivityTraceId.CreateFromString(TraceId.AsSpan()); + var spanId = ActivitySpanId.CreateFromString(SpanId.AsSpan()); + var propagationContext = new PropagationContext( + new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, isRemote: true), + default); + + var headers = new Dictionary(); + + // act + new JaegerPropagator().Inject(propagationContext, headers, null); + + // assert + Assert.Empty(headers); + } + + [Theory] + [InlineData(FlagSampled)] + [InlineData(FlagNotSampled)] + public void InjectWillAddJaegerFormattedTraceToCarrier(string sampledFlag) + { + // arrange + var traceId = ActivityTraceId.CreateFromString(TraceId.AsSpan()); + var spanId = ActivitySpanId.CreateFromString(SpanId.AsSpan()); + var flags = sampledFlag == "1" ? ActivityTraceFlags.Recorded : ActivityTraceFlags.None; + + var propagationContext = new PropagationContext(new ActivityContext(traceId, spanId, flags, isRemote: true), default); + + var expectedValue = string.Join( + JaegerDelimiter, + traceId, + spanId, + ParentSpanId, + sampledFlag); + + var headers = new Dictionary(); + + // act + new JaegerPropagator().Inject(propagationContext, headers, Setter); + + // assert + Assert.Single(headers); + Assert.Equal(expectedValue, headers[JaegerHeader]); + } + } +} diff --git a/test/OpenTelemetry.Extensions.Serilog.Tests/AssemblyInfo.cs b/test/OpenTelemetry.Extensions.Serilog.Tests/AssemblyInfo.cs new file mode 100644 index 000000000..11bfd5a20 --- /dev/null +++ b/test/OpenTelemetry.Extensions.Serilog.Tests/AssemblyInfo.cs @@ -0,0 +1,19 @@ +// +// 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; + +[assembly: CLSCompliant(false)] diff --git a/test/OpenTelemetry.Extensions.Serilog.Tests/OpenTelemetry.Extensions.Serilog.Tests.csproj b/test/OpenTelemetry.Extensions.Serilog.Tests/OpenTelemetry.Extensions.Serilog.Tests.csproj new file mode 100644 index 000000000..daa1bc3df --- /dev/null +++ b/test/OpenTelemetry.Extensions.Serilog.Tests/OpenTelemetry.Extensions.Serilog.Tests.csproj @@ -0,0 +1,27 @@ + + + Unit test project for OpenTelemetry Serilog extensions + + net6.0 + $(TargetFrameworks);net462 + enable + AllEnabledByDefault + latest + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + diff --git a/test/OpenTelemetry.Extensions.Serilog.Tests/OpenTelemetrySerilogSinkTests.cs b/test/OpenTelemetry.Extensions.Serilog.Tests/OpenTelemetrySerilogSinkTests.cs new file mode 100644 index 000000000..000a1e7c1 --- /dev/null +++ b/test/OpenTelemetry.Extensions.Serilog.Tests/OpenTelemetrySerilogSinkTests.cs @@ -0,0 +1,301 @@ +// +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using Serilog; +using Xunit; +using ILogger = Serilog.ILogger; + +namespace OpenTelemetry.Extensions.Serilog.Tests +{ + public class OpenTelemetrySerilogSinkTests + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SerilogDisposesProviderTests(bool dispose) + { + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: dispose) + .CreateLogger(); + + Log.CloseAndFlush(); + + Assert.Equal(dispose, openTelemetryLoggerProvider.Disposed); + + if (!dispose) + { + openTelemetryLoggerProvider.Dispose(); + } + + Assert.True(openTelemetryLoggerProvider.Disposed); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SerilogBasicLogTests(bool includeFormattedMessage) + { + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => + { + options.IncludeFormattedMessage = includeFormattedMessage; + + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true) + .CreateLogger(); + + Log.Logger.Information("Hello {greeting}", "World"); + + Log.CloseAndFlush(); + + Assert.Single(exportedItems); + + LogRecord logRecord = exportedItems[0]; + + if (!includeFormattedMessage) + { + Assert.Equal("Hello {greeting}", logRecord.FormattedMessage); + } + else + { + Assert.Equal("Hello \"World\"", logRecord.FormattedMessage); + } + + Assert.NotEqual(DateTime.MinValue, logRecord.Timestamp); + Assert.Equal(DateTimeKind.Utc, logRecord.Timestamp.Kind); + Assert.Equal(LogLevel.Information, logRecord.LogLevel); + Assert.Null(logRecord.CategoryName); + + Assert.NotNull(logRecord.StateValues); + Assert.Single(logRecord.StateValues); + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "greeting" && (string?)kvp.Value == "World"); + + Assert.Equal(default, logRecord.TraceId); + Assert.Equal(default, logRecord.SpanId); + Assert.Null(logRecord.TraceState); + Assert.Equal(ActivityTraceFlags.None, logRecord.TraceFlags); + } + + [Fact] + public void SerilogBasicLogWithActivityTest() + { + using var activity = new Activity("Test"); + activity.Start(); + + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true) + .CreateLogger(); + + Log.Logger.Information("Hello {greeting}", "World"); + + Log.CloseAndFlush(); + + Assert.Single(exportedItems); + + var logRecord = exportedItems[0]; + + Assert.NotEqual(default, logRecord.TraceId); + + Assert.Equal(activity.TraceId, logRecord.TraceId); + Assert.Equal(activity.SpanId, logRecord.SpanId); + Assert.Equal(activity.TraceStateString, logRecord.TraceState); + Assert.Equal(activity.ActivityTraceFlags, logRecord.TraceFlags); + } + + [Fact] + public void SerilogCategoryNameTest() + { + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true) + .CreateLogger(); + + // Note: Serilog ForContext API is used to set "CategoryName" on log messages + ILogger logger = Log.Logger.ForContext(); + + logger.Information("Hello {greeting}", "World"); + + Log.CloseAndFlush(); + + Assert.Single(exportedItems); + + LogRecord logRecord = exportedItems[0]; + + Assert.Equal("OpenTelemetry.Extensions.Serilog.Tests.OpenTelemetrySerilogSinkTests", logRecord.CategoryName); + } + + [Fact] + public void SerilogComplexMessageTemplateTest() + { + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true) + .CreateLogger(); + + ComplexType complexType = new(); + + Log.Logger.Information("Hello {greeting} {id} {@complexObj} {$complexStr}", "World", 18, complexType, complexType); + + Log.CloseAndFlush(); + + Assert.Single(exportedItems); + + LogRecord logRecord = exportedItems[0]; + + Assert.NotNull(logRecord.StateValues); + Assert.Equal(3, logRecord.StateValues!.Count); // Note: complexObj is currently not supported/ignored. + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "greeting" && (string?)kvp.Value == "World"); + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "id" && (int?)kvp.Value == 18); + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "complexStr" && (string?)kvp.Value == "ComplexTypeToString"); + } + + [Fact] + public void SerilogArrayMessageTemplateTest() + { + List exportedItems = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true) + .CreateLogger(); + + ComplexType complexType = new(); + + var intArray = new int[] { 0, 1, 2, 3, 4 }; + var mixedArray = new object?[] { 0, null, "3", 18.0D }; + + Log.Logger.Information("Int array {data}", intArray); + Log.Logger.Information("Mixed array {data}", new object[] { mixedArray }); + + Log.CloseAndFlush(); + + Assert.Equal(2, exportedItems.Count); + + LogRecord logRecord = exportedItems[0]; + + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "data" && kvp.Value is int[] typedArray && intArray.SequenceEqual(typedArray)); + + logRecord = exportedItems[1]; + + Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "data" && kvp.Value is object?[] typedArray && mixedArray.SequenceEqual(typedArray)); + } + + [Fact] + public void SerilogExceptionTest() + { + List exportedItems = new(); + + InvalidOperationException ex = new(); + +#pragma warning disable CA2000 // Dispose objects before losing scope + var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true) + .CreateLogger(); + + ComplexType complexType = new(); + + Log.Logger.Information(ex, "Exception"); + + Log.CloseAndFlush(); + + Assert.Single(exportedItems); + + LogRecord logRecord = exportedItems[0]; + + Assert.Equal(ex, logRecord.Exception); + } + + private sealed class WrappedOpenTelemetryLoggerProvider : OpenTelemetryLoggerProvider + { + public WrappedOpenTelemetryLoggerProvider(Action configure) + : base(configure) + { + } + + public bool Disposed { get; private set; } + + protected override void Dispose(bool disposing) + { + this.Disposed = true; + + base.Dispose(disposing); + } + } + + private sealed class ComplexType + { + public override string ToString() => "ComplexTypeToString"; + } + } +} diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs deleted file mode 100644 index c5fd34408..000000000 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs +++ /dev/null @@ -1,546 +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. -// - -namespace OpenTelemetry.Instrumentation.AspNet.Tests -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Threading; - using System.Threading.Tasks; - using System.Web; - using OpenTelemetry.Context.Propagation; - using Xunit; - - public class ActivityHelperTest : IDisposable - { - private const string TraceParentHeaderName = "traceparent"; - private const string TraceStateHeaderName = "tracestate"; - private const string BaggageHeaderName = "baggage"; - private const string BaggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; - private const string TestActivityName = "Activity.Test"; - private readonly TextMapPropagator noopTextMapPropagator = new NoopTextMapPropagator(); - private ActivityListener activitySourceListener; - - public void Dispose() - { - this.activitySourceListener?.Dispose(); - } - - [Fact] - public void Has_Started_Returns_Correctly() - { - var context = HttpContextHelper.GetFakeHttpContext(); - - bool result = ActivityHelper.HasStarted(context, out Activity aspNetActivity); - - Assert.False(result); - Assert.Null(aspNetActivity); - - context.Items[ActivityHelper.ContextKey] = ActivityHelper.StartedButNotSampledObj; - - result = ActivityHelper.HasStarted(context, out aspNetActivity); - - Assert.True(result); - Assert.Null(aspNetActivity); - - Activity activity = new Activity(TestActivityName); - context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = activity }; - - result = ActivityHelper.HasStarted(context, out aspNetActivity); - - Assert.True(result); - Assert.NotNull(aspNetActivity); - Assert.Equal(activity, aspNetActivity); - } - - [Fact] - public async Task Can_Restore_Activity() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Task testTask; - using (ExecutionContext.SuppressFlow()) - { - testTask = Task.Run(() => - { - Task.Yield(); - - Assert.Null(Activity.Current); - - ActivityHelper.RestoreContextIfNeeded(context); - - Assert.Same(Activity.Current, rootActivity); - }); - } - - await testTask.ConfigureAwait(false); - } - - [Fact(Skip = "Temporarily disable until stable.")] - public async Task Can_Restore_Baggage() - { - this.EnableListener(); - - var requestHeaders = new Dictionary - { - { BaggageHeaderName, BaggageInHeader }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - using var rootActivity = ActivityHelper.StartAspNetActivity(new CompositeTextMapPropagator(new TextMapPropagator[] { new TraceContextPropagator(), new BaggagePropagator() }), context, null); - - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Task testTask; - using (ExecutionContext.SuppressFlow()) - { - testTask = Task.Run(() => - { - Task.Yield(); - - Assert.Null(Activity.Current); - Assert.Equal(0, Baggage.Current.Count); - - ActivityHelper.RestoreContextIfNeeded(context); - - Assert.Same(Activity.Current, rootActivity); - Assert.Empty(rootActivity.Baggage); - - Assert.Equal(2, Baggage.Current.Count); - Assert.Equal("789", Baggage.Current.GetBaggage("TestKey1")); - Assert.Equal("456", Baggage.Current.GetBaggage("TestKey2")); - }); - } - - await testTask.ConfigureAwait(false); - } - - [Fact] - public void Can_Stop_Lost_Activity() - { - this.EnableListener(a => - { - Assert.NotNull(Activity.Current); - Assert.Equal(Activity.Current, a); - Assert.Equal(TelemetryHttpModule.AspNetActivityName, Activity.Current.OperationName); - }); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } - - [Fact] - public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() - { - this.EnableListener(); - ActivityHelper.RestoreContextIfNeeded(HttpContextHelper.GetFakeHttpContext()); - - Assert.Null(Activity.Current); - } - - [Fact] - public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() - { - this.EnableListener(); - var root = new Activity("root").Start(); - - var context = HttpContextHelper.GetFakeHttpContext(); - context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = root }; - - ActivityHelper.RestoreContextIfNeeded(context); - - Assert.Equal(root, Activity.Current); - } - - [Fact] - public void Can_Stop_Activity_Without_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = new Activity(TestActivityName); - rootActivity.Start(); - context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = rootActivity }; - Thread.Sleep(100); - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } - - [Fact] - public void Can_Stop_Activity_With_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = new Activity(TestActivityName); - rootActivity.Start(); - context.Items[ActivityHelper.ContextKey] = new ActivityHelper.ContextHolder { Activity = rootActivity }; - Thread.Sleep(100); - this.EnableListener(); - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } - - [Fact] - public void Can_Stop_Root_Activity_With_All_Children() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - - var child = new Activity("child").Start(); - new Activity("grandchild").Start(); - - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.True(child.Duration == TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } - - [Fact] - public void Can_Stop_Root_While_Child_Is_Current() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - var child = new Activity("child").Start(); - - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - - Assert.True(child.Duration == TimeSpan.Zero); - Assert.NotNull(Activity.Current); - Assert.Equal(Activity.Current, child); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } - - [Fact] - public async Task Can_Stop_Root_Activity_If_It_Is_Broken() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var root = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - new Activity("child").Start(); - - for (int i = 0; i < 2; i++) - { - await Task.Run(() => - { - // when we enter this method, Current is 'child' activity - Activity.Current.Stop(); - - // here Current is 'parent', but only in this execution context - }); - } - - // when we return back here, in the 'parent' execution context - // Current is still 'child' activity - changes in child context (inside Task.Run) - // do not affect 'parent' context in which Task.Run is called. - // But 'child' Activity is stopped, thus consequent calls to Stop will - // not update Current - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, root, context, null); - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - Assert.Null(Activity.Current); - } - - [Fact] - public void Stop_Root_Activity_With_129_Nesting_Depth() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var root = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - - for (int i = 0; i < 129; i++) - { - new Activity("child" + i).Start(); - } - - // can stop any activity regardless of the stack depth - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, root, context, null); - - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - Assert.NotNull(Activity.Current); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - - Assert.Null(rootActivity); - Assert.Equal(ActivityHelper.StartedButNotSampledObj, context.Items[ActivityHelper.ContextKey]); - - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableListener(onSample: (context) => ActivitySamplingResult.None); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - - Assert.Null(rootActivity); - Assert.Equal(ActivityHelper.StartedButNotSampledObj, context.Items[ActivityHelper.ContextKey]); - - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - Assert.Null(context.Items[ActivityHelper.ContextKey]); - } - - [Fact] - public void Can_Create_RootActivity_From_W3C_Traceparent() - { - this.EnableListener(); - var requestHeaders = new Dictionary - { - { TraceParentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - using var rootActivity = ActivityHelper.StartAspNetActivity(new TraceContextPropagator(), context, null); - - Assert.NotNull(rootActivity); - Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", rootActivity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); - Assert.True(rootActivity.Recorded); // note: We're not using a parent-based sampler in this test so the recorded flag of traceparent is ignored. - - Assert.Null(rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); - - Assert.Equal(0, Baggage.Current.Count); - } - - [Fact] - public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() - { - this.EnableListener(); - var requestHeaders = new Dictionary - { - { TraceParentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { TraceStateHeaderName, "ts1=v1,ts2=v2" }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - using var rootActivity = ActivityHelper.StartAspNetActivity(new TraceContextPropagator(), context, null); - - Assert.NotNull(rootActivity); - Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-01", rootActivity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); - Assert.True(rootActivity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); - - Assert.Equal(0, Baggage.Current.Count); - } - - [Fact] - public void Can_Create_RootActivity_From_W3C_Traceparent_With_Baggage() - { - this.EnableListener(); - var requestHeaders = new Dictionary - { - { TraceParentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - { BaggageHeaderName, BaggageInHeader }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - using var rootActivity = ActivityHelper.StartAspNetActivity(new CompositeTextMapPropagator(new TextMapPropagator[] { new TraceContextPropagator(), new BaggagePropagator() }), context, null); - - Assert.NotNull(rootActivity); - Assert.Equal(ActivityIdFormat.W3C, rootActivity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", rootActivity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", rootActivity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", rootActivity.ParentSpanId.ToHexString()); - Assert.True(rootActivity.Recorded); // note: We're not using a parent-based sampler in this test so the recorded flag of traceparent is ignored. - - Assert.Null(rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); - - Assert.Equal(2, Baggage.Current.Count); - Assert.Equal("789", Baggage.Current.GetBaggage("TestKey1")); - Assert.Equal("456", Baggage.Current.GetBaggage("TestKey2")); - - ActivityHelper.StopAspNetActivity(this.noopTextMapPropagator, rootActivity, context, null); - - Assert.Equal(0, Baggage.Current.Count); - } - - [Fact] - public void Can_Create_RootActivity_And_Start_Activity() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - - Assert.NotNull(rootActivity); - Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); - } - - [Fact] - public void Can_Create_RootActivity_And_Saved_In_HttContext() - { - this.EnableListener(); - var context = HttpContextHelper.GetFakeHttpContext(); - using var rootActivity = ActivityHelper.StartAspNetActivity(this.noopTextMapPropagator, context, null); - - Assert.NotNull(rootActivity); - Assert.Same(rootActivity, ((ActivityHelper.ContextHolder)context.Items[ActivityHelper.ContextKey])?.Activity); - } - - [Fact] - public void Fire_Exception_Events() - { - int callbacksFired = 0; - - var context = HttpContextHelper.GetFakeHttpContext(); - - Activity activity = new Activity(TestActivityName); - - ActivityHelper.WriteActivityException(activity, context, new InvalidOperationException(), (a, c, e) => { callbacksFired++; }); - - ActivityHelper.WriteActivityException(null, context, new InvalidOperationException(), (a, c, e) => { callbacksFired++; }); - - // Callback should fire only for non-null activity - Assert.Equal(1, callbacksFired); - } - - private void EnableListener(Action onStarted = null, Action onStopped = null, Func onSample = null) - { - Debug.Assert(this.activitySourceListener == null, "Cannot attach multiple listeners in tests."); - - this.activitySourceListener = new ActivityListener - { - ShouldListenTo = (activitySource) => activitySource.Name == TelemetryHttpModule.AspNetSourceName, - ActivityStarted = (a) => onStarted?.Invoke(a), - ActivityStopped = (a) => onStopped?.Invoke(a), - Sample = (ref ActivityCreationOptions options) => - { - if (onSample != null) - { - return onSample(options.Parent); - } - - return ActivitySamplingResult.AllDataAndRecorded; - }, - }; - - ActivitySource.AddActivityListener(this.activitySourceListener); - } - - private class TestHttpRequest : HttpRequestBase - { - private readonly NameValueCollection headers = new(); - - public override NameValueCollection Headers => this.headers; - - public override UnvalidatedRequestValuesBase Unvalidated => new TestUnvalidatedRequestValues(this.headers); - } - - private class TestUnvalidatedRequestValues : UnvalidatedRequestValuesBase - { - public TestUnvalidatedRequestValues(NameValueCollection headers) - { - this.Headers = headers; - } - - public override NameValueCollection Headers { get; } - } - - private class TestHttpResponse : HttpResponseBase - { - } - - private class TestHttpServerUtility : HttpServerUtilityBase - { - private readonly HttpContextBase context; - - public TestHttpServerUtility(HttpContextBase context) - { - this.context = context; - } - - public override Exception GetLastError() - { - return this.context.Error; - } - } - - private class TestHttpContext : HttpContextBase - { - private readonly Hashtable items; - - public TestHttpContext(Exception error = null) - { - this.Server = new TestHttpServerUtility(this); - this.items = new Hashtable(); - this.Error = error; - } - - public override HttpRequestBase Request { get; } = new TestHttpRequest(); - - /// - public override IDictionary Items => this.items; - - public override Exception Error { get; } - - public override HttpServerUtilityBase Server { get; } - } - - private class NoopTextMapPropagator : TextMapPropagator - { - private static readonly PropagationContext DefaultPropagationContext = default; - - public override ISet Fields => null; - - public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) - { - return DefaultPropagationContext; - } - - public override void Inject(PropagationContext context, T carrier, Action setter) - { - } - } - } -} diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/HttpContextHelper.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/HttpContextHelper.cs deleted file mode 100644 index ebb5ede4f..000000000 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/HttpContextHelper.cs +++ /dev/null @@ -1,103 +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. -// - -namespace OpenTelemetry.Instrumentation.AspNet.Tests -{ - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Threading; - using System.Web; - using System.Web.Hosting; - - internal class HttpContextHelper - { - public static HttpContext GetFakeHttpContext(string page = "/page", string query = "", IDictionary headers = null) - { - Thread.GetDomain().SetData(".appPath", string.Empty); - Thread.GetDomain().SetData(".appVPath", string.Empty); - - var workerRequest = new SimpleWorkerRequestWithHeaders(page, query, new StringWriter(CultureInfo.InvariantCulture), headers); - var context = new HttpContext(workerRequest); - HttpContext.Current = context; - return context; - } - - public static HttpContextBase GetFakeHttpContextBase(string page = "/page", string query = "", IDictionary headers = null) - { - var context = GetFakeHttpContext(page, query, headers); - return new HttpContextWrapper(context); - } - - private class SimpleWorkerRequestWithHeaders : SimpleWorkerRequest - { - private readonly IDictionary headers; - - public SimpleWorkerRequestWithHeaders(string page, string query, TextWriter output, IDictionary headers) - : base(page, query, output) - { - if (headers != null) - { - this.headers = headers; - } - else - { - this.headers = new Dictionary(); - } - } - - public override string[][] GetUnknownRequestHeaders() - { - List result = new List(); - - foreach (var header in this.headers) - { - result.Add(new string[] { header.Key, header.Value }); - } - - var baseResult = base.GetUnknownRequestHeaders(); - if (baseResult != null) - { - result.AddRange(baseResult); - } - - return result.ToArray(); - } - - public override string GetUnknownRequestHeader(string name) - { - if (this.headers.ContainsKey(name)) - { - return this.headers[name]; - } - - return base.GetUnknownRequestHeader(name); - } - - public override string GetKnownRequestHeader(int index) - { - var name = GetKnownRequestHeaderName(index); - - if (this.headers.ContainsKey(name)) - { - return this.headers[name]; - } - - return base.GetKnownRequestHeader(index); - } - } - } -} diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj deleted file mode 100644 index 0a4edd08a..000000000 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - Unit test project for ASP.NET HttpModule - - net462 - false - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - - - - Resources\web.config.install.xdt - - - Resources\web.config.uninstall.xdt - - - diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs deleted file mode 100644 index 4a35d43cd..000000000 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs +++ /dev/null @@ -1,409 +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. -// - -namespace OpenTelemetry.Instrumentation.AspNet.Tests -{ - using System.IO; - using System.Xml.Linq; - using Microsoft.Web.XmlTransform; - using Xunit; - - public class WebConfigTransformTest - { - private const string InstallConfigTransformationResourceName = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.Resources.web.config.install.xdt"; - private const string UninstallConfigTransformationResourceName = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.Resources.web.config.uninstall.xdt"; - - [Fact] - public void VerifyInstallationToBasicWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUpdateWithTypeRenamingWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUpdateNewerVersionWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUpdateWithIntegratedModeWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUninstallationWithBasicWebConfig() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - "; - - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUninstallWithIntegratedPrecondition() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - "; - - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyUninstallationWithUserModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyUninstallTransformation(OriginalWebConfigContent, UninstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToWebConfigWithUserModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToEmptyWebConfig() - { - const string OriginalWebConfigContent = @""; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToWebConfigWithoutModules() - { - const string OriginalWebConfigContent = @""; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) - { - Assert.True( - XNode.DeepEquals( - transformedWebConfig.FirstNode, - XDocument.Parse(expectedConfigContent).FirstNode)); - } - - private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) - { - XDocument result; - Stream stream = null; - try - { - stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); - var document = new XmlTransformableDocument(); - using var transformation = new XmlTransformation(stream, null); - stream = null; - document.LoadXml(originalConfiguration); - transformation.Apply(document); - result = XDocument.Parse(document.OuterXml); - } - finally - { - if (stream != null) - { - stream.Dispose(); - } - } - - return result; - } - } -} diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs deleted file mode 100644 index 8b070653d..000000000 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs +++ /dev/null @@ -1,439 +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. -// - -namespace OpenTelemetry.Instrumentation.AspNet.Tests -{ - using System.IO; - using System.Xml.Linq; - using Microsoft.Web.XmlTransform; - using Xunit; - - public class WebConfigWithLocationTagTransformTest - { - private const string InstallConfigTransformationResourceName = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.Resources.web.config.install.xdt"; - - [Fact] - public void VerifyInstallationWhenNonGlobalLocationTagExists() - { - const string OriginalWebConfigContent = @" - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationWhenGlobalAndNonGlobalLocationTagExists() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithDotPathAndExistingModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathAndExistingModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithDotPathWithNoModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathWithNoModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithDotPathWithGlobalModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - [Fact] - public void VerifyInstallationToLocationTagWithEmptyPathWithGlobalModules() - { - const string OriginalWebConfigContent = @" - - - - - - - - - - - - - - "; - - const string ExpectedWebConfigContent = @" - - - - - - - - - - - - - - - - - - "; - - var transformedWebConfig = this.ApplyInstallTransformation(OriginalWebConfigContent, InstallConfigTransformationResourceName); - this.VerifyTransformation(ExpectedWebConfigContent, transformedWebConfig); - } - - private XDocument ApplyInstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private XDocument ApplyUninstallTransformation(string originalConfiguration, string resourceName) - { - return this.ApplyTransformation(originalConfiguration, resourceName); - } - - private void VerifyTransformation(string expectedConfigContent, XDocument transformedWebConfig) - { - Assert.True( - XNode.DeepEquals( - transformedWebConfig.FirstNode, - XDocument.Parse(expectedConfigContent).FirstNode)); - } - - private XDocument ApplyTransformation(string originalConfiguration, string transformationResourceName) - { - XDocument result; - Stream stream = null; - try - { - stream = typeof(WebConfigTransformTest).Assembly.GetManifestResourceStream(transformationResourceName); - var document = new XmlTransformableDocument(); - using var transformation = new XmlTransformation(stream, null); - stream = null; - document.LoadXml(originalConfiguration); - transformation.Apply(document); - result = XDocument.Parse(document.OuterXml); - } - finally - { - if (stream != null) - { - stream.Dispose(); - } - } - - return result; - } - } -} diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs b/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs deleted file mode 100644 index bc1588d00..000000000 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs +++ /dev/null @@ -1,370 +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; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Web; -using System.Web.Routing; -using Moq; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Instrumentation.AspNet.Implementation; -using OpenTelemetry.Tests; -using OpenTelemetry.Trace; -using Xunit; - -namespace OpenTelemetry.Instrumentation.AspNet.Tests -{ - public class HttpInListenerTests - { - [Theory] - [InlineData("http://localhost/", "http://localhost/", 0, null)] - [InlineData("http://localhost/", "http://localhost/", 0, null, true)] - [InlineData("https://localhost/", "https://localhost/", 0, null)] - [InlineData("https://localhost/", "https://user:pass@localhost/", 0, null)] // Test URL sanitization - [InlineData("http://localhost:443/", "http://localhost:443/", 0, null)] // Test http over 443 - [InlineData("https://localhost:80/", "https://localhost:80/", 0, null)] // Test https over 80 - [InlineData("https://localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", "https://localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", 0, null)] // Test complex URL - [InlineData("https://localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", "https://user:password@localhost:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", 0, null)] // Test complex URL sanitization - [InlineData("http://localhost:80/Index", "http://localhost:80/Index", 1, "{controller}/{action}/{id}")] - [InlineData("https://localhost:443/about_attr_route/10", "https://localhost:443/about_attr_route/10", 2, "about_attr_route/{customerId}")] - [InlineData("http://localhost:1880/api/weatherforecast", "http://localhost:1880/api/weatherforecast", 3, "api/{controller}/{id}")] - [InlineData("https://localhost:1843/subroute/10", "https://localhost:1843/subroute/10", 4, "subroute/{customerId}")] - [InlineData("http://localhost/api/value", "http://localhost/api/value", 0, null, false, "/api/value")] // Request will be filtered - [InlineData("http://localhost/api/value", "http://localhost/api/value", 0, null, false, "{ThrowException}")] // Filter user code will throw an exception - [InlineData("http://localhost/", "http://localhost/", 0, null, false, null, true)] // Test RecordException option - public void AspNetRequestsAreCollectedSuccessfully( - string expectedUrl, - string url, - int routeType, - string routeTemplate, - bool setStatusToErrorInEnrich = false, - string filter = null, - bool recordException = false) - { - IDisposable tracerProvider = null; - RouteData routeData; - switch (routeType) - { - case 0: // WebForm, no route data. - routeData = new RouteData(); - break; - case 1: // Traditional MVC. - case 2: // Attribute routing MVC. - case 3: // Traditional WebAPI. - routeData = new RouteData() - { - Route = new Route(routeTemplate, null), - }; - break; - case 4: // Attribute routing WebAPI. - routeData = new RouteData(); - var value = new[] - { - new - { - Route = new - { - RouteTemplate = routeTemplate, - }, - }, - }; - routeData.Values.Add( - "MS_SubRoutes", - value); - break; - default: - throw new NotSupportedException(); - } - - var workerRequest = new Mock(); - workerRequest.Setup(wr => wr.GetKnownRequestHeader(It.IsAny())).Returns(i => - { - return i switch - { - 39 => "Test", // User-Agent - _ => null, - }; - }); - - HttpContext.Current = new HttpContext( - new HttpRequest(string.Empty, url, string.Empty) - { - RequestContext = new RequestContext() - { - RouteData = routeData, - }, - }, - new HttpResponse(new StringWriter())); - - typeof(HttpRequest).GetField("_wr", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(HttpContext.Current.Request, workerRequest.Object); - - List exportedItems = new List(16); - - Sdk.SetDefaultTextMapPropagator(new TraceContextPropagator()); - using (tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddAspNetInstrumentation((options) => - { - options.Filter = httpContext => - { - Assert.True(Activity.Current.IsAllDataRequested); - if (string.IsNullOrEmpty(filter)) - { - return true; - } - - if (filter == "{ThrowException}") - { - throw new InvalidOperationException(); - } - - return httpContext.Request.Path != filter; - }; - - options.Enrich = GetEnrichmentAction(setStatusToErrorInEnrich ? Status.Error : default); - - options.RecordException = recordException; - }) - .AddInMemoryExporter(exportedItems) - .Build()) - { - using var inMemoryEventListener = new InMemoryEventListener(AspNetInstrumentationEventSource.Log); - - var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); - - if (filter == "{ThrowException}") - { - Assert.Single(inMemoryEventListener.Events.Where((e) => e.EventId == 2)); - } - - Assert.Equal(TelemetryHttpModule.AspNetActivityName, Activity.Current.OperationName); - - if (recordException) - { - ActivityHelper.WriteActivityException(activity, HttpContext.Current, new InvalidOperationException(), TelemetryHttpModule.Options.OnExceptionCallback); - } - - ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); - } - - if (HttpContext.Current.Request.Path == filter || filter == "{ThrowException}") - { - Assert.Empty(exportedItems); - return; - } - - Assert.Single(exportedItems); - - Activity span = exportedItems[0]; - - Assert.Equal(TelemetryHttpModule.AspNetActivityName, span.OperationName); - Assert.NotEqual(TimeSpan.Zero, span.Duration); - - Assert.Equal(routeTemplate ?? HttpContext.Current.Request.Path, span.DisplayName); - Assert.Equal(ActivityKind.Server, span.Kind); - Assert.True(span.Duration != TimeSpan.Zero); - - Assert.Equal(200, span.GetTagValue(SemanticConventions.AttributeHttpStatusCode)); - - var expectedUri = new Uri(expectedUrl); - var actualUrl = span.GetTagValue(SemanticConventions.AttributeHttpUrl); - - Assert.Equal(expectedUri.ToString(), actualUrl); - - // Url strips 80 or 443 if the scheme matches. - if ((expectedUri.Port == 80 && expectedUri.Scheme == "http") || (expectedUri.Port == 443 && expectedUri.Scheme == "https")) - { - Assert.DoesNotContain($":{expectedUri.Port}", actualUrl as string); - } - else - { - Assert.Contains($":{expectedUri.Port}", actualUrl as string); - } - - // Host includes port if it isn't 80 or 443. - if (expectedUri.Port is 80 or 443) - { - Assert.Equal( - expectedUri.Host, - span.GetTagValue(SemanticConventions.AttributeHttpHost) as string); - } - else - { - Assert.Equal( - $"{expectedUri.Host}:{expectedUri.Port}", - span.GetTagValue(SemanticConventions.AttributeHttpHost) as string); - } - - Assert.Equal(HttpContext.Current.Request.HttpMethod, span.GetTagValue(SemanticConventions.AttributeHttpMethod) as string); - Assert.Equal(HttpContext.Current.Request.Path, span.GetTagValue(SemanticConventions.AttributeHttpTarget) as string); - Assert.Equal(HttpContext.Current.Request.UserAgent, span.GetTagValue(SemanticConventions.AttributeHttpUserAgent) as string); - - if (recordException) - { - var status = span.GetStatus(); - Assert.Equal(Status.Error.StatusCode, status.StatusCode); - Assert.Equal("Operation is not valid due to the current state of the object.", status.Description); - } - else if (setStatusToErrorInEnrich) - { - // This validates that users can override the - // status in Enrich. - Assert.Equal(Status.Error, span.GetStatus()); - - // Instrumentation is not expected to set status description - // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode - Assert.True(string.IsNullOrEmpty(span.GetStatus().Description)); - } - else - { - Assert.Equal(Status.Unset, span.GetStatus()); - - // Instrumentation is not expected to set status description - // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode - Assert.True(string.IsNullOrEmpty(span.GetStatus().Description)); - } - } - - [Theory] - [InlineData(SamplingDecision.Drop)] - [InlineData(SamplingDecision.RecordOnly)] - [InlineData(SamplingDecision.RecordAndSample)] - public void ExtractContextIrrespectiveOfSamplingDecision(SamplingDecision samplingDecision) - { - HttpContext.Current = new HttpContext( - new HttpRequest(string.Empty, "http://localhost/", string.Empty) - { - RequestContext = new RequestContext() - { - RouteData = new RouteData(), - }, - }, - new HttpResponse(new StringWriter())); - - bool isPropagatorCalled = false; - var propagator = new Mock(); - propagator.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())) - .Returns(() => - { - isPropagatorCalled = true; - return default; - }); - - var activityProcessor = new Mock>(); - Sdk.SetDefaultTextMapPropagator(propagator.Object); - using (var tracerProvider = Sdk.CreateTracerProviderBuilder() - .SetSampler(new TestSampler(samplingDecision)) - .AddAspNetInstrumentation() - .AddProcessor(activityProcessor.Object).Build()) - { - var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); - ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); - } - - Assert.True(isPropagatorCalled); - } - - [Fact] - public void ExtractContextIrrespectiveOfTheFilterApplied() - { - HttpContext.Current = new HttpContext( - new HttpRequest(string.Empty, "http://localhost/", string.Empty) - { - RequestContext = new RequestContext() - { - RouteData = new RouteData(), - }, - }, - new HttpResponse(new StringWriter())); - - bool isPropagatorCalled = false; - var propagator = new Mock(); - propagator.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())) - .Returns(() => - { - isPropagatorCalled = true; - return default; - }); - - bool isFilterCalled = false; - var activityProcessor = new Mock>(); - Sdk.SetDefaultTextMapPropagator(propagator.Object); - using (var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddAspNetInstrumentation(options => - { - options.Filter = context => - { - isFilterCalled = true; - return false; - }; - }) - .AddProcessor(activityProcessor.Object).Build()) - { - var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); - ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); - } - - Assert.True(isFilterCalled); - Assert.True(isPropagatorCalled); - } - - private static Action GetEnrichmentAction(Status statusToBeSet) - { - void EnrichAction(Activity activity, string method, object obj) - { - Assert.True(activity.IsAllDataRequested); - switch (method) - { - case "OnStartActivity": - Assert.True(obj is HttpRequest); - break; - - case "OnStopActivity": - Assert.True(obj is HttpResponse); - if (statusToBeSet != default) - { - activity.SetStatus(statusToBeSet); - } - - break; - - default: - break; - } - } - - return EnrichAction; - } - - private class TestSampler : Sampler - { - private readonly SamplingDecision samplingDecision; - - public TestSampler(SamplingDecision samplingDecision) - { - this.samplingDecision = samplingDecision; - } - - public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) - { - return new SamplingResult(this.samplingDecision); - } - } - } -} diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInMetricsListenerTests.cs b/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInMetricsListenerTests.cs deleted file mode 100644 index b9fbbbeeb..000000000 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInMetricsListenerTests.cs +++ /dev/null @@ -1,112 +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.Collections.Generic; -using System.IO; -using System.Web; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; -using Xunit; - -namespace OpenTelemetry.Instrumentation.AspNet.Tests -{ - public class HttpInMetricsListenerTests - { - [Fact] - public void HttpDurationMetricIsEmitted() - { - string url = "http://localhost/api/value"; - double duration = 0; - HttpContext.Current = new HttpContext( - new HttpRequest(string.Empty, url, string.Empty), - new HttpResponse(new StringWriter())); - - // This is to enable activity creation - // as it is created using activitysource inside TelemetryHttpModule - // TODO: This should not be needed once the dependency on activity is removed from metrics - using var traceprovider = Sdk.CreateTracerProviderBuilder() - .AddAspNetInstrumentation(opts => opts.Enrich - = (activity, eventName, rawObject) => - { - if (eventName.Equals("OnStopActivity")) - { - duration = activity.Duration.TotalMilliseconds; - } - }) - .Build(); - - var exportedItems = new List(); - using var meterprovider = Sdk.CreateMeterProviderBuilder() - .AddAspNetInstrumentation() - .AddInMemoryExporter(exportedItems) - .Build(); - - var activity = ActivityHelper.StartAspNetActivity(Propagators.DefaultTextMapPropagator, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStartedCallback); - ActivityHelper.StopAspNetActivity(Propagators.DefaultTextMapPropagator, activity, HttpContext.Current, TelemetryHttpModule.Options.OnRequestStoppedCallback); - - meterprovider.ForceFlush(); - - var metricPoints = new List(); - foreach (var p in exportedItems[0].GetMetricPoints()) - { - metricPoints.Add(p); - } - - Assert.Single(metricPoints); - - var metricPoint = metricPoints[0]; - - var count = metricPoint.GetHistogramCount(); - var sum = metricPoint.GetHistogramSum(); - - Assert.Equal(MetricType.Histogram, exportedItems[0].MetricType); - Assert.Equal("http.server.duration", exportedItems[0].Name); - Assert.Equal(1L, count); - Assert.Equal(duration, sum); - - Assert.Equal(3, metricPoints[0].Tags.Count); - string httpMethod = null; - int httpStatusCode = 0; - string httpScheme = null; - - foreach (var tag in metricPoints[0].Tags) - { - if (tag.Key == SemanticConventions.AttributeHttpMethod) - { - httpMethod = (string)tag.Value; - continue; - } - - if (tag.Key == SemanticConventions.AttributeHttpStatusCode) - { - httpStatusCode = (int)tag.Value; - continue; - } - - if (tag.Key == SemanticConventions.AttributeHttpScheme) - { - httpScheme = (string)tag.Value; - continue; - } - } - - Assert.Equal("GET", httpMethod); - Assert.Equal(200, httpStatusCode); - Assert.Equal("http", httpScheme); - } - } -} diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs index 21eaa64a3..90465e901 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs @@ -19,6 +19,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net.Http; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -26,7 +27,6 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Moq; -using Newtonsoft.Json; using OpenTelemetry.Context.Propagation; using OpenTelemetry.Instrumentation.AspNetCore.Implementation; using OpenTelemetry.Tests; @@ -367,7 +367,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests // Test TraceContext Propagation var request = new HttpRequestMessage(HttpMethod.Get, "/api/GetChildActivityTraceContext"); var response = await client.SendAsync(request); - var childActivityTraceContext = JsonConvert.DeserializeObject>(response.Content.ReadAsStringAsync().Result); + var childActivityTraceContext = JsonSerializer.Deserialize>(response.Content.ReadAsStringAsync().Result); response.EnsureSuccessStatusCode(); @@ -379,7 +379,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests request = new HttpRequestMessage(HttpMethod.Get, "/api/GetChildActivityBaggageContext"); response = await client.SendAsync(request); - var childActivityBaggageContext = JsonConvert.DeserializeObject>(response.Content.ReadAsStringAsync().Result); + var childActivityBaggageContext = JsonSerializer.Deserialize>(response.Content.ReadAsStringAsync().Result); response.EnsureSuccessStatusCode(); @@ -434,7 +434,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests // Ensure that filter was called Assert.True(isFilterCalled); - var childActivityTraceContext = JsonConvert.DeserializeObject>(response.Content.ReadAsStringAsync().Result); + var childActivityTraceContext = JsonSerializer.Deserialize>(response.Content.ReadAsStringAsync().Result); response.EnsureSuccessStatusCode(); @@ -446,7 +446,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests request = new HttpRequestMessage(HttpMethod.Get, "/api/GetChildActivityBaggageContext"); response = await client.SendAsync(request); - var childActivityBaggageContext = JsonConvert.DeserializeObject>(response.Content.ReadAsStringAsync().Result); + var childActivityBaggageContext = JsonSerializer.Deserialize>(response.Content.ReadAsStringAsync().Result); response.EnsureSuccessStatusCode(); @@ -553,6 +553,39 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests Assert.Equal(shouldEnrichBeCalled, enrichCalled); } + [Fact(Skip = "Changes pending on instrumentation")] + public async Task ActivitiesStartedInMiddlewareShouldNotBeUpdatedByInstrumentation() + { + var exportedItems = new List(); + + var activitySourceName = "TestMiddlewareActivitySource"; + var activityName = "TestMiddlewareActivity"; + + // Arrange + using (var client = this.factory + .WithWebHostBuilder(builder => + builder.ConfigureTestServices((IServiceCollection services) => + { + services.AddSingleton(new TestActivityMiddlewareImpl(activitySourceName, activityName)); + services.AddOpenTelemetryTracing((builder) => builder.AddAspNetCoreInstrumentation() + .AddSource(activitySourceName) + .AddInMemoryExporter(exportedItems)); + })) + .CreateClient()) + { + var response = await client.GetAsync("/api/values/2"); + response.EnsureSuccessStatusCode(); + WaitForActivityExport(exportedItems, 2); + } + + Assert.Equal(2, exportedItems.Count); + + var middlewareActivity = exportedItems[0]; + + // Middleware activity name should not be changed + Assert.Equal(activityName, middlewareActivity.DisplayName); + } + public void Dispose() { this.tracerProvider?.Dispose(); @@ -664,5 +697,28 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests this.OnStopActivityCallback?.Invoke(activity, payload); } } + + private class TestActivityMiddlewareImpl : ActivityMiddleware.ActivityMiddlewareImpl + { + private ActivitySource activitySource; + 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) + { + this.activity = this.activitySource.StartActivity(this.activityName); + } + + public override void PostProcess(HttpContext context) + { + this.activity?.Stop(); + } + } } } diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/InProcServerTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/InProcServerTests.cs new file mode 100644 index 000000000..20b3ef3c6 --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/InProcServerTests.cs @@ -0,0 +1,88 @@ +// +// 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. +// + +#if NET6_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using OpenTelemetry.Trace; +using Xunit; + +namespace OpenTelemetry.Instrumentation.AspNetCore.Tests +{ + public sealed class InProcServerTests : IDisposable + { + private TracerProvider tracerProvider; + private WebApplication app; + private HttpClient client; + private List exportedItems; + + public InProcServerTests() + { + this.exportedItems = new List(); + var builder = WebApplication.CreateBuilder(); + var app = builder.Build(); + + this.tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddAspNetCoreInstrumentation() + .AddInMemoryExporter(this.exportedItems).Build(); + app.MapGet("/", () => "Hello World!"); + app.RunAsync(); + + this.app = app; + this.client = new HttpClient(); + } + + [Fact] + public async void ExampleTest() + { + var res = await this.client.GetStringAsync("http://localhost:5000"); + Assert.NotNull(res); + + this.tracerProvider.ForceFlush(); + for (var i = 0; i < 10; i++) + { + if (this.exportedItems.Count > 0) + { + 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)); + } + + var activity = this.exportedItems[0]; + Assert.Equal(ActivityKind.Server, activity.Kind); + Assert.Equal("localhost:5000", activity.GetTagValue(SemanticConventions.AttributeHttpHost)); + Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpMethod)); + Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeHttpFlavor)); + Assert.Equal(200, activity.GetTagValue(SemanticConventions.AttributeHttpStatusCode)); + } + + public async void Dispose() + { + this.tracerProvider.Dispose(); + this.client.Dispose(); + await this.app.DisposeAsync(); + } + } +} +#endif diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs index fc6825532..80f09a9a9 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs @@ -115,6 +115,8 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests Assert.Equal(ActivityKind.Server, activity.Kind); Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeHttpHost)); 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)); diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs index 9dfb1f1db..4d93ba9a7 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs @@ -120,7 +120,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests 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, "HTTP/1.1"); + var flavor = new KeyValuePair(SemanticConventions.AttributeHttpFlavor, "1.1"); var host = new KeyValuePair(SemanticConventions.AttributeHttpHost, "localhost"); var target = new KeyValuePair(SemanticConventions.AttributeHttpTarget, "api/Values"); Assert.Contains(method, attributes); diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.netcore31.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.netcore31.cs index 4cf0ff75a..05c399354 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.netcore31.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.netcore31.cs @@ -22,9 +22,9 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; +using System.Text.Json; using System.Threading.Tasks; using Moq; -using Newtonsoft.Json; using OpenTelemetry.Metrics; using OpenTelemetry.Tests; using OpenTelemetry.Trace; @@ -66,7 +66,6 @@ namespace OpenTelemetry.Instrumentation.Http.Tests using (Sdk.CreateTracerProviderBuilder() .AddHttpClientInstrumentation((opt) => { - opt.SetHttpFlavor = tc.SetHttpFlavor; opt.Enrich = ActivityEnrichment; opt.RecordException = tc.RecordException ?? false; }) @@ -129,7 +128,7 @@ namespace OpenTelemetry.Instrumentation.Http.Tests foreach (var kv in normalizedAttributesTestCase) { - Assert.Contains(activity.TagObjects, i => i.Key == kv.Key && i.Value.ToString().Equals(kv.Value, StringComparison.InvariantCultureIgnoreCase)); + Assert.Contains(activity.TagObjects, i => i.Key == kv.Key && i.Value.ToString().Equals(kv.Value, StringComparison.OrdinalIgnoreCase)); } if (tc.RecordException.HasValue && tc.RecordException.Value) @@ -186,27 +185,30 @@ namespace OpenTelemetry.Instrumentation.Http.Tests [Fact] public async Task DebugIndividualTestAsync() { - var serializer = new JsonSerializer(); - var input = serializer.Deserialize(new JsonTextReader(new StringReader(@" -[ - { - ""name"": ""Response code: 399"", - ""method"": ""GET"", - ""url"": ""http://{host}:{port}/"", - ""responseCode"": 399, - ""responseExpected"": true, - ""spanName"": ""HTTP GET"", - ""spanStatus"": ""UNSET"", - ""spanKind"": ""Client"", - ""spanAttributes"": { - ""http.method"": ""GET"", - ""http.host"": ""{host}:{port}"", - ""http.status_code"": ""399"", - ""http.url"": ""http://{host}:{port}/"" - } - } -] -"))); + var input = JsonSerializer.Deserialize( + @" + [ + { + ""name"": ""Response code: 399"", + ""method"": ""GET"", + ""url"": ""http://{host}:{port}/"", + ""responseCode"": 399, + ""responseExpected"": true, + ""spanName"": ""HTTP GET"", + ""spanStatus"": ""UNSET"", + ""spanKind"": ""Client"", + ""spanAttributes"": { + ""http.scheme"": ""http"", + ""http.method"": ""GET"", + ""http.host"": ""{host}:{port}"", + ""http.status_code"": ""399"", + ""http.flavor"": ""2.0"", + ""http.url"": ""http://{host}:{port}/"" + } + } + ] + ", + new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); var t = (Task)this.GetType().InvokeMember(nameof(this.HttpOutCallsAreCollectedSuccessfullyAsync), BindingFlags.InvokeMethod, null, this, HttpTestData.GetArgumentsFromTestCaseObject(input).First()); await t; diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpTestData.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpTestData.cs index 803eba5b6..aa8efe91f 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpTestData.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpTestData.cs @@ -14,9 +14,8 @@ // limitations under the License. // using System.Collections.Generic; -using System.IO; using System.Reflection; -using Newtonsoft.Json; +using System.Text.Json; namespace OpenTelemetry.Instrumentation.Http.Tests { @@ -25,9 +24,8 @@ namespace OpenTelemetry.Instrumentation.Http.Tests public static IEnumerable ReadTestCases() { var assembly = Assembly.GetExecutingAssembly(); - var serializer = new JsonSerializer(); - var input = serializer.Deserialize(new JsonTextReader(new StreamReader(assembly.GetManifestResourceStream("OpenTelemetry.Instrumentation.Http.Tests.http-out-test-cases.json")))); - + var input = JsonSerializer.Deserialize( + assembly.GetManifestResourceStream("OpenTelemetry.Instrumentation.Http.Tests.http-out-test-cases.json"), new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); return GetArgumentsFromTestCaseObject(input); } @@ -74,8 +72,6 @@ namespace OpenTelemetry.Instrumentation.Http.Tests public bool? SpanStatusHasDescription { get; set; } public Dictionary SpanAttributes { get; set; } - - public bool SetHttpFlavor { get; set; } } } } diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.netfx.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.netfx.cs index 14c3347da..a31ddbaaf 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.netfx.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.netfx.cs @@ -20,8 +20,8 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Net; +using System.Text.Json; using Moq; -using Newtonsoft.Json; using OpenTelemetry.Tests; using OpenTelemetry.Trace; using Xunit; @@ -50,7 +50,6 @@ namespace OpenTelemetry.Instrumentation.Http.Tests .AddProcessor(activityProcessor.Object) .AddHttpClientInstrumentation(options => { - options.SetHttpFlavor = tc.SetHttpFlavor; options.Enrich = ActivityEnrichment; options.RecordException = tc.RecordException.HasValue ? tc.RecordException.Value : false; }) @@ -138,26 +137,27 @@ namespace OpenTelemetry.Instrumentation.Http.Tests [Fact] public void DebugIndividualTest() { - var serializer = new JsonSerializer(); - var input = serializer.Deserialize(new JsonTextReader(new StringReader(@" - { - ""name"": ""Http version attribute populated"", - ""method"": ""GET"", - ""url"": ""http://{host}:{port}/"", - ""responseCode"": 200, - ""spanName"": ""HTTP GET"", - ""spanStatus"": ""UNSET"", - ""spanKind"": ""Client"", - ""setHttpFlavor"": true, - ""spanAttributes"": { - ""http.method"": ""GET"", - ""http.host"": ""{host}:{port}"", - ""http.flavor"": ""2.0"", - ""http.status_code"": 200, - ""http.url"": ""http://{host}:{port}/"" - } - } -"))); + var input = JsonSerializer.Deserialize( + @" + { + ""name"": ""Http version attribute populated"", + ""method"": ""GET"", + ""url"": ""http://{host}:{port}/"", + ""responseCode"": 200, + ""spanName"": ""HTTP GET"", + ""spanStatus"": ""UNSET"", + ""spanKind"": ""Client"", + ""setHttpFlavor"": true, + ""spanAttributes"": { + ""http.method"": ""GET"", + ""http.host"": ""{host}:{port}"", + ""http.flavor"": ""2.0"", + ""http.status_code"": ""200"", + ""http.url"": ""http://{host}:{port}/"" + } + } + ", + new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); this.HttpOutCallsAreCollectedSuccessfully(input); } diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj b/test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj index 3b8c6bf69..c6636eae6 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj @@ -15,9 +15,9 @@ - + all 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 eac94c334..3441e152d 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/http-out-test-cases.json +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/http-out-test-cases.json @@ -1,4 +1,4 @@ -[ +[ { "name": "Successful GET call to localhost", "method": "GET", @@ -7,9 +7,11 @@ "spanStatus": "UNSET", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", - "http.status_code": 200, + "http.flavor": "2.0", + "http.status_code": "200", "http.url": "http://{host}:{port}/" } }, @@ -21,9 +23,11 @@ "spanStatus": "UNSET", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "POST", "http.host": "{host}:{port}", - "http.status_code": 200, + "http.flavor": "2.0", + "http.status_code": "200", "http.url": "http://{host}:{port}/" } }, @@ -36,9 +40,11 @@ "spanStatus": "UNSET", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", - "http.status_code": 200, + "http.flavor": "2.0", + "http.status_code": "200", "http.url": "http://{host}:{port}/path/to/resource/" } }, @@ -51,9 +57,11 @@ "spanStatus": "UNSET", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", - "http.status_code": 200, + "http.flavor": "2.0", + "http.status_code": "200", "http.url": "http://{host}:{port}/path/to/resource#fragment" } }, @@ -66,9 +74,11 @@ "spanStatus": "UNSET", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", - "http.status_code": 200, + "http.flavor": "2.0", + "http.status_code": "200", "http.url": "http://{host}:{port}/path/to/resource#fragment" } }, @@ -82,8 +92,10 @@ "responseExpected": false, "recordException": false, "spanAttributes": { + "http.scheme": "https", "http.method": "GET", "http.host": "sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com", + "http.flavor": "2.0", "http.url": "https://sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com/" } }, @@ -97,8 +109,10 @@ "responseExpected": false, "recordException": true, "spanAttributes": { + "http.scheme": "https", "http.method": "GET", "http.host": "sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com", + "http.flavor": "2.0", "http.url": "https://sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com/" } }, @@ -111,9 +125,11 @@ "spanStatus": "UNSET", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", - "http.status_code": 200, + "http.flavor": "2.0", + "http.status_code": "200", "http.url": "http://{host}:{port}/" } }, @@ -126,9 +142,11 @@ "spanStatus": "UNSET", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", - "http.status_code": 200, + "http.flavor": "2.0", + "http.status_code": "200", "http.url": "http://{host}:{port}/" } }, @@ -141,8 +159,10 @@ "spanStatus": "UNSET", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", + "http.flavor": "2.0", "http.status_code": "399", "http.url": "http://{host}:{port}/" } @@ -156,8 +176,10 @@ "spanStatus": "ERROR", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", + "http.flavor": "2.0", "http.status_code": "400", "http.url": "http://{host}:{port}/" } @@ -171,8 +193,10 @@ "spanStatus": "ERROR", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", + "http.flavor": "2.0", "http.status_code": "401", "http.url": "http://{host}:{port}/" } @@ -186,8 +210,10 @@ "spanStatus": "ERROR", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", + "http.flavor": "2.0", "http.status_code": "403", "http.url": "http://{host}:{port}/" } @@ -201,8 +227,10 @@ "spanStatus": "ERROR", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", + "http.flavor": "2.0", "http.status_code": "404", "http.url": "http://{host}:{port}/" } @@ -216,8 +244,10 @@ "spanStatus": "ERROR", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", + "http.flavor": "2.0", "http.status_code": "429", "http.url": "http://{host}:{port}/" } @@ -231,8 +261,10 @@ "spanStatus": "ERROR", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", + "http.flavor": "2.0", "http.status_code": "501", "http.url": "http://{host}:{port}/" } @@ -246,8 +278,10 @@ "spanStatus": "ERROR", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", + "http.flavor": "2.0", "http.status_code": "503", "http.url": "http://{host}:{port}/" } @@ -261,8 +295,10 @@ "spanStatus": "ERROR", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", + "http.flavor": "2.0", "http.status_code": "504", "http.url": "http://{host}:{port}/" } @@ -276,8 +312,10 @@ "spanStatus": "ERROR", "responseExpected": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", + "http.flavor": "2.0", "http.status_code": "600", "http.url": "http://{host}:{port}/" } @@ -290,12 +328,12 @@ "spanName": "HTTP GET", "spanStatus": "UNSET", "responseExpected": true, - "setHttpFlavor": true, "spanAttributes": { + "http.scheme": "http", "http.method": "GET", "http.host": "{host}:{port}", "http.flavor": "2.0", - "http.status_code": 200, + "http.status_code": "200", "http.url": "http://{host}:{port}/" } } diff --git a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/InProcessServer.cs b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/InProcessServer.cs index 9cb29488d..31010ff2f 100644 --- a/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/InProcessServer.cs +++ b/test/OpenTelemetry.Instrumentation.W3cTraceContext.Tests/InProcessServer.cs @@ -23,9 +23,6 @@ using OpenTelemetry.Trace; #if NETCOREAPP3_1 using TestApp.AspNetCore._3._1; #endif -#if NET5_0 -using TestApp.AspNetCore._5._0; -#endif #if NET6_0 using TestApp.AspNetCore._6._0; #endif diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs index c149928d7..27f9cc11c 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanBuilderShimTests.cs @@ -127,13 +127,13 @@ namespace OpenTelemetry.Shims.OpenTracing.Tests // matching root operation name var tracer = TracerProvider.Default.GetTracer(TracerName); - var shim = new SpanBuilderShim(tracer, "foo", new List { "foo" }); + var shim = new SpanBuilderShim(tracer, "foo"); var spanShim1 = (SpanShim)shim.Start(); Assert.Equal("foo", spanShim1.Span.Activity.OperationName); // mis-matched root operation name - shim = new SpanBuilderShim(tracer, "foo", new List { "bar" }); + shim = new SpanBuilderShim(tracer, "foo"); var spanShim2 = (SpanShim)shim.Start(); Assert.Equal("foo", spanShim2.Span.Activity.OperationName); @@ -330,5 +330,29 @@ namespace OpenTelemetry.Shims.OpenTracing.Tests Assert.NotNull(span); Assert.Equal("foo", span.Span.Activity.OperationName); } + + [Fact] + public void Start_UnderAspNetCoreInstrumentation() + { + // Simulate a span from AspNetCore instrumentation as parent. + using var source = new ActivitySource("Microsoft.AspNetCore.Hosting.HttpRequestIn"); + using var parentSpan = source.StartActivity("OTelParent"); + Assert.NotNull(parentSpan); + + // Start the OpenTracing span. + var tracer = TracerProvider.Default.GetTracer(TracerName); + var builderShim = new SpanBuilderShim(tracer, "foo"); + var spanShim = builderShim.StartActive().Span as SpanShim; + Assert.NotNull(spanShim); + + var telemetrySpan = spanShim.Span; + Assert.Same(telemetrySpan.Activity, Activity.Current); + Assert.Same(parentSpan, telemetrySpan.Activity.Parent); + + // Dispose the spanShim.Span and ensure correct state for Activity.Current + spanShim.Span.Dispose(); + + Assert.Same(parentSpan, Activity.Current); + } } } diff --git a/test/OpenTelemetry.Tests.Stress.Logs/OpenTelemetry.Tests.Stress.Logs.csproj b/test/OpenTelemetry.Tests.Stress.Logs/OpenTelemetry.Tests.Stress.Logs.csproj index 7bfd0cdf3..b58648816 100644 --- a/test/OpenTelemetry.Tests.Stress.Logs/OpenTelemetry.Tests.Stress.Logs.csproj +++ b/test/OpenTelemetry.Tests.Stress.Logs/OpenTelemetry.Tests.Stress.Logs.csproj @@ -3,14 +3,14 @@ Exe - net6.0;net5.0;netcoreapp3.1;net462 - false + net6.0;net462 - + + diff --git a/test/OpenTelemetry.Tests.Stress.Metrics/OpenTelemetry.Tests.Stress.Metrics.csproj b/test/OpenTelemetry.Tests.Stress.Metrics/OpenTelemetry.Tests.Stress.Metrics.csproj index 467e98c07..71e573557 100644 --- a/test/OpenTelemetry.Tests.Stress.Metrics/OpenTelemetry.Tests.Stress.Metrics.csproj +++ b/test/OpenTelemetry.Tests.Stress.Metrics/OpenTelemetry.Tests.Stress.Metrics.csproj @@ -4,17 +4,17 @@ Exe net6.0;net462 - false - - - + + + + diff --git a/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs b/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs index 8838a1854..da3215aee 100644 --- a/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs +++ b/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs @@ -46,12 +46,8 @@ public partial class Program using var meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter(TestMeter.Name) - .AddPrometheusExporter(options => - { - options.StartHttpListener = true; - options.HttpListenerPrefixes = new string[] { $"http://localhost:9185/" }; - options.ScrapeResponseCacheDurationMilliseconds = 0; - }) + .AddPrometheusHttpListener( + options => options.UriPrefixes = new string[] { $"http://localhost:9185/" }) .Build(); Stress(prometheusPort: 9184); diff --git a/test/OpenTelemetry.Tests.Stress/OpenTelemetry.Tests.Stress.csproj b/test/OpenTelemetry.Tests.Stress/OpenTelemetry.Tests.Stress.csproj index 93013144b..1c06ae607 100644 --- a/test/OpenTelemetry.Tests.Stress/OpenTelemetry.Tests.Stress.csproj +++ b/test/OpenTelemetry.Tests.Stress/OpenTelemetry.Tests.Stress.csproj @@ -3,10 +3,13 @@ Exe net6.0;net462 - false - + + + + + diff --git a/test/OpenTelemetry.Tests.Stress/README.md b/test/OpenTelemetry.Tests.Stress/README.md index 4b7b10ddb..0013c573a 100644 --- a/test/OpenTelemetry.Tests.Stress/README.md +++ b/test/OpenTelemetry.Tests.Stress/README.md @@ -41,25 +41,34 @@ Running (concurrency = 1), press to stop... ``` The stress test metrics are exposed via -[PrometheusExporter](../../src/OpenTelemetry.Exporter.Prometheus/README.md), +[Prometheus HttpListener](../../src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md), which can be accessed via -[http://localhost:9184/metrics/](http://localhost:9184/metrics/): +[http://localhost:9184/metrics/](http://localhost:9184/metrics/). + +Following shows a section of the metrics exposed in prometheus format: ```text -# TYPE Process_NonpagedSystemMemorySize64 gauge -Process_NonpagedSystemMemorySize64 31651 1637385964580 +# HELP OpenTelemetry_Tests_Stress_Loops The total number of `Run()` invocations that are completed. +# TYPE OpenTelemetry_Tests_Stress_Loops counter +OpenTelemetry_Tests_Stress_Loops 1844902947 1658950184752 -# TYPE Process_PagedSystemMemorySize64 gauge -Process_PagedSystemMemorySize64 238672 1637385964580 +# HELP OpenTelemetry_Tests_Stress_LoopsPerSecond The rate of `Run()` invocations based on a small sliding window of few hundreds of milliseconds. +# TYPE OpenTelemetry_Tests_Stress_LoopsPerSecond gauge +OpenTelemetry_Tests_Stress_LoopsPerSecond 9007731.132075472 1658950184752 -# TYPE Process_PagedMemorySize64 gauge -Process_PagedMemorySize64 16187392 1637385964580 +# HELP OpenTelemetry_Tests_Stress_CpuCyclesPerLoop The average CPU cycles for each `Run()` invocation, based on a small sliding window of few hundreds of milliseconds. +# TYPE OpenTelemetry_Tests_Stress_CpuCyclesPerLoop gauge +OpenTelemetry_Tests_Stress_CpuCyclesPerLoop 3008 1658950184752 -# TYPE Process_WorkingSet64 gauge -Process_WorkingSet64 29753344 1637385964580 +# HELP process_runtime_dotnet_gc_collections_count Number of garbage collections that have occurred since process start. +# TYPE process_runtime_dotnet_gc_collections_count counter +process_runtime_dotnet_gc_collections_count{generation="gen2"} 0 1658950184752 +process_runtime_dotnet_gc_collections_count{generation="gen1"} 0 1658950184752 +process_runtime_dotnet_gc_collections_count{generation="gen0"} 0 1658950184752 -# TYPE Process_VirtualMemorySize64 gauge -Process_VirtualMemorySize64 2204045848576 1637385964580 +# HELP process_runtime_dotnet_gc_allocations_size_bytes Count of bytes allocated on the managed GC heap since the process start. .NET objects are allocated from this heap. Object allocations from unmanaged languages such as C/C++ do not use this heap. +# TYPE process_runtime_dotnet_gc_allocations_size_bytes counter +process_runtime_dotnet_gc_allocations_size_bytes 5485192 1658950184752 ``` ## Writing your own stress test @@ -92,6 +101,13 @@ Add the [`Skeleton.cs`](./Skeleton.cs) file to your `*.csproj` file: ``` +Add the following packages to the project: + +```shell +dotnet add package OpenTelemetry.Exporter.Prometheus --prerelease +dotnet add package OpenTelemetry.Instrumentation.Runtime --prerelease +``` + Now you are ready to run your own stress test. Some useful notes: @@ -114,3 +130,4 @@ Some useful notes: sliding window of few hundreds of milliseconds. * `CPU Cycles/Loop` represents the average CPU cycles for each `Run()` invocation, based on a small sliding window of few hundreds of milliseconds. +* `Runaway Time` represents the runaway time (seconds) since the test started. diff --git a/test/OpenTelemetry.Tests.Stress/Skeleton.cs b/test/OpenTelemetry.Tests.Stress/Skeleton.cs index caf89303b..c69e2543a 100644 --- a/test/OpenTelemetry.Tests.Stress/Skeleton.cs +++ b/test/OpenTelemetry.Tests.Stress/Skeleton.cs @@ -27,18 +27,11 @@ namespace OpenTelemetry.Tests.Stress; public partial class Program { - private static readonly Meter StressMeter = new("OpenTelemetry.Tests.Stress"); private static volatile bool bContinue = true; private static volatile string output = "Test results not available yet."; static Program() { - var process = Process.GetCurrentProcess(); - StressMeter.CreateObservableGauge("Process.NonpagedSystemMemorySize64", () => process.NonpagedSystemMemorySize64); - StressMeter.CreateObservableGauge("Process.PagedSystemMemorySize64", () => process.PagedSystemMemorySize64); - StressMeter.CreateObservableGauge("Process.PagedMemorySize64", () => process.PagedMemorySize64); - StressMeter.CreateObservableGauge("Process.WorkingSet64", () => process.WorkingSet64); - StressMeter.CreateObservableGauge("Process.VirtualMemorySize64", () => process.VirtualMemorySize64); } public static void Stress(int concurrency = 0, int prometheusPort = 0) @@ -70,11 +63,8 @@ public partial class Program () => dLoopsPerSecond, description: "The rate of `Run()` invocations based on a small sliding window of few hundreds of milliseconds."); var dCpuCyclesPerLoop = 0D; -#if NET462 - if (Environment.OSVersion.Platform == PlatformID.Win32NT) -#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) -#endif { meter.CreateObservableGauge( "OpenTelemetry.Tests.Stress.CpuCyclesPerLoop", @@ -83,14 +73,10 @@ public partial class Program } using var meterProvider = prometheusPort != 0 ? Sdk.CreateMeterProviderBuilder() - .AddMeter(StressMeter.Name) .AddMeter(meter.Name) - .AddPrometheusExporter(options => - { - options.StartHttpListener = true; - options.HttpListenerPrefixes = new string[] { $"http://localhost:{prometheusPort}/" }; - options.ScrapeResponseCacheDurationMilliseconds = 0; - }) + .AddRuntimeInstrumentation() + .AddPrometheusHttpListener( + options => options.UriPrefixes = new string[] { $"http://localhost:{prometheusPort}/" }) .Build() : null; var statistics = new long[concurrency]; @@ -153,7 +139,7 @@ public partial class Program dLoopsPerSecond = (double)nLoops / ((double)watch.ElapsedMilliseconds / 1000.0); dCpuCyclesPerLoop = nLoops == 0 ? 0 : nCpuCycles / nLoops; - output = $"Loops: {cntLoopsTotal:n0}, Loops/Second: {dLoopsPerSecond:n0}, CPU Cycles/Loop: {dCpuCyclesPerLoop:n0}"; + output = $"Loops: {cntLoopsTotal:n0}, Loops/Second: {dLoopsPerSecond:n0}, CPU Cycles/Loop: {dCpuCyclesPerLoop:n0}, RunwayTime (Seconds): {watchForTotal.Elapsed.TotalSeconds:n0} "; Console.Title = output; } }, @@ -176,6 +162,7 @@ public partial class Program var cntCpuCyclesTotal = GetCpuCycles(); var cpuCyclesPerLoopTotal = cntLoopsTotal == 0 ? 0 : cntCpuCyclesTotal / cntLoopsTotal; Console.WriteLine("Stopping the stress test..."); + Console.WriteLine($"* Total Runaway Time (seconds) {watchForTotal.Elapsed.TotalSeconds:n0}"); Console.WriteLine($"* Total Loops: {cntLoopsTotal:n0}"); Console.WriteLine($"* Average Loops/Second: {totalLoopsPerSecond:n0}"); Console.WriteLine($"* Average CPU Cycles/Loop: {cpuCyclesPerLoopTotal:n0}"); @@ -187,11 +174,7 @@ public partial class Program private static ulong GetCpuCycles() { -#if NET462 - if (Environment.OSVersion.Platform != PlatformID.Win32NT) -#else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) -#endif { return 0; } diff --git a/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs b/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs index 387a91626..7a5309557 100644 --- a/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs +++ b/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs @@ -15,6 +15,9 @@ // using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace OpenTelemetry.Internal.Tests @@ -109,5 +112,66 @@ namespace OpenTelemetry.Internal.Tests Assert.Equal(1, circularBuffer.RemovedCount); Assert.Equal(1, circularBuffer.Count); } + + [Fact] + public async Task CpuPressureTest() + { + if (Environment.ProcessorCount < 2) + { + return; + } + + var circularBuffer = new CircularBuffer(2048); + + List tasks = new(); + + int numberOfItemsPerWorker = 100_000; + + for (int i = 0; i < Environment.ProcessorCount; i++) + { + int tid = i; + + tasks.Add(Task.Run(async () => + { + await Task.Delay(2000).ConfigureAwait(false); + + if (tid == 0) + { + for (int i = 0; i < numberOfItemsPerWorker * (Environment.ProcessorCount - 1); i++) + { + SpinWait wait = default; + while (true) + { + if (circularBuffer.Count > 0) + { + circularBuffer.Read(); + break; + } + + wait.SpinOnce(); + } + } + } + else + { + for (int i = 0; i < numberOfItemsPerWorker; i++) + { + SpinWait wait = default; + while (true) + { + if (circularBuffer.Add("item")) + { + break; + } + + wait.SpinOnce(); + } + } + } + })); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + } } } diff --git a/test/OpenTelemetry.Tests/Internal/EnvironmentVariableHelperTests.cs b/test/OpenTelemetry.Tests/Internal/EnvironmentVariableHelperTests.cs index 290e5ceb7..738fa27b9 100644 --- a/test/OpenTelemetry.Tests/Internal/EnvironmentVariableHelperTests.cs +++ b/test/OpenTelemetry.Tests/Internal/EnvironmentVariableHelperTests.cs @@ -55,6 +55,44 @@ namespace OpenTelemetry.Internal.Tests Assert.Null(actualValue); } + [Theory] + [InlineData("true", true)] + [InlineData("TRUE", true)] + [InlineData("false", false)] + [InlineData("FALSE", false)] + [InlineData(" true ", true)] + [InlineData(" false ", false)] + public void LoadBoolean(string value, bool expectedValue) + { + Environment.SetEnvironmentVariable(EnvVar, value); + + bool actualBool = EnvironmentVariableHelper.LoadBoolean(EnvVar, out bool actualValue); + + Assert.True(actualBool); + Assert.Equal(expectedValue, actualValue); + } + + [Fact] + public void LoadBoolean_NoValue() + { + bool actualBool = EnvironmentVariableHelper.LoadBoolean(EnvVar, out bool actualValue); + + Assert.False(actualBool); + Assert.False(actualValue); + } + + [Theory] + [InlineData("something")] // non true/false + [InlineData(" ")] // whitespaces + [InlineData("0")] // 0 + [InlineData("1")] // 1 + public void LoadBoolean_Invalid(string value) + { + Environment.SetEnvironmentVariable(EnvVar, value); + + Assert.Throws(() => EnvironmentVariableHelper.LoadBoolean(EnvVar, out bool _)); + } + [Theory] [InlineData("123", 123)] [InlineData("0", 0)] diff --git a/test/OpenTelemetry.Tests/Internal/GuardTest.cs b/test/OpenTelemetry.Tests/Internal/GuardTest.cs index 01e23d3d7..30b0ab7b7 100644 --- a/test/OpenTelemetry.Tests/Internal/GuardTest.cs +++ b/test/OpenTelemetry.Tests/Internal/GuardTest.cs @@ -15,20 +15,12 @@ // using System; +using System.Runtime.CompilerServices; using System.Threading; using Xunit; namespace OpenTelemetry.Internal.Tests { -#pragma warning disable SA1402 // File may only contain a single type -#pragma warning disable SA1649 // File name should match first type name - public class Thing -#pragma warning restore SA1649 // File name should match first type name -#pragma warning restore SA1402 // File may only contain a single type - { - public string Bar { get; set; } - } - public class GuardTest { [Fact] @@ -171,5 +163,38 @@ namespace OpenTelemetry.Internal.Tests Assert.Contains("Must not be zero", ex1.Message); Assert.Equal("0", ex1.ParamName); } + + public class Thing + { + public string Bar { get; set; } + } + +#if !NETCOREAPP3_0_OR_GREATER + /// + /// Borrowed from: . + /// + public class CallerArgumentExpressionAttributeTests + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("paramName")] + public static void Ctor_ParameterName_Roundtrip(string value) + { + var caea = new CallerArgumentExpressionAttribute(value); + Assert.Equal(value, caea.ParameterName); + } + + [Fact] + public static void BasicTest() + { + Assert.Equal("\"hello\"", GetValue("hello")); + Assert.Equal("3 + 2", GetValue(3 + 2)); + Assert.Equal("new object()", GetValue(new object())); + } + + private static string GetValue(object argument, [CallerArgumentExpression("argument")] string expr = null) => expr; + } +#endif } } diff --git a/test/OpenTelemetry.Tests/Internal/MathHelperTest.cs b/test/OpenTelemetry.Tests/Internal/MathHelperTest.cs new file mode 100644 index 000000000..5ae953006 --- /dev/null +++ b/test/OpenTelemetry.Tests/Internal/MathHelperTest.cs @@ -0,0 +1,83 @@ +// +// 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 Xunit; + +namespace OpenTelemetry.Internal.Tests; + +public class MathHelperTest +{ + [Theory] + [InlineData(0b0000_0000, 8)] + [InlineData(0b0000_0001, 7)] + [InlineData(0b0000_0010, 6)] + [InlineData(0b0000_0011, 6)] + [InlineData(0b0000_0100, 5)] + [InlineData(0b0000_0101, 5)] + [InlineData(0b0000_0111, 5)] + [InlineData(0b0000_1000, 4)] + [InlineData(0b0000_1001, 4)] + [InlineData(0b0000_1111, 4)] + [InlineData(0b0001_0000, 3)] + [InlineData(0b0001_0001, 3)] + [InlineData(0b0001_1111, 3)] + [InlineData(0b0010_0000, 2)] + [InlineData(0b0010_0001, 2)] + [InlineData(0b0011_1111, 2)] + [InlineData(0b0100_0000, 1)] + [InlineData(0b0100_0001, 1)] + [InlineData(0b0111_1111, 1)] + [InlineData(0b1000_0000, 0)] + [InlineData(0b1000_0001, 0)] + [InlineData(0b1111_1111, 0)] + public void LeadingZero8(byte value, int numberOfLeaderZeros) + { + Assert.Equal(numberOfLeaderZeros, MathHelper.LeadingZero8(value)); + } + + [Theory] + [InlineData(unchecked((short)0b0000_0000_0000_0000), 16)] + [InlineData(unchecked((short)0b0000_0000_0000_0001), 15)] + [InlineData(unchecked((short)0b0000_0000_1000_0000), 8)] + [InlineData(unchecked((short)0b0000_0001_0000_0000), 7)] + [InlineData(unchecked((short)0b1000_0000_0000_0000), 0)] + public void LeadingZero16(short value, int numberOfLeaderZeros) + { + Assert.Equal(numberOfLeaderZeros, MathHelper.LeadingZero16(value)); + } + + [Theory] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0000, 32)] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0001, 31)] + [InlineData(0b0000_0000_0000_0000_1000_0000_0000_0000, 16)] + [InlineData(0b0000_0000_0000_0001_0000_0000_0000_0000, 15)] + [InlineData(unchecked((int)0b1000_0000_0000_0000_0000_0000_0000_0000), 0)] + public void LeadingZero32(int value, int numberOfLeaderZeros) + { + Assert.Equal(numberOfLeaderZeros, MathHelper.LeadingZero32(value)); + } + + [Theory] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L, 64)] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0001L, 63)] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0000_1000_0000_0000_0000_0000_0000_0000_0000L, 32)] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0001_0000_0000_0000_0000_0000_0000_0000_0000L, 31)] + [InlineData(unchecked((long)0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000), 0)] + public void LeadingZero64(long value, int numberOfLeaderZeros) + { + Assert.Equal(numberOfLeaderZeros, MathHelper.LeadingZero64(value)); + } +} diff --git a/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs b/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs new file mode 100644 index 000000000..78fe54943 --- /dev/null +++ b/test/OpenTelemetry.Tests/Internal/PeriodicExportingMetricReaderHelperTests.cs @@ -0,0 +1,135 @@ +// +// 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; +using OpenTelemetry.Exporter; +using OpenTelemetry.Metrics; +using Xunit; + +namespace OpenTelemetry.Internal.Tests +{ + public class PeriodicExportingMetricReaderHelperTests : IDisposable + { + public PeriodicExportingMetricReaderHelperTests() + { + ClearEnvVars(); + } + + public void Dispose() + { + ClearEnvVars(); + } + + [Fact] + public void CreatePeriodicExportingMetricReader_Defaults() + { + var reader = CreatePeriodicExportingMetricReader(); + + Assert.Equal(60000, reader.ExportIntervalMilliseconds); + Assert.Equal(30000, reader.ExportTimeoutMilliseconds); + Assert.Equal(MetricReaderTemporalityPreference.Cumulative, reader.TemporalityPreference); + } + + [Fact] + public void CreatePeriodicExportingMetricReader_TemporalityPreference_FromOptions() + { + var value = MetricReaderTemporalityPreference.Delta; + var reader = CreatePeriodicExportingMetricReader(new() + { + TemporalityPreference = value, + }); + + Assert.Equal(value, reader.TemporalityPreference); + } + + [Fact] + public void CreatePeriodicExportingMetricReader_ExportIntervalMilliseconds_FromOptions() + { + Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderHelper.OTelMetricExportIntervalEnvVarKey, "88888"); // should be ignored, as value set via options has higher priority + var value = 123; + var reader = CreatePeriodicExportingMetricReader(new() + { + PeriodicExportingMetricReaderOptions = new() + { + ExportIntervalMilliseconds = value, + }, + }); + + Assert.Equal(value, reader.ExportIntervalMilliseconds); + } + + [Fact] + public void CreatePeriodicExportingMetricReader_ExportTimeoutMilliseconds_FromOptions() + { + Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderHelper.OTelMetricExportTimeoutEnvVarKey, "99999"); // should be ignored, as value set via options has higher priority + var value = 456; + var reader = CreatePeriodicExportingMetricReader(new() + { + PeriodicExportingMetricReaderOptions = new() + { + ExportTimeoutMilliseconds = value, + }, + }); + + Assert.Equal(value, reader.ExportTimeoutMilliseconds); + } + + [Fact] + public void CreatePeriodicExportingMetricReader_ExportIntervalMilliseconds_FromEnvVar() + { + var value = 789; + Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderHelper.OTelMetricExportIntervalEnvVarKey, value.ToString()); + var reader = CreatePeriodicExportingMetricReader(); + + Assert.Equal(value, reader.ExportIntervalMilliseconds); + } + + [Fact] + public void CreatePeriodicExportingMetricReader_ExportTimeoutMilliseconds_FromEnvVar() + { + var value = 246; + Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderHelper.OTelMetricExportTimeoutEnvVarKey, value.ToString()); + var reader = CreatePeriodicExportingMetricReader(); + + Assert.Equal(value, reader.ExportTimeoutMilliseconds); + } + + [Fact] + public void EnvironmentVariableNames() + { + Assert.Equal("OTEL_METRIC_EXPORT_INTERVAL", PeriodicExportingMetricReaderHelper.OTelMetricExportIntervalEnvVarKey); + Assert.Equal("OTEL_METRIC_EXPORT_TIMEOUT", PeriodicExportingMetricReaderHelper.OTelMetricExportTimeoutEnvVarKey); + } + + private static void ClearEnvVars() + { + Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderHelper.OTelMetricExportIntervalEnvVarKey, null); + Environment.SetEnvironmentVariable(PeriodicExportingMetricReaderHelper.OTelMetricExportTimeoutEnvVarKey, null); + } + + private static PeriodicExportingMetricReader CreatePeriodicExportingMetricReader( + MetricReaderOptions options = null) + { + if (options == null) + { + options = new(); + } + + var dummyMetricExporter = new InMemoryExporter(new Metric[0]); + return PeriodicExportingMetricReaderHelper.CreatePeriodicExportingMetricReader(dummyMetricExporter, options); + } + } +} diff --git a/test/OpenTelemetry.Tests/Internal/WildcardHelperTests.cs b/test/OpenTelemetry.Tests/Internal/WildcardHelperTests.cs new file mode 100644 index 000000000..7adf9e8a5 --- /dev/null +++ b/test/OpenTelemetry.Tests/Internal/WildcardHelperTests.cs @@ -0,0 +1,41 @@ +// +// 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 Xunit; + +namespace OpenTelemetry.Internal.Tests +{ + public class WildcardHelperTests + { + [Theory] + [InlineData(new[] { "a" }, "a", true)] + [InlineData(new[] { "a.*" }, "a.b", true)] + [InlineData(new[] { "a" }, "a.b", false)] + [InlineData(new[] { "a", "x.*" }, "x.y", true)] + [InlineData(new[] { "a", "x.*" }, "a.b", false)] + [InlineData(new[] { "a", "x", "y" }, "abbbt", false)] + [InlineData(new[] { "a", "x", "y" }, "ccxccc", false)] + [InlineData(new[] { "a", "x", "y" }, "wecgy", false)] + public void WildcardRegex_ShouldMatch(string[] patterns, string matchWith, bool isMatch) + { + var regex = WildcardHelper.GetWildcardRegex(patterns); + + var result = regex.IsMatch(matchWith); + + Assert.True(result == isMatch); + } + } +} diff --git a/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs b/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs new file mode 100644 index 000000000..4a9e6ed3f --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/BatchLogRecordExportProcessorTests.cs @@ -0,0 +1,107 @@ +// +// 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. +// + +#if !NETFRAMEWORK +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Exporter; +using Xunit; + +namespace OpenTelemetry.Logs.Tests +{ + public sealed class BatchLogRecordExportProcessorTests + { + [Fact] + public void StateValuesAndScopeBufferingTest() + { + var scopeProvider = new LoggerExternalScopeProvider(); + + List exportedItems = new(); + + using var exporter = new BatchLogRecordExportProcessor( + new InMemoryExporter(exportedItems)); + + using var scope = scopeProvider.Push(exportedItems); + + var logRecord = new LogRecord(); + + var state = new LogRecordTest.DisposingState("Hello world"); + + logRecord.ScopeProvider = scopeProvider; + logRecord.StateValues = state; + + exporter.OnEnd(logRecord); + + state.Dispose(); + + Assert.Empty(exportedItems); + + Assert.Null(logRecord.ScopeProvider); + Assert.False(ReferenceEquals(state, logRecord.StateValues)); + Assert.NotNull(logRecord.AttributeStorage); + Assert.NotNull(logRecord.BufferedScopes); + + KeyValuePair actualState = logRecord.StateValues[0]; + + Assert.Same("Value", actualState.Key); + Assert.Same("Hello world", actualState.Value); + + bool foundScope = false; + + logRecord.ForEachScope( + (s, o) => + { + foundScope = ReferenceEquals(s.Scope, exportedItems); + }, + null); + + Assert.True(foundScope); + } + + [Fact] + public void StateBufferingTest() + { + // LogRecord.State is never inspected or buffered. Accessing it + // after OnEnd may throw. This test verifies that behavior. TODO: + // Investigate this. Potentially obsolete logRecord.State and force + // StateValues/ParseStateValues behavior. + List exportedItems = new(); + + using var exporter = new BatchLogRecordExportProcessor( + new InMemoryExporter(exportedItems)); + + var logRecord = new LogRecord(); + + var state = new LogRecordTest.DisposingState("Hello world"); + logRecord.State = state; + + exporter.OnEnd(logRecord); + + state.Dispose(); + + Assert.Throws(() => + { + IReadOnlyList> state = (IReadOnlyList>)logRecord.State; + + foreach (var kvp in state) + { + } + }); + } + } +} +#endif diff --git a/test/OpenTelemetry.Tests/Logs/LogEmitterTests.cs b/test/OpenTelemetry.Tests/Logs/LogEmitterTests.cs new file mode 100644 index 000000000..7e086accf --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/LogEmitterTests.cs @@ -0,0 +1,177 @@ +// +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace OpenTelemetry.Logs.Tests +{ + public sealed class LogEmitterTests + { + [Fact] + public void LogEmitterBasicTest() + { + var exportedItems = new List(); + + using var provider = new OpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); + + var logEmitter = provider.CreateEmitter(); + + Exception ex = new InvalidOperationException(); + + logEmitter.Emit( + new() + { + CategoryName = "LogEmitter", + Message = "Hello world", + LogLevel = LogLevel.Warning, + EventId = new EventId(18, "CustomEvent"), + Exception = ex, + }, + new() + { + ["key1"] = "value1", + ["key2"] = "value2", + }); + + Assert.Single(exportedItems); + + var logRecord = exportedItems[0]; + + Assert.NotNull(logRecord); + Assert.Equal("LogEmitter", logRecord.CategoryName); + Assert.Equal("Hello world", logRecord.FormattedMessage); + Assert.Equal(LogLevel.Warning, logRecord.LogLevel); + Assert.Equal(18, logRecord.EventId.Id); + Assert.Equal("CustomEvent", logRecord.EventId.Name); + Assert.Equal(ex, logRecord.Exception); + Assert.NotEqual(DateTime.MinValue, logRecord.Timestamp); + + Assert.Equal(default, logRecord.TraceId); + Assert.Equal(default, logRecord.SpanId); + Assert.Equal(ActivityTraceFlags.None, logRecord.TraceFlags); + Assert.Null(logRecord.TraceState); + + Assert.NotNull(logRecord.StateValues); + Assert.Equal(2, logRecord.StateValues.Count); + Assert.Contains(logRecord.StateValues, item => item.Key == "key1" && (string)item.Value == "value1"); + Assert.Contains(logRecord.StateValues, item => item.Key == "key2" && (string)item.Value == "value2"); + } + + [Fact] + public void LogEmitterFromActivityTest() + { + var exportedItems = new List(); + + using var provider = new OpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); + + var logEmitter = provider.CreateEmitter(); + + using var activity = new Activity("Test"); + + activity.Start(); + + activity.ActivityTraceFlags = ActivityTraceFlags.Recorded; + activity.TraceStateString = "key1=value1"; + + logEmitter.Emit(new(activity)); + + Assert.Single(exportedItems); + + var logRecord = exportedItems[0]; + + Assert.NotNull(logRecord); + + Assert.Equal(activity.TraceId, logRecord.TraceId); + Assert.Equal(activity.SpanId, logRecord.SpanId); + Assert.Equal(activity.ActivityTraceFlags, logRecord.TraceFlags); + Assert.Equal(activity.TraceStateString, logRecord.TraceState); + + Assert.Null(logRecord.StateValues); + } + + [Fact] + public void LogEmitterLocalToUtcTimestampTest() + { + var exportedItems = new List(); + + using var provider = new OpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); + + var logEmitter = provider.CreateEmitter(); + + DateTime timestamp = DateTime.SpecifyKind( + new DateTime(2022, 6, 30, 16, 0, 0), + DateTimeKind.Local); + + logEmitter.Emit(new() + { + Timestamp = timestamp, + }); + + Assert.Single(exportedItems); + + var logRecord = exportedItems[0]; + + Assert.NotNull(logRecord); + + Assert.Equal(timestamp.ToUniversalTime(), logRecord.Timestamp); + Assert.Equal(DateTimeKind.Utc, logRecord.Timestamp.Kind); + } + + [Fact] + public void LogEmitterUnspecifiedTimestampTest() + { + var exportedItems = new List(); + + using var provider = new OpenTelemetryLoggerProvider(options => + { + options.AddInMemoryExporter(exportedItems); + }); + + var logEmitter = provider.CreateEmitter(); + + DateTime timestamp = DateTime.SpecifyKind( + new DateTime(2022, 6, 30, 16, 0, 0), + DateTimeKind.Unspecified); + + logEmitter.Emit(new() + { + Timestamp = timestamp, + }); + + Assert.Single(exportedItems); + + var logRecord = exportedItems[0]; + + Assert.NotNull(logRecord); + + Assert.Equal(timestamp, logRecord.Timestamp); + Assert.Equal(DateTimeKind.Unspecified, logRecord.Timestamp.Kind); + } + } +} diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordAttributeListTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordAttributeListTests.cs new file mode 100644 index 000000000..d0e068c46 --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/LogRecordAttributeListTests.cs @@ -0,0 +1,103 @@ +// +// 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.Collections.Generic; +using Xunit; + +namespace OpenTelemetry.Logs.Tests +{ + public sealed class LogRecordAttributeListTests + { + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(8)] + [InlineData(9)] + [InlineData(64)] + public void ReadWriteTest(int numberOfItems) + { + LogRecordAttributeList attributes = default; + + for (int i = 0; i < numberOfItems; i++) + { + attributes.Add($"key{i}", i); + } + + Assert.Equal(numberOfItems, attributes.Count); + + for (int i = 0; i < numberOfItems; i++) + { + var item = attributes[i]; + + Assert.Equal($"key{i}", item.Key); + Assert.Equal(i, (int)item.Value); + } + + int index = 0; + foreach (KeyValuePair item in attributes) + { + Assert.Equal($"key{index}", item.Key); + Assert.Equal(index, (int)item.Value); + index++; + } + + if (attributes.Count <= LogRecordAttributeList.OverflowAdditionalCapacity) + { + Assert.Null(attributes.OverflowAttributes); + } + else + { + Assert.NotNull(attributes.OverflowAttributes); + } + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(8)] + [InlineData(9)] + [InlineData(64)] + public void ApplyToLogRecordTest(int numberOfItems) + { + LogRecordAttributeList attributes = default; + + for (int i = 0; i < numberOfItems; i++) + { + attributes.Add($"key{i}", i); + } + + LogRecord logRecord = new(); + + attributes.ApplyToLogRecord(logRecord); + + if (numberOfItems == 0) + { + Assert.Null(logRecord.StateValues); + return; + } + + Assert.NotNull(logRecord.StateValues); + + int index = 0; + foreach (KeyValuePair item in logRecord.StateValues) + { + Assert.Equal($"key{index}", item.Key); + Assert.Equal(index, (int)item.Value); + index++; + } + } + } +} diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs new file mode 100644 index 000000000..0a6d7d37b --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs @@ -0,0 +1,278 @@ +// +// 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. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTelemetry.Logs.Tests +{ + public sealed class LogRecordSharedPoolTests + { + [Fact] + public void ResizeTests() + { + LogRecordSharedPool.Resize(LogRecordSharedPool.DefaultMaxPoolSize); + Assert.NotNull(LogRecordSharedPool.Current); + Assert.Equal(LogRecordSharedPool.DefaultMaxPoolSize, LogRecordSharedPool.Current.Capacity); + + Assert.Throws(() => LogRecordSharedPool.Resize(0)); + + var beforePool = LogRecordSharedPool.Current; + + LogRecordSharedPool.Resize(1); + + Assert.NotNull(LogRecordSharedPool.Current); + Assert.Equal(1, LogRecordSharedPool.Current.Capacity); + Assert.NotEqual(beforePool, LogRecordSharedPool.Current); + } + + [Fact] + public void RentReturnTests() + { + LogRecordSharedPool.Resize(2); + + var pool = LogRecordSharedPool.Current; + + var logRecord1 = pool.Rent(); + Assert.NotNull(logRecord1); + + var logRecord2 = pool.Rent(); + Assert.NotNull(logRecord1); + + pool.Return(logRecord1); + + Assert.Equal(1, pool.Count); + + // Note: This is ignored because logRecord manually created has PoolReferenceCount = int.MaxValue. + LogRecord manualRecord = new(); + Assert.Equal(int.MaxValue, manualRecord.PoolReferenceCount); + pool.Return(manualRecord); + + Assert.Equal(1, pool.Count); + + pool.Return(logRecord2); + + Assert.Equal(2, pool.Count); + + logRecord1 = pool.Rent(); + Assert.NotNull(logRecord1); + Assert.Equal(1, pool.Count); + + logRecord2 = pool.Rent(); + Assert.NotNull(logRecord2); + Assert.Equal(0, pool.Count); + + var logRecord3 = pool.Rent(); + var logRecord4 = pool.Rent(); + Assert.NotNull(logRecord3); + Assert.NotNull(logRecord4); + + pool.Return(logRecord1); + pool.Return(logRecord2); + pool.Return(logRecord3); + pool.Return(logRecord4); // <- Discarded due to pool size of 2 + + Assert.Equal(2, pool.Count); + } + + [Fact] + public void TrackReferenceTests() + { + LogRecordSharedPool.Resize(2); + + var pool = LogRecordSharedPool.Current; + + var logRecord1 = pool.Rent(); + Assert.NotNull(logRecord1); + + Assert.Equal(1, logRecord1.PoolReferenceCount); + + logRecord1.AddReference(); + + Assert.Equal(2, logRecord1.PoolReferenceCount); + + pool.Return(logRecord1); + + Assert.Equal(1, logRecord1.PoolReferenceCount); + + pool.Return(logRecord1); + + Assert.Equal(1, pool.Count); + Assert.Equal(0, logRecord1.PoolReferenceCount); + + pool.Return(logRecord1); + + Assert.Equal(-1, logRecord1.PoolReferenceCount); + Assert.Equal(1, pool.Count); // Record was not returned because PoolReferences was negative. + } + + [Fact] + public void ClearTests() + { + LogRecordSharedPool.Resize(LogRecordSharedPool.DefaultMaxPoolSize); + + var pool = LogRecordSharedPool.Current; + + var logRecord1 = pool.Rent(); + logRecord1.AttributeStorage = new List>(16) + { + new KeyValuePair("key1", "value1"), + new KeyValuePair("key2", "value2"), + }; + logRecord1.BufferedScopes = new List(8) { null, null }; + + pool.Return(logRecord1); + + Assert.Empty(logRecord1.AttributeStorage); + Assert.Equal(16, logRecord1.AttributeStorage.Capacity); + Assert.Empty(logRecord1.BufferedScopes); + Assert.Equal(8, logRecord1.BufferedScopes.Capacity); + + logRecord1 = pool.Rent(); + + Assert.NotNull(logRecord1.AttributeStorage); + Assert.NotNull(logRecord1.BufferedScopes); + + for (int i = 0; i <= LogRecordPoolHelper.DefaultMaxNumberOfAttributes; i++) + { + logRecord1.AttributeStorage!.Add(new KeyValuePair("key", "value")); + } + + for (int i = 0; i <= LogRecordPoolHelper.DefaultMaxNumberOfScopes; i++) + { + logRecord1.BufferedScopes!.Add(null); + } + + pool.Return(logRecord1); + + Assert.Null(logRecord1.AttributeStorage); + Assert.Null(logRecord1.BufferedScopes); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ExportTest(bool warmup) + { + LogRecordSharedPool.Resize(LogRecordSharedPool.DefaultMaxPoolSize); + + var pool = LogRecordSharedPool.Current; + + if (warmup) + { + for (int i = 0; i < LogRecordSharedPool.DefaultMaxPoolSize; i++) + { + pool.Return(new LogRecord { PoolReferenceCount = 1 }); + } + } + + using BatchLogRecordExportProcessor processor = new(new NoopExporter()); + + List tasks = new(); + + for (int i = 0; i < Environment.ProcessorCount; i++) + { + tasks.Add(Task.Run(async () => + { + Random random = new Random(); + + await Task.Delay(random.Next(100, 150)).ConfigureAwait(false); + + for (int i = 0; i < 1000; i++) + { + var logRecord = pool.Rent(); + + processor.OnEnd(logRecord); + + // This should no-op mostly. + pool.Return(logRecord); + + await Task.Delay(random.Next(0, 20)).ConfigureAwait(false); + } + })); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + + processor.ForceFlush(); + + if (warmup) + { + Assert.Equal(LogRecordSharedPool.DefaultMaxPoolSize, pool.Count); + } + + Assert.True(pool.Count <= LogRecordSharedPool.DefaultMaxPoolSize); + } + + [Fact] + public async Task DeadlockTest() + { + /* + * The way the LogRecordPool works is it maintains two counters one + * for readers and one for writers. The counters always increment + * and point to an index in the pool array by way of a modulus on + * the size of the array (index = counter % capacity). Under very + * heavy load it is possible for a reader to receive an index and + * then be yielded. When waking up that index may no longer be valid + * if other threads caused the counters to loop around. There is + * protection for this case in the pool, this test verifies it is + * working. + * + * This is considered a corner case. Many threads have to be renting + * & returning logs in a tight loop for this to happen. Real + * applications should be logging based on logic firing which should + * have more natural back-off time. + */ + + LogRecordSharedPool.Resize(LogRecordSharedPool.DefaultMaxPoolSize); + + var pool = LogRecordSharedPool.Current; + + List tasks = new(); + + for (int i = 0; i < Environment.ProcessorCount; i++) + { + tasks.Add(Task.Run(async () => + { + await Task.Delay(2000).ConfigureAwait(false); + + for (int i = 0; i < 100_000; i++) + { + var logRecord = pool.Rent(); + + pool.Return(logRecord); + } + })); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + + Assert.True(pool.Count <= LogRecordSharedPool.DefaultMaxPoolSize); + } + + private sealed class NoopExporter : BaseExporter + { + public override ExportResult Export(in Batch batch) + { + return ExportResult.Success; + } + } + } +} diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs index 37dd56151..79fb0d2fa 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs @@ -256,8 +256,7 @@ namespace OpenTelemetry.Logs.Tests using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null); var logger = loggerFactory.CreateLogger(); - var message = $"This does not matter."; - logger.LogInformation(message); + logger.LogInformation("This does not matter."); var logRecord = exportedItems[0]; logRecord.State = "newState"; @@ -744,17 +743,44 @@ namespace OpenTelemetry.Logs.Tests Assert.Same(state, actualState.Value); } + [Fact] + public void DisposingStateTest() + { + using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true); + var logger = loggerFactory.CreateLogger(); + + DisposingState state = new DisposingState("Hello world"); + + logger.Log( + LogLevel.Information, + 0, + state, + null, + (s, e) => "OpenTelemetry!"); + var logRecord = exportedItems[0]; + + state.Dispose(); + + Assert.Null(logRecord.State); + Assert.NotNull(logRecord.StateValues); + Assert.Equal(1, logRecord.StateValues.Count); + + KeyValuePair actualState = logRecord.StateValues[0]; + + Assert.Same("Value", actualState.Key); + Assert.Same("Hello world", actualState.Value); + } + private static ILoggerFactory InitializeLoggerFactory(out List exportedItems, Action configure = null) { - exportedItems = new List(); - var exporter = new InMemoryExporter(exportedItems); - var processor = new TestLogRecordProcessor(exporter); + var items = exportedItems = new List(); + return LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => { configure?.Invoke(options); - options.AddProcessor(processor); + options.AddInMemoryExporter(items); }); builder.AddFilter(typeof(LogRecordTest).FullName, LogLevel.Trace); }); @@ -791,6 +817,54 @@ namespace OpenTelemetry.Logs.Tests } } + internal sealed class DisposingState : IReadOnlyList>, IDisposable + { + private string value; + private bool disposed; + + public DisposingState(string value) + { + this.Value = value; + } + + public int Count => 1; + + public string Value + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException(nameof(DisposingState)); + } + + return this.value; + } + private set => this.value = value; + } + + public KeyValuePair this[int index] => index switch + { + 0 => new KeyValuePair(nameof(this.Value), this.Value), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + + public void Dispose() + { + this.disposed = true; + } + + public IEnumerator> GetEnumerator() + { + for (var i = 0; i < this.Count; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } + private class RedactionProcessor : BaseProcessor { private readonly Field fieldToUpdate; @@ -841,21 +915,6 @@ namespace OpenTelemetry.Logs.Tests { public string Property { get; set; } } - - private class TestLogRecordProcessor : SimpleExportProcessor - { - public TestLogRecordProcessor(BaseExporter exporter) - : base(exporter) - { - } - - public override void OnEnd(LogRecord data) - { - data.BufferLogScopes(); - - base.OnEnd(data); - } - } } } #endif diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs new file mode 100644 index 000000000..93a334cc5 --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs @@ -0,0 +1,90 @@ +// +// 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. +// + +#nullable enable + +using System.Collections.Generic; +using Xunit; + +namespace OpenTelemetry.Logs.Tests +{ + public sealed class LogRecordThreadStaticPoolTests + { + [Fact] + public void RentReturnTests() + { + LogRecordThreadStaticPool.Storage = null; + + var logRecord = LogRecordThreadStaticPool.Instance.Rent(); + Assert.NotNull(logRecord); + Assert.Null(LogRecordThreadStaticPool.Storage); + + LogRecordThreadStaticPool.Instance.Return(logRecord); + Assert.NotNull(LogRecordThreadStaticPool.Storage); + Assert.Equal(logRecord, LogRecordThreadStaticPool.Storage); + + LogRecordThreadStaticPool.Instance.Return(new()); + Assert.NotNull(LogRecordThreadStaticPool.Storage); + Assert.Equal(logRecord, LogRecordThreadStaticPool.Storage); + + LogRecordThreadStaticPool.Storage = null; + + var manual = new LogRecord(); + LogRecordThreadStaticPool.Instance.Return(manual); + Assert.NotNull(LogRecordThreadStaticPool.Storage); + Assert.Equal(manual, LogRecordThreadStaticPool.Storage); + } + + [Fact] + public void ClearTests() + { + var logRecord1 = LogRecordThreadStaticPool.Instance.Rent(); + logRecord1.AttributeStorage = new List>(16) + { + new KeyValuePair("key1", "value1"), + new KeyValuePair("key2", "value2"), + }; + logRecord1.BufferedScopes = new List(8) { null, null }; + + LogRecordThreadStaticPool.Instance.Return(logRecord1); + + Assert.Empty(logRecord1.AttributeStorage); + Assert.Equal(16, logRecord1.AttributeStorage.Capacity); + Assert.Empty(logRecord1.BufferedScopes); + Assert.Equal(8, logRecord1.BufferedScopes.Capacity); + + logRecord1 = LogRecordThreadStaticPool.Instance.Rent(); + + Assert.NotNull(logRecord1.AttributeStorage); + Assert.NotNull(logRecord1.BufferedScopes); + + for (int i = 0; i <= LogRecordPoolHelper.DefaultMaxNumberOfAttributes; i++) + { + logRecord1.AttributeStorage!.Add(new KeyValuePair("key", "value")); + } + + for (int i = 0; i <= LogRecordPoolHelper.DefaultMaxNumberOfScopes; i++) + { + logRecord1.BufferedScopes!.Add(null); + } + + LogRecordThreadStaticPool.Instance.Return(logRecord1); + + Assert.Null(logRecord1.AttributeStorage); + Assert.Null(logRecord1.BufferedScopes); + } + } +} diff --git a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs new file mode 100644 index 000000000..6ddd5d7ca --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs @@ -0,0 +1,149 @@ +// +// 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.Collections.Generic; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Exporter; +using OpenTelemetry.Resources; +using Xunit; + +namespace OpenTelemetry.Logs.Tests +{ + public sealed class OpenTelemetryLoggerProviderTests + { + [Fact] + public void DefaultCtorTests() + { + OpenTelemetryLoggerOptions defaults = new(); + + using OpenTelemetryLoggerProvider provider = new(); + + Assert.Equal(defaults.IncludeScopes, provider.IncludeScopes); + Assert.Equal(defaults.IncludeFormattedMessage, provider.IncludeFormattedMessage); + Assert.Equal(defaults.ParseStateValues, provider.ParseStateValues); + Assert.Null(provider.Processor); + Assert.NotNull(provider.Resource); + } + + [Fact] + public void ConfigureCtorTests() + { + OpenTelemetryLoggerOptions defaults = new(); + + using OpenTelemetryLoggerProvider provider = new(options => + { + options.IncludeScopes = !defaults.IncludeScopes; + options.IncludeFormattedMessage = !defaults.IncludeFormattedMessage; + options.ParseStateValues = !defaults.ParseStateValues; + + options.SetResourceBuilder(ResourceBuilder + .CreateEmpty() + .AddAttributes(new[] { new KeyValuePair("key1", "value1") })); + + options.AddInMemoryExporter(new List()); + }); + + Assert.Equal(!defaults.IncludeScopes, provider.IncludeScopes); + Assert.Equal(!defaults.IncludeFormattedMessage, provider.IncludeFormattedMessage); + Assert.Equal(!defaults.ParseStateValues, provider.ParseStateValues); + Assert.NotNull(provider.Processor); + Assert.NotNull(provider.Resource); + Assert.Contains(provider.Resource.Attributes, value => value.Key == "key1" && (string)value.Value == "value1"); + } + + [Fact] + public void ForceFlushTest() + { + using OpenTelemetryLoggerProvider provider = new(); + + Assert.True(provider.ForceFlush()); + + List exportedItems = new(); + + provider.AddProcessor(new BatchLogRecordExportProcessor(new InMemoryExporter(exportedItems))); + + var logger = provider.CreateLogger("TestLogger"); + + logger.LogInformation("hello world"); + + Assert.Empty(exportedItems); + + Assert.True(provider.ForceFlush()); + + Assert.Single(exportedItems); + } + + [Fact] + public void ThreadStaticPoolUsedByProviderTests() + { + using var provider1 = new OpenTelemetryLoggerProvider(new OpenTelemetryLoggerOptions()); + + Assert.Equal(LogRecordThreadStaticPool.Instance, provider1.LogRecordPool); + + var options = new OpenTelemetryLoggerOptions(); + options.AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())); + + using var provider2 = new OpenTelemetryLoggerProvider(options); + + Assert.Equal(LogRecordThreadStaticPool.Instance, provider2.LogRecordPool); + + options.AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())); + + using var provider3 = new OpenTelemetryLoggerProvider(options); + + Assert.Equal(LogRecordThreadStaticPool.Instance, provider3.LogRecordPool); + } + + [Fact] + public void SharedPoolUsedByProviderTests() + { + var options = new OpenTelemetryLoggerOptions(); + options.AddProcessor(new BatchLogRecordExportProcessor(new NoopExporter())); + + using var provider1 = new OpenTelemetryLoggerProvider(options); + + Assert.Equal(LogRecordSharedPool.Current, provider1.LogRecordPool); + + options = new OpenTelemetryLoggerOptions(); + options.AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())); + options.AddProcessor(new BatchLogRecordExportProcessor(new NoopExporter())); + + using var provider2 = new OpenTelemetryLoggerProvider(options); + + Assert.Equal(LogRecordSharedPool.Current, provider2.LogRecordPool); + + options = new OpenTelemetryLoggerOptions(); + options.AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())); + options.AddProcessor(new CompositeProcessor(new BaseProcessor[] + { + new SimpleLogRecordExportProcessor(new NoopExporter()), + new BatchLogRecordExportProcessor(new NoopExporter()), + })); + + using var provider3 = new OpenTelemetryLoggerProvider(options); + + Assert.Equal(LogRecordSharedPool.Current, provider3.LogRecordPool); + } + + private sealed class NoopExporter : BaseExporter + { + public override ExportResult Export(in Batch batch) + { + return ExportResult.Success; + } + } + } +} diff --git a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs new file mode 100644 index 000000000..5f07ee7b0 --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs @@ -0,0 +1,211 @@ +// +// 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. +// + +#nullable enable + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace OpenTelemetry.Logs.Tests; + +public sealed class OpenTelemetryLoggingExtensionsTests +{ + [Fact] + public void ServiceCollectionAddOpenTelemetryNoParametersTest() + { + bool optionsCallbackInvoked = false; + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(configure => + { + configure.AddOpenTelemetry(); + }); + + serviceCollection.Configure(options => + { + optionsCallbackInvoked = true; + }); + + using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); + + ILoggerFactory? loggerFactory = serviceProvider.GetService(); + + Assert.NotNull(loggerFactory); + + Assert.True(optionsCallbackInvoked); + } + + [Theory] + [InlineData(1, 0)] + [InlineData(1, 1)] + [InlineData(5, 5)] + public void ServiceCollectionAddOpenTelemetryConfigureActionTests(int numberOfBuilderRegistrations, int numberOfOptionsRegistrations) + { + int configureCallbackInvocations = 0; + int optionsCallbackInvocations = 0; + OpenTelemetryLoggerOptions? optionsInstance = null; + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(configure => + { + for (int i = 0; i < numberOfBuilderRegistrations; i++) + { + configure.AddOpenTelemetry(ConfigureCallback); + } + }); + + for (int i = 0; i < numberOfOptionsRegistrations; i++) + { + serviceCollection.Configure(OptionsCallback); + } + + using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); + + ILoggerFactory? loggerFactory = serviceProvider.GetService(); + + Assert.NotNull(loggerFactory); + + Assert.NotNull(optionsInstance); + + Assert.Equal(numberOfBuilderRegistrations, configureCallbackInvocations); + Assert.Equal(numberOfOptionsRegistrations, optionsCallbackInvocations); + + void ConfigureCallback(OpenTelemetryLoggerOptions options) + { + if (optionsInstance == null) + { + optionsInstance = options; + } + else + { + Assert.Equal(optionsInstance, options); + } + + configureCallbackInvocations++; + } + + void OptionsCallback(OpenTelemetryLoggerOptions options) + { + if (optionsInstance == null) + { + optionsInstance = options; + } + else + { + Assert.Equal(optionsInstance, options); + } + + optionsCallbackInvocations++; + } + } + + [Fact] + public void ServiceCollectionAddOpenTelemetryWithProviderTest() + { + var provider = new WrappedOpenTelemetryLoggerProvider(); + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(configure => + { + configure.AddOpenTelemetry(provider); + }); + + using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider()) + { + ILoggerFactory? loggerFactory = serviceProvider.GetService(); + + Assert.NotNull(loggerFactory); + + loggerFactory!.Dispose(); + + // Note: Provider disposal does not actually happen until serviceProvider is disposed + Assert.False(provider.Disposed); + } + + Assert.True(provider.Disposed); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ServiceCollectionAddOpenTelemetryWithProviderAndDisposeSpecifiedTests(bool dispose) + { + var provider = new WrappedOpenTelemetryLoggerProvider(); + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(configure => + { + configure.AddOpenTelemetry(provider, disposeProvider: dispose); + }); + + using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider()) + { + ILoggerFactory? loggerFactory = serviceProvider.GetService(); + + Assert.NotNull(loggerFactory); + + loggerFactory!.Dispose(); + + // Note: Provider disposal does not actually happen until serviceProvider is disposed + Assert.False(provider.Disposed); + } + + Assert.Equal(dispose, provider.Disposed); + + provider.Dispose(); + + Assert.True(provider.Disposed); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void LoggerFactoryCreateAddOpenTelemetryWithProviderAndDisposeSpecifiedTests(bool dispose) + { + var provider = new WrappedOpenTelemetryLoggerProvider(); + + using (var factory = LoggerFactory.Create(configure => + { + configure.AddOpenTelemetry(provider, disposeProvider: dispose); + })) + { + Assert.False(provider.Disposed); + } + + Assert.Equal(dispose, provider.Disposed); + + provider.Dispose(); + + Assert.True(provider.Disposed); + } + + private sealed class WrappedOpenTelemetryLoggerProvider : OpenTelemetryLoggerProvider + { + public bool Disposed { get; private set; } + + protected override void Dispose(bool disposing) + { + this.Disposed = true; + + base.Dispose(disposing); + } + } +} diff --git a/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs b/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs new file mode 100644 index 000000000..e83b4bda4 --- /dev/null +++ b/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs @@ -0,0 +1,415 @@ +// +// 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; +using Xunit; + +namespace OpenTelemetry.Metrics.Tests; + +public class CircularBufferBucketsTest +{ + [Fact] + public void ConstructorThrowsOnInvalidCapacity() + { + Assert.Throws(() => new CircularBufferBuckets(0)); + Assert.Throws(() => new CircularBufferBuckets(-1)); + } + + [Fact] + public void BasicInsertions() + { + var buckets = new CircularBufferBuckets(5); + + Assert.Equal(5, buckets.Capacity); + Assert.Equal(0, buckets.Size); + + Assert.Equal(0, buckets.TryIncrement(0)); + Assert.Equal(1, buckets.Size); + + Assert.Equal(0, buckets.TryIncrement(1)); + Assert.Equal(2, buckets.Size); + + Assert.Equal(0, buckets.TryIncrement(3)); + Assert.Equal(4, buckets.Size); + + Assert.Equal(0, buckets.TryIncrement(4)); + Assert.Equal(5, buckets.Size); + + Assert.Equal(0, buckets.TryIncrement(2)); + Assert.Equal(5, buckets.Size); + + Assert.Equal(1, buckets.TryIncrement(9)); + Assert.Equal(1, buckets.TryIncrement(5)); + Assert.Equal(1, buckets.TryIncrement(-1)); + Assert.Equal(2, buckets.TryIncrement(10)); + Assert.Equal(2, buckets.TryIncrement(19)); + Assert.Equal(3, buckets.TryIncrement(20)); + Assert.Equal(3, buckets.TryIncrement(39)); + Assert.Equal(4, buckets.TryIncrement(40)); + Assert.Equal(5, buckets.Size); + } + + [Fact] + public void PositiveInsertions() + { + var buckets = new CircularBufferBuckets(5); + + Assert.Equal(0, buckets.TryIncrement(102)); + Assert.Equal(0, buckets.TryIncrement(103)); + Assert.Equal(0, buckets.TryIncrement(101)); + Assert.Equal(0, buckets.TryIncrement(100)); + Assert.Equal(0, buckets.TryIncrement(104)); + + Assert.Equal(100, buckets.Offset); + Assert.Equal(5, buckets.Size); + + Assert.Equal(1, buckets.TryIncrement(99)); + Assert.Equal(1, buckets.TryIncrement(105)); + } + + [Fact] + public void NegativeInsertions() + { + var buckets = new CircularBufferBuckets(5); + + Assert.Equal(0, buckets.TryIncrement(2)); + Assert.Equal(0, buckets.TryIncrement(0)); + Assert.Equal(0, buckets.TryIncrement(-2)); + Assert.Equal(0, buckets.TryIncrement(1)); + Assert.Equal(0, buckets.TryIncrement(-1)); + + Assert.Equal(-2, buckets.Offset); + Assert.Equal(5, buckets.Size); + + Assert.Equal(1, buckets.TryIncrement(3)); + Assert.Equal(1, buckets.TryIncrement(-3)); + } + + [Fact] + public void IntegerOverflow() + { + var buckets = new CircularBufferBuckets(2); + + Assert.Equal(0, buckets.TryIncrement(int.MaxValue)); + + Assert.Equal(int.MaxValue, buckets.Offset); + Assert.Equal(1, buckets.Size); + + Assert.Equal(30, buckets.TryIncrement(1)); + Assert.Equal(30, buckets.TryIncrement(0)); + Assert.Equal(31, buckets.TryIncrement(-1)); + Assert.Equal(31, buckets.TryIncrement(int.MinValue + 1)); + Assert.Equal(31, buckets.TryIncrement(int.MinValue)); + } + + [Fact] + public void IndexOperations() + { + var buckets = new CircularBufferBuckets(5); + + buckets.TryIncrement(2); + buckets.TryIncrement(2); + buckets.TryIncrement(2); + buckets.TryIncrement(2); + buckets.TryIncrement(2); + buckets.TryIncrement(0); + buckets.TryIncrement(0); + buckets.TryIncrement(0); + buckets.TryIncrement(-2); + buckets.TryIncrement(1); + buckets.TryIncrement(1); + buckets.TryIncrement(1); + buckets.TryIncrement(1); + buckets.TryIncrement(-1); + buckets.TryIncrement(-1); + + Assert.Equal(-2, buckets.Offset); + + Assert.Equal(1, buckets[-2]); + Assert.Equal(2, buckets[-1]); + Assert.Equal(3, buckets[0]); + Assert.Equal(4, buckets[1]); + Assert.Equal(5, buckets[2]); + } + + [Fact] + public void ScaleDownCapacity1() + { + var buckets = new CircularBufferBuckets(1); + + buckets.ScaleDown(1); + buckets.ScaleDown(2); + buckets.ScaleDown(3); + buckets.ScaleDown(4); + + buckets.TryIncrement(0); + + Assert.Equal(0, buckets.Offset); + Assert.Equal(1, buckets.Size); + Assert.Equal(1, buckets[0]); + } + + [Fact] + public void ScaleDownIntMaxValue() + { + var buckets = new CircularBufferBuckets(1); + + buckets.TryIncrement(int.MaxValue); + + Assert.Equal(int.MaxValue, buckets.Offset); + + buckets.ScaleDown(1); + + Assert.Equal(0x3FFFFFFF, buckets.Offset); + Assert.Equal(1, buckets[0x3FFFFFFF]); + } + + [Fact] + public void ScaleDownIntMinValue() + { + var buckets = new CircularBufferBuckets(1); + + buckets.TryIncrement(int.MinValue); + + Assert.Equal(int.MinValue, buckets.Offset); + + buckets.ScaleDown(1); + + Assert.Equal(-0x40000000, buckets.Offset); + Assert.Equal(1, buckets[-0x40000000]); + } + + [Fact] + public void ScaleDownCapacity2() + { + var buckets = new CircularBufferBuckets(2); + + buckets.TryIncrement(int.MinValue, 2); + buckets.TryIncrement(int.MinValue + 1); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Size); + Assert.Equal(3, buckets[buckets.Offset]); + + buckets = new CircularBufferBuckets(2); + + buckets.TryIncrement(int.MaxValue - 1, 2); + buckets.TryIncrement(int.MaxValue); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Size); + Assert.Equal(3, buckets[buckets.Offset]); + Assert.Equal(0, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(2); + + buckets.TryIncrement(int.MaxValue - 2, 2); + buckets.TryIncrement(int.MaxValue - 1); + buckets.ScaleDown(1); + + Assert.Equal(2, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(1, buckets[buckets.Offset + 1]); + } + + [Fact] + public void ScaleDownCapacity3() + { + var buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(0, 2); + buckets.TryIncrement(1, 4); + buckets.TryIncrement(2, 8); + buckets.ScaleDown(1); + + Assert.Equal(0, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(8, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(1, 2); + buckets.TryIncrement(2, 4); + buckets.TryIncrement(3, 8); + buckets.ScaleDown(1); + + Assert.Equal(0, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(2, 2); + buckets.TryIncrement(3, 4); + buckets.TryIncrement(4, 8); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(8, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(3, 2); + buckets.TryIncrement(4, 4); + buckets.TryIncrement(5, 8); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(4, 2); + buckets.TryIncrement(5, 4); + buckets.TryIncrement(6, 8); + buckets.ScaleDown(1); + + Assert.Equal(2, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(8, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(5, 2); + buckets.TryIncrement(6, 4); + buckets.TryIncrement(7, 8); + buckets.ScaleDown(1); + + Assert.Equal(2, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + } + + [Fact] + public void ScaleDownCapacity4() + { + var buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(0, 2); + buckets.TryIncrement(1, 4); + buckets.TryIncrement(2, 8); + buckets.TryIncrement(2, 16); + buckets.ScaleDown(1); + + Assert.Equal(0, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(24, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(1, 2); + buckets.TryIncrement(2, 4); + buckets.TryIncrement(3, 8); + buckets.TryIncrement(4, 16); + buckets.ScaleDown(1); + + Assert.Equal(0, buckets.Offset); + Assert.Equal(3, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + Assert.Equal(16, buckets[buckets.Offset + 2]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(2, 2); + buckets.TryIncrement(3, 4); + buckets.TryIncrement(4, 8); + buckets.TryIncrement(5, 16); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(24, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(3, 2); + buckets.TryIncrement(4, 4); + buckets.TryIncrement(5, 8); + buckets.TryIncrement(6, 16); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Offset); + Assert.Equal(3, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + Assert.Equal(16, buckets[buckets.Offset + 2]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(4, 2); + buckets.TryIncrement(5, 4); + buckets.TryIncrement(6, 8); + buckets.TryIncrement(7, 16); + buckets.ScaleDown(1); + + Assert.Equal(2, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(24, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(5, 2); + buckets.TryIncrement(6, 4); + buckets.TryIncrement(7, 8); + buckets.TryIncrement(8, 16); + buckets.ScaleDown(1); + + Assert.Equal(2, buckets.Offset); + Assert.Equal(3, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + Assert.Equal(16, buckets[buckets.Offset + 2]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(6, 2); + buckets.TryIncrement(7, 4); + buckets.TryIncrement(8, 8); + buckets.TryIncrement(9, 16); + buckets.ScaleDown(1); + + Assert.Equal(3, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(24, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(7, 2); + buckets.TryIncrement(8, 4); + buckets.TryIncrement(9, 8); + buckets.TryIncrement(10, 16); + buckets.ScaleDown(1); + + Assert.Equal(3, buckets.Offset); + Assert.Equal(3, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + Assert.Equal(16, buckets[buckets.Offset + 2]); + } +} diff --git a/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs b/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs new file mode 100644 index 000000000..59f80ad09 --- /dev/null +++ b/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs @@ -0,0 +1,257 @@ +// +// 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. +// + +#if NET6_0_OR_GREATER + +using System; +using OpenTelemetry.Tests; +using Xunit; + +namespace OpenTelemetry.Metrics.Tests; + +public class ExponentialBucketHistogramTest +{ + [Fact] + public void IndexLookup() + { + /* + An exponential bucket histogram with scale = 0. + The base is 2 ^ (2 ^ 0) = 2. + The buckets are: + ... + bucket[-3]: (1/8, 1/4] + bucket[-2]: (1/4, 1/2] + bucket[-1]: (1/2, 1] + bucket[0]: (1, 2] + bucket[1]: (2, 4] + bucket[2]: (4, 8] + bucket[3]: (8, 16] + ... + */ + var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0); + + Assert.Equal(-1075, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon) + Assert.Equal(-1074, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2 + Assert.Equal(-1073, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000011"))); // double.Epsilon * 3 + Assert.Equal(-1073, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000100"))); // double.Epsilon * 4 + Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000101"))); // double.Epsilon * 5 + Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6 + Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7 + Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8 + Assert.Equal(-1025, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024) + Assert.Equal(-1024, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309 + Assert.Equal(-1024, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023) + Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308 + Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive) + Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022) + Assert.Equal(-1022, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308 + Assert.Equal(-3, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25 + Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000001"))); // ~0.5000000000000001 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000000 0000000000000000000000000000000000000000000000000000"))); // 2 + Assert.Equal(1, histogram.MapToIndex(IEEE754Double.FromString("0 10000000001 0000000000000000000000000000000000000000000000000000"))); // 4 + Assert.Equal(2, histogram.MapToIndex(IEEE754Double.FromString("0 10000000010 0000000000000000000000000000000000000000000000000000"))); // 8 + Assert.Equal(3, histogram.MapToIndex(IEEE754Double.FromString("0 10000000011 0000000000000000000000000000000000000000000000000000"))); // 16 + Assert.Equal(4, histogram.MapToIndex(IEEE754Double.FromString("0 10000000100 0000000000000000000000000000000000000000000000000000"))); // 32 + Assert.Equal(1022, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000000"))); // ~8.98846567431158E+307 + Assert.Equal(1023, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000001"))); // ~8.988465674311582E+307 + Assert.Equal(1023, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308 + Assert.Equal(1023, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue) + + /* + An exponential bucket histogram with scale = -1. + The base is 2 ^ (2 ^ 1) = 4. + The buckets are: + ... + bucket[-3]: (1/64, 1/16] + bucket[-2]: (1/16, 1/4] + bucket[-1]: (1/4, 1] + bucket[0]: (1, 4] + bucket[1]: (4, 16] + bucket[2]: (16, 64] + bucket[3]: (64, 256] + ... + */ + histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -1); + + Assert.Equal(-538, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon) + Assert.Equal(-537, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2 + Assert.Equal(-537, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000011"))); // double.Epsilon * 3 + Assert.Equal(-537, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000100"))); // double.Epsilon * 4 + Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000101"))); // double.Epsilon * 5 + Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6 + Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7 + Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8 + Assert.Equal(-513, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024) + Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309 + Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023) + Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308 + Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive) + Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022) + Assert.Equal(-511, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308 + Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000001"))); // ~0.5000000000000001 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000000 0000000000000000000000000000000000000000000000000000"))); // 2 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000001 0000000000000000000000000000000000000000000000000000"))); // 4 + Assert.Equal(1, histogram.MapToIndex(IEEE754Double.FromString("0 10000000010 0000000000000000000000000000000000000000000000000000"))); // 8 + Assert.Equal(1, histogram.MapToIndex(IEEE754Double.FromString("0 10000000011 0000000000000000000000000000000000000000000000000000"))); // 16 + Assert.Equal(2, histogram.MapToIndex(IEEE754Double.FromString("0 10000000100 0000000000000000000000000000000000000000000000000000"))); // 32 + Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000000"))); // ~8.98846567431158E+307 + Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000001"))); // ~8.988465674311582E+307 + Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308 + Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue) + + /* + An exponential bucket histogram with scale = -2. + The base is 2 ^ (2 ^ 2) = 16. + The buckets are: + ... + bucket[-3]: (1/4096, 1/256] + bucket[-2]: (1/256, 1/16] + bucket[-1]: (1/16, 1] + bucket[0]: (1, 16] + bucket[1]: (16, 256] + bucket[2]: (256, 4096] + bucket[3]: (4096, 65536] + ... + */ + histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -2); + + Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon) + Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2 + Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000011"))); // double.Epsilon * 3 + Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000100"))); // double.Epsilon * 4 + Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000101"))); // double.Epsilon * 5 + Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6 + Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7 + Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8 + Assert.Equal(-257, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024) + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309 + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023) + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308 + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive) + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022) + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000001"))); // ~0.5000000000000001 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000000 0000000000000000000000000000000000000000000000000000"))); // 2 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000001 0000000000000000000000000000000000000000000000000000"))); // 4 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000010 0000000000000000000000000000000000000000000000000000"))); // 8 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000011 0000000000000000000000000000000000000000000000000000"))); // 16 + Assert.Equal(1, histogram.MapToIndex(IEEE754Double.FromString("0 10000000100 0000000000000000000000000000000000000000000000000000"))); // 32 + Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000000"))); // ~8.98846567431158E+307 + Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000001"))); // ~8.988465674311582E+307 + Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308 + Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue) + + /* + An exponential bucket histogram with scale = -10. + The base is 2 ^ (2 ^ 10) = 2 ^ 1024 = double.MaxValue + 2 ^ -52 (slightly bigger than double.MaxValue). + The buckets are: + bucket[-2]: [double.Epsilon, 2 ^ -1024] + bucket[-1]: (2 ^ -1024, 1] + bucket[0]: (1, double.MaxValue] + */ + histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -10); + + Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon) + Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.1125369292536007E-308 (2 ^ -1023) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue) + + /* + An exponential bucket histogram with scale = -11. + The base is 2 ^ (2 ^ 11) = 2 ^ 2048 (much bigger than double.MaxValue). + The buckets are: + bucket[-1]: [double.Epsilon, 1] + bucket[0]: (1, double.MaxValue] + */ + histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -11); + + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue) + + /* + An exponential bucket histogram with scale = 1. + The base is 2 ^ (2 ^ -1) = sqrt(2) = 1.41421356237. + The buckets are: + ... + bucket[-3]: (0.35355339059, 1/2] + bucket[-2]: (1/2, 0.70710678118] + bucket[-1]: (0.70710678118, 1] + bucket[0]: (1, 1.41421356237] + bucket[1]: (1.41421356237, 2] + bucket[2]: (2, 2.82842712474] + bucket[3]: (2.82842712474, 4] + ... + */ + histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 1); + } + + [Fact] + public void InfinityHandling() + { + var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0); + + histogram.Record(double.PositiveInfinity); + histogram.Record(double.NegativeInfinity); + + Assert.Equal(0, histogram.ZeroCount + histogram.PositiveBuckets.Size + histogram.NegativeBuckets.Size); + } + + [Fact] + public void NaNHandling() + { + var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0); + + histogram.Record(double.NaN); // NaN (language/runtime native) + histogram.Record(IEEE754Double.FromString("0 11111111111 0000000000000000000000000000000000000000000000000001").DoubleValue); // sNaN on x86/64 and ARM + histogram.Record(IEEE754Double.FromString("0 11111111111 1000000000000000000000000000000000000000000000000001").DoubleValue); // qNaN on x86/64 and ARM + histogram.Record(IEEE754Double.FromString("0 11111111111 1111111111111111111111111111111111111111111111111111").DoubleValue); // NaN (alternative encoding) + + Assert.Equal(0, histogram.ZeroCount + histogram.PositiveBuckets.Size + histogram.NegativeBuckets.Size); + } + + [Fact] + public void ZeroHandling() + { + var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0); + + histogram.Record(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000000").DoubleValue); // +0 + histogram.Record(IEEE754Double.FromString("1 00000000000 0000000000000000000000000000000000000000000000000000").DoubleValue); // -0 + + Assert.Equal(2, histogram.ZeroCount); + } +} + +#endif diff --git a/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTests.cs new file mode 100644 index 000000000..254edf887 --- /dev/null +++ b/test/OpenTelemetry.Tests/Metrics/MetricSnapshotTests.cs @@ -0,0 +1,168 @@ +// +// 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.Collections.Generic; +using System.Diagnostics.Metrics; + +using OpenTelemetry.Tests; + +using Xunit; + +namespace OpenTelemetry.Metrics.Tests +{ + public class MetricSnapshotTests + { + [Fact] + public void VerifySnapshot_Counter() + { + var exportedMetrics = new List(); + var exportedSnapshots = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + var counter = meter.CreateCounter("meter"); + using var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(exportedMetrics) + .AddInMemoryExporter(exportedSnapshots) + .Build(); + + // FIRST EXPORT + counter.Add(10); + meterProvider.ForceFlush(); + + // Verify Metric 1 + Assert.Single(exportedMetrics); + var metric1 = exportedMetrics[0]; + var metricPoints1Enumerator = metric1.GetMetricPoints().GetEnumerator(); + Assert.True(metricPoints1Enumerator.MoveNext()); + ref readonly var metricPoint1 = ref metricPoints1Enumerator.Current; + Assert.Equal(10, metricPoint1.GetSumLong()); + + // Verify Snapshot 1 + Assert.Single(exportedSnapshots); + var snapshot1 = exportedSnapshots[0]; + Assert.Single(snapshot1.MetricPoints); + Assert.Equal(10, snapshot1.MetricPoints[0].GetSumLong()); + + // Verify Metric == Snapshot + Assert.Equal(metric1.Name, snapshot1.Name); + Assert.Equal(metric1.Description, snapshot1.Description); + Assert.Equal(metric1.Unit, snapshot1.Unit); + Assert.Equal(metric1.MeterName, snapshot1.MeterName); + Assert.Equal(metric1.MetricType, snapshot1.MetricType); + Assert.Equal(metric1.MeterVersion, snapshot1.MeterVersion); + + // SECOND EXPORT + counter.Add(5); + meterProvider.ForceFlush(); + + // Verify Metric 1, after second export + // This value is expected to be updated. + Assert.Equal(15, metricPoint1.GetSumLong()); + + // Verify Metric 2 + Assert.Equal(2, exportedMetrics.Count); + var metric2 = exportedMetrics[1]; + var metricPoints2Enumerator = metric2.GetMetricPoints().GetEnumerator(); + Assert.True(metricPoints2Enumerator.MoveNext()); + ref readonly var metricPoint2 = ref metricPoints2Enumerator.Current; + Assert.Equal(15, metricPoint2.GetSumLong()); + + // Verify Snapshot 1, after second export + // This value is expected to be unchanged. + Assert.Equal(10, snapshot1.MetricPoints[0].GetSumLong()); + + // Verify Snapshot 2 + Assert.Equal(2, exportedSnapshots.Count); + var snapshot2 = exportedSnapshots[1]; + Assert.Single(snapshot2.MetricPoints); + Assert.Equal(15, snapshot2.MetricPoints[0].GetSumLong()); + } + + [Fact] + public void VerifySnapshot_Histogram() + { + var exportedMetrics = new List(); + var exportedSnapshots = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + var histogram = meter.CreateHistogram("histogram"); + using var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(exportedMetrics) + .AddInMemoryExporter(exportedSnapshots) + .Build(); + + // FIRST EXPORT + histogram.Record(10); + meterProvider.ForceFlush(); + + // Verify Metric 1 + Assert.Single(exportedMetrics); + var metric1 = exportedMetrics[0]; + var metricPoints1Enumerator = metric1.GetMetricPoints().GetEnumerator(); + Assert.True(metricPoints1Enumerator.MoveNext()); + ref readonly var metricPoint1 = ref metricPoints1Enumerator.Current; + Assert.Equal(1, metricPoint1.GetHistogramCount()); + Assert.Equal(10, metricPoint1.GetHistogramSum()); + + // Verify Snapshot 1 + Assert.Single(exportedSnapshots); + var snapshot1 = exportedSnapshots[0]; + Assert.Single(snapshot1.MetricPoints); + Assert.Equal(1, snapshot1.MetricPoints[0].GetHistogramCount()); + Assert.Equal(10, snapshot1.MetricPoints[0].GetHistogramSum()); + + // Verify Metric == Snapshot + Assert.Equal(metric1.Name, snapshot1.Name); + Assert.Equal(metric1.Description, snapshot1.Description); + Assert.Equal(metric1.Unit, snapshot1.Unit); + Assert.Equal(metric1.MeterName, snapshot1.MeterName); + Assert.Equal(metric1.MetricType, snapshot1.MetricType); + Assert.Equal(metric1.MeterVersion, snapshot1.MeterVersion); + + // SECOND EXPORT + histogram.Record(5); + meterProvider.ForceFlush(); + + // Verify Metric 1 after second export + // This value is expected to be updated. + Assert.Equal(2, metricPoint1.GetHistogramCount()); + Assert.Equal(15, metricPoint1.GetHistogramSum()); + + // Verify Metric 2 + Assert.Equal(2, exportedMetrics.Count); + var metric2 = exportedMetrics[1]; + var metricPoints2Enumerator = metric2.GetMetricPoints().GetEnumerator(); + Assert.True(metricPoints2Enumerator.MoveNext()); + ref readonly var metricPoint2 = ref metricPoints2Enumerator.Current; + Assert.Equal(2, metricPoint2.GetHistogramCount()); + Assert.Equal(15, metricPoint2.GetHistogramSum()); + + // Verify Snapshot 1 after second export + // This value is expected to be unchanged. + Assert.Equal(1, snapshot1.MetricPoints[0].GetHistogramCount()); + Assert.Equal(10, snapshot1.MetricPoints[0].GetHistogramSum()); + + // Verify Snapshot 2 + Assert.Equal(2, exportedSnapshots.Count); + var snapshot2 = exportedSnapshots[1]; + Assert.Single(snapshot2.MetricPoints); + Assert.Equal(2, snapshot2.MetricPoints[0].GetHistogramCount()); + Assert.Equal(15, snapshot2.MetricPoints[0].GetHistogramSum()); + } + } +} diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/BasicTests.cs b/test/OpenTelemetry.Tests/Shared/DelegatingExporter.cs similarity index 59% rename from test/OpenTelemetry.Instrumentation.AspNet.Tests/BasicTests.cs rename to test/OpenTelemetry.Tests/Shared/DelegatingExporter.cs index d3ded5f63..13cef6924 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Tests/Shared/DelegatingExporter.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,18 +15,13 @@ // using System; -using OpenTelemetry.Trace; -using Xunit; -namespace OpenTelemetry.Instrumentation.AspNet.Tests +namespace OpenTelemetry.Tests; + +internal sealed class DelegatingExporter : BaseExporter + where T : class { - public class BasicTests - { - [Fact] - public void AddAspNetInstrumentation_BadArgs() - { - TracerProviderBuilder builder = null; - Assert.Throws(() => builder.AddAspNetInstrumentation()); - } - } + public Func, ExportResult> OnExportFunc { get; set; } = (batch) => default; + + public override ExportResult Export(in Batch batch) => this.OnExportFunc(batch); } diff --git a/test/OpenTelemetry.Tests/Shared/DelegatingTestExporter.cs b/test/OpenTelemetry.Tests/Shared/DelegatingTestExporter.cs deleted file mode 100644 index 108003691..000000000 --- a/test/OpenTelemetry.Tests/Shared/DelegatingTestExporter.cs +++ /dev/null @@ -1,46 +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; -using System.Collections.Generic; - -namespace OpenTelemetry.Tests -{ - public class DelegatingTestExporter : BaseExporter - where T : class - { - private readonly BaseExporter exporter; - private readonly Action onExportAction; - - public DelegatingTestExporter( - BaseExporter exporter, - Action onExportAction = null) - { - this.exporter = exporter; - this.onExportAction = onExportAction; - } - - public List ExportResults { get; } = new(); - - public override ExportResult Export(in Batch batch) - { - var result = this.exporter.Export(batch); - this.ExportResults.Add(result); - this.onExportAction?.Invoke(); - return result; - } - } -} diff --git a/test/OpenTelemetry.Tests/Shared/IEEE754Double.cs b/test/OpenTelemetry.Tests/Shared/IEEE754Double.cs new file mode 100644 index 000000000..4ae89faab --- /dev/null +++ b/test/OpenTelemetry.Tests/Shared/IEEE754Double.cs @@ -0,0 +1,103 @@ +// +// 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; +using System.Runtime.InteropServices; + +namespace OpenTelemetry.Tests; + +[StructLayout(LayoutKind.Explicit)] +public struct IEEE754Double +{ + [FieldOffset(0)] + public double DoubleValue = 0; + + [FieldOffset(0)] + public long LongValue = 0; + + [FieldOffset(0)] + public ulong ULongValue = 0; + + public IEEE754Double(double value) + { + this.DoubleValue = value; + } + + public static implicit operator double(IEEE754Double value) + { + return value.DoubleValue; + } + + public static IEEE754Double operator ++(IEEE754Double value) + { + value.ULongValue++; + return value; + } + + public static IEEE754Double operator --(IEEE754Double value) + { + value.ULongValue--; + return value; + } + + public static IEEE754Double FromDouble(double value) + { + return new IEEE754Double(value); + } + + public static IEEE754Double FromLong(long value) + { + return new IEEE754Double { LongValue = value }; + } + + public static IEEE754Double FromULong(ulong value) + { + return new IEEE754Double { ULongValue = value }; + } + + public static IEEE754Double FromString(string value) + { + return IEEE754Double.FromLong(Convert.ToInt64(value.Replace(" ", string.Empty), 2)); + } + + public override string ToString() + { + Span chars = stackalloc char[66]; + + var bits = this.ULongValue; + var index = chars.Length - 1; + + for (int i = 0; i < 52; i++) + { + chars[index--] = (char)(bits & 0x01 | 0x30); + bits >>= 1; + } + + chars[index--] = ' '; + + for (int i = 0; i < 11; i++) + { + chars[index--] = (char)(bits & 0x01 | 0x30); + bits >>= 1; + } + + chars[index--] = ' '; + + chars[index--] = (char)(bits & 0x01 | 0x30); + + return $"{chars.ToString()} ({this.DoubleValue})"; + } +} diff --git a/test/OpenTelemetry.Tests/Shared/TestEventListener.cs b/test/OpenTelemetry.Tests/Shared/TestEventListener.cs index 9c6ffc3e5..edff6577c 100644 --- a/test/OpenTelemetry.Tests/Shared/TestEventListener.cs +++ b/test/OpenTelemetry.Tests/Shared/TestEventListener.cs @@ -90,7 +90,7 @@ namespace OpenTelemetry.Tests /// The event source that was created. protected override void OnEventSourceCreated(EventSource eventSource) { - // Check for null because this method is called by the base class constror before we can initialize it + // Check for null because this method is called by the base class constructor before we can initialize it Action callback = this.OnOnEventSourceCreated; callback?.Invoke(eventSource); } diff --git a/test/OpenTelemetry.Tests/Trace/ActivityExtensionsTest.cs b/test/OpenTelemetry.Tests/Trace/ActivityExtensionsTest.cs index b67dc8033..fbfd54c27 100644 --- a/test/OpenTelemetry.Tests/Trace/ActivityExtensionsTest.cs +++ b/test/OpenTelemetry.Tests/Trace/ActivityExtensionsTest.cs @@ -152,6 +152,43 @@ namespace OpenTelemetry.Trace.Tests Assert.Equal("System.ArgumentNullException", @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionType).Value); } + [Fact] + public void RecordExceptionWithAdditionalTags() + { + var message = "message"; + var exception = new ArgumentNullException(message, new Exception(message)); + var activity = new Activity("test-activity"); + + var tags = new TagList + { + { "key1", "value1" }, + { "key2", "value2" }, + }; + + activity.RecordException(exception, tags); + + // Additional tags passed in override attributes added from the exception + tags.Add(SemanticConventions.AttributeExceptionMessage, "SomeOtherExceptionMessage"); + tags.Add(SemanticConventions.AttributeExceptionType, "SomeOtherExceptionType"); + + activity.RecordException(exception, tags); + + var events = activity.Events.ToArray(); + Assert.Equal(2, events.Length); + + Assert.Equal(SemanticConventions.AttributeExceptionEventName, events[0].Name); + Assert.Equal(message, events[0].Tags.First(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value); + Assert.Equal("System.ArgumentNullException", events[0].Tags.First(t => t.Key == SemanticConventions.AttributeExceptionType).Value); + Assert.Equal("value1", events[0].Tags.First(t => t.Key == "key1").Value); + Assert.Equal("value2", events[0].Tags.First(t => t.Key == "key2").Value); + + Assert.Equal(SemanticConventions.AttributeExceptionEventName, events[1].Name); + Assert.Equal("SomeOtherExceptionMessage", events[1].Tags.First(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value); + Assert.Equal("SomeOtherExceptionType", events[1].Tags.First(t => t.Key == SemanticConventions.AttributeExceptionType).Value); + Assert.Equal("value1", events[1].Tags.First(t => t.Key == "key1").Value); + Assert.Equal("value2", events[1].Tags.First(t => t.Key == "key2").Value); + } + [Fact] public void GetTagValueEmpty() { diff --git a/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs b/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs index c73f212ca..37522f89a 100644 --- a/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs +++ b/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs @@ -99,5 +99,30 @@ namespace OpenTelemetry.Trace.Tests Assert.True(p1.ForceFlushCalled); Assert.True(p2.ForceFlushCalled); } + + [Fact] + public void CompositeActivityProcessor_ForwardsParentProvider() + { + using TracerProvider provider = new TestProvider(); + + using var p1 = new TestActivityProcessor(null, null); + using var p2 = new TestActivityProcessor(null, null); + + using var processor = new CompositeProcessor(new[] { p1, p2 }); + + Assert.Null(processor.ParentProvider); + Assert.Null(p1.ParentProvider); + Assert.Null(p2.ParentProvider); + + processor.SetParentProvider(provider); + + Assert.Equal(provider, processor.ParentProvider); + Assert.Equal(provider, p1.ParentProvider); + Assert.Equal(provider, p2.ParentProvider); + } + + private sealed class TestProvider : TracerProvider + { + } } } diff --git a/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs b/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs index 1c49dfa78..b1170bbd9 100644 --- a/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/ExceptionProcessorTest.cs @@ -69,6 +69,16 @@ namespace OpenTelemetry.Trace.Tests } catch (Exception) { + /* + Note: Behavior here is different depending on the processor + architecture. + + x86: Exception is cleared BEFORE the catch runs. + Marshal.GetExceptionPointers returns zero. + + non-x86: Exception is cleared AFTER the catch runs. + Marshal.GetExceptionPointers returns non-zero. + */ activity5 = activitySource.StartActivity("Activity5"); } finally @@ -86,8 +96,15 @@ namespace OpenTelemetry.Trace.Tests Assert.Null(activity4.GetTagValue("otel.exception_pointers")); Assert.Equal(StatusCode.Unset, activity5.GetStatus().StatusCode); #if !NETFRAMEWORK - // In this rare case, the following Activity tag will not get cleaned up due to perf consideration. - Assert.NotNull(activity5.GetTagValue("otel.exception_pointers")); + if (Environment.Is64BitProcess) + { + // In this rare case, the following Activity tag will not get cleaned up due to perf consideration. + Assert.NotNull(activity5.GetTagValue("otel.exception_pointers")); + } + else + { + Assert.Null(activity5.GetTagValue("otel.exception_pointers")); + } #endif } diff --git a/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs b/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs index a18a24df0..e487d334e 100644 --- a/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs +++ b/test/OpenTelemetry.Tests/Trace/TracerProviderSdkTest.cs @@ -419,7 +419,7 @@ namespace OpenTelemetry.Trace.Tests (a) => { Assert.False(Sdk.SuppressInstrumentation); - Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true + Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true startCalled = true; }; @@ -476,7 +476,7 @@ namespace OpenTelemetry.Trace.Tests { Assert.True(samplerCalled); Assert.False(Sdk.SuppressInstrumentation); - Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true + Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true startCalled = true; }; @@ -537,7 +537,7 @@ namespace OpenTelemetry.Trace.Tests { Assert.True(samplerCalled); Assert.False(Sdk.SuppressInstrumentation); - Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true + Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true startCalled = true; }; @@ -586,7 +586,7 @@ namespace OpenTelemetry.Trace.Tests (a) => { Assert.False(Sdk.SuppressInstrumentation); - Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true + Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true startCalled = true; }; @@ -635,7 +635,7 @@ namespace OpenTelemetry.Trace.Tests (a) => { Assert.False(Sdk.SuppressInstrumentation); - Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true + Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true startCalled = true; }; @@ -684,7 +684,7 @@ namespace OpenTelemetry.Trace.Tests (a) => { Assert.False(Sdk.SuppressInstrumentation); - Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true + Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true startCalled = true; }; @@ -700,9 +700,11 @@ namespace OpenTelemetry.Trace.Tests // AddLegacyOperationName chained to TracerProviderBuilder using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddProcessor(testActivityProcessor) - .AddLegacySource(operationNameForLegacyActivity) - .Build(); + .AddProcessor(testActivityProcessor) + .AddLegacySource(operationNameForLegacyActivity) + .Build(); + + Assert.Equal(tracerProvider, testActivityProcessor.ParentProvider); Activity activity = new Activity(operationNameForLegacyActivity); activity.Start(); @@ -722,7 +724,7 @@ namespace OpenTelemetry.Trace.Tests (a) => { Assert.False(Sdk.SuppressInstrumentation); - Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true + Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true startCalledNew = true; }; @@ -736,6 +738,12 @@ namespace OpenTelemetry.Trace.Tests tracerProvider.AddProcessor(testActivityProcessorNew); + var sdkProvider = (TracerProviderSdk)tracerProvider; + + Assert.True(sdkProvider.Processor is CompositeProcessor); + Assert.Equal(tracerProvider, sdkProvider.Processor.ParentProvider); + Assert.Equal(tracerProvider, testActivityProcessorNew.ParentProvider); + Activity activityNew = new Activity(operationNameForLegacyActivity); // Create a new Activity with the same operation name activityNew.Start(); activityNew.Stop(); @@ -1060,11 +1068,15 @@ namespace OpenTelemetry.Trace.Tests Assert.True(emptyActivitySource.HasListeners()); } - [Fact] - public void TracerProviderSdkBuildsWithSDKResource() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TracerProviderSdkBuildsWithSDKResource(bool useConfigure) { - var tracerProvider = Sdk.CreateTracerProviderBuilder().SetResourceBuilder( - ResourceBuilder.CreateDefault().AddTelemetrySdk()).Build(); + var tracerProvider = useConfigure ? + Sdk.CreateTracerProviderBuilder().SetResourceBuilder( + ResourceBuilder.CreateDefault().AddTelemetrySdk()).Build() : + Sdk.CreateTracerProviderBuilder().ConfigureResource(r => r.AddTelemetrySdk()).Build(); var resource = tracerProvider.GetResource(); var attributes = resource.Attributes; @@ -1114,7 +1126,7 @@ namespace OpenTelemetry.Trace.Tests { Assert.Contains(a.OperationName, sampledActivities); Assert.False(Sdk.SuppressInstrumentation); - Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true + Assert.True(a.IsAllDataRequested); // If Processor.OnStart is called, activity's IsAllDataRequested is set to true onStartProcessedActivities.Add(a.OperationName); }; diff --git a/test/OpenTelemetry.Tests/Trace/TracerTest.cs b/test/OpenTelemetry.Tests/Trace/TracerTest.cs index dbb11051e..2e86434b4 100644 --- a/test/OpenTelemetry.Tests/Trace/TracerTest.cs +++ b/test/OpenTelemetry.Tests/Trace/TracerTest.cs @@ -25,12 +25,10 @@ namespace OpenTelemetry.Trace.Tests public class TracerTest : IDisposable { // TODO: This is only a basic test. This must cover the entire shim API scenarios. - private readonly TracerProvider tracerProvider; private readonly Tracer tracer; public TracerTest() { - this.tracerProvider = TracerProvider.Default; this.tracer = TracerProvider.Default.GetTracer("tracername", "tracerversion"); } @@ -291,7 +289,6 @@ namespace OpenTelemetry.Trace.Tests public void Dispose() { Activity.Current = null; - this.tracerProvider.Dispose(); GC.SuppressFinalize(this); } diff --git a/test/TestApp.AspNetCore.6.0/ActivityMiddleware.cs b/test/TestApp.AspNetCore.6.0/ActivityMiddleware.cs new file mode 100644 index 000000000..c45b5e828 --- /dev/null +++ b/test/TestApp.AspNetCore.6.0/ActivityMiddleware.cs @@ -0,0 +1,61 @@ +// +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace TestApp.AspNetCore._6._0 +{ + public class ActivityMiddleware + { + private readonly ActivityMiddlewareImpl impl; + private readonly RequestDelegate next; + + public ActivityMiddleware(RequestDelegate next, ActivityMiddlewareImpl impl) + { + this.next = next; + this.impl = impl; + } + + public async Task InvokeAsync(HttpContext context) + { + if (this.impl != null) + { + this.impl.PreProcess(context); + } + + await this.next(context); + + if (this.impl != null) + { + this.impl.PostProcess(context); + } + } + + public class ActivityMiddlewareImpl + { + public virtual void PreProcess(HttpContext context) + { + // Do nothing + } + + public virtual void PostProcess(HttpContext context) + { + // Do nothing + } + } + } +} diff --git a/test/TestApp.AspNetCore.6.0/Controllers/ForwardController.cs b/test/TestApp.AspNetCore.6.0/Controllers/ForwardController.cs index 36290e144..3f1a86718 100644 --- a/test/TestApp.AspNetCore.6.0/Controllers/ForwardController.cs +++ b/test/TestApp.AspNetCore.6.0/Controllers/ForwardController.cs @@ -16,9 +16,10 @@ using System.Net.Http; using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; namespace TestApp.AspNetCore._6._0.Controllers { @@ -45,7 +46,7 @@ namespace TestApp.AspNetCore._6._0.Controllers var request = new HttpRequestMessage(HttpMethod.Post, argument.Url) { Content = new StringContent( - JsonConvert.SerializeObject(argument.Arguments), + JsonSerializer.Serialize(argument.Arguments), Encoding.UTF8, "application/json"), }; @@ -62,10 +63,10 @@ namespace TestApp.AspNetCore._6._0.Controllers public class Data { - [JsonProperty("url")] + [JsonPropertyName("url")] public string Url { get; set; } - [JsonProperty("arguments")] + [JsonPropertyName("arguments")] public Data[] Arguments { get; set; } } } diff --git a/test/TestApp.AspNetCore.6.0/Startup.cs b/test/TestApp.AspNetCore.6.0/Startup.cs index 1da705364..988ee7470 100644 --- a/test/TestApp.AspNetCore.6.0/Startup.cs +++ b/test/TestApp.AspNetCore.6.0/Startup.cs @@ -39,6 +39,8 @@ namespace TestApp.AspNetCore._6._0 services.AddSingleton(); services.AddSingleton( new CallbackMiddleware.CallbackMiddlewareImpl()); + services.AddSingleton( + new ActivityMiddleware.ActivityMiddlewareImpl()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -50,6 +52,7 @@ namespace TestApp.AspNetCore._6._0 } app.UseMiddleware(); + app.UseMiddleware(); app.UseRouting(); app.UseAuthorization(); diff --git a/test/TestApp.AspNetCore.7.0/ActivityMiddleware.cs b/test/TestApp.AspNetCore.7.0/ActivityMiddleware.cs new file mode 100644 index 000000000..70a28e214 --- /dev/null +++ b/test/TestApp.AspNetCore.7.0/ActivityMiddleware.cs @@ -0,0 +1,61 @@ +// +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace TestApp.AspNetCore._7._0 +{ + public class ActivityMiddleware + { + private readonly ActivityMiddlewareImpl impl; + private readonly RequestDelegate next; + + public ActivityMiddleware(RequestDelegate next, ActivityMiddlewareImpl impl) + { + this.next = next; + this.impl = impl; + } + + public async Task InvokeAsync(HttpContext context) + { + if (this.impl != null) + { + this.impl.PreProcess(context); + } + + await this.next(context); + + if (this.impl != null) + { + this.impl.PostProcess(context); + } + } + + public class ActivityMiddlewareImpl + { + public virtual void PreProcess(HttpContext context) + { + // do nothing + } + + public virtual void PostProcess(HttpContext context) + { + // do nothing + } + } + } +} diff --git a/test/TestApp.AspNetCore.7.0/AssemblyInfo.cs b/test/TestApp.AspNetCore.7.0/AssemblyInfo.cs index e953a8003..e78e70ff5 100644 --- a/test/TestApp.AspNetCore.7.0/AssemblyInfo.cs +++ b/test/TestApp.AspNetCore.7.0/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Diagnostics.CodeAnalysis; "SA1300", Justification = "Reviewed.", Scope = "namespaceanddescendants", - Target = "TestApp.AspNetCore._6._0")] + Target = "TestApp.AspNetCore._7._0")] diff --git a/test/TestApp.AspNetCore.7.0/Controllers/ForwardController.cs b/test/TestApp.AspNetCore.7.0/Controllers/ForwardController.cs index ebdc5f062..378224116 100644 --- a/test/TestApp.AspNetCore.7.0/Controllers/ForwardController.cs +++ b/test/TestApp.AspNetCore.7.0/Controllers/ForwardController.cs @@ -16,9 +16,10 @@ using System.Net.Http; using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; #pragma warning disable SA1300 // Element should begin with upper-case letter namespace TestApp.AspNetCore._7._0.Controllers @@ -47,7 +48,7 @@ namespace TestApp.AspNetCore._7._0.Controllers var request = new HttpRequestMessage(HttpMethod.Post, argument.Url) { Content = new StringContent( - JsonConvert.SerializeObject(argument.Arguments), + JsonSerializer.Serialize(argument.Arguments), Encoding.UTF8, "application/json"), }; @@ -64,10 +65,10 @@ namespace TestApp.AspNetCore._7._0.Controllers public class Data { - [JsonProperty("url")] + [JsonPropertyName("url")] public string Url { get; set; } - [JsonProperty("arguments")] + [JsonPropertyName("arguments")] public Data[] Arguments { get; set; } } } diff --git a/test/TestApp.AspNetCore.7.0/Startup.cs b/test/TestApp.AspNetCore.7.0/Startup.cs index 47bb7ac8d..d9bbe701b 100644 --- a/test/TestApp.AspNetCore.7.0/Startup.cs +++ b/test/TestApp.AspNetCore.7.0/Startup.cs @@ -41,6 +41,8 @@ namespace TestApp.AspNetCore._7._0 services.AddSingleton(); services.AddSingleton( new CallbackMiddleware.CallbackMiddlewareImpl()); + services.AddSingleton( + new ActivityMiddleware.ActivityMiddlewareImpl()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -52,6 +54,7 @@ namespace TestApp.AspNetCore._7._0 } app.UseMiddleware(); + app.UseMiddleware(); app.UseRouting(); app.UseAuthorization();