From 28f59eb4c20f305c80be667d09bfde44e7933764 Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Mon, 26 Jul 2021 15:34:43 -0700 Subject: [PATCH 01/45] Add InMemoryExporter for Metrics (#2192) --- .../CHANGELOG.md | 15 ++++-- .../InMemoryExporterMetricHelperExtensions.cs | 50 +++++++++++++++++++ .../InMemoryExporterOptions.cs | 32 ++++++++++++ 3 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs create mode 100644 src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs diff --git a/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md b/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md index 7f8b02afcfa..e9367ea4bd7 100644 --- a/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md @@ -2,18 +2,23 @@ ## Unreleased +* Add Metrics + support.([#2192](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2192)) + ## 1.2.0-alpha1 Released 2021-Jul-23 -* Removes support for .NET Framework 4.5.2 and 4.6. The minimum .NET Framework version - supported is .NET 4.6.1. ([#2138](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2138)) +* Removes support for .NET Framework 4.5.2 and 4.6. The minimum .NET Framework + version supported is .NET 4.6.1. + ([#2138](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2138)) ## 1.1.0 Released 2021-Jul-12 -* Supports OpenTelemetry.Extensions.Hosting based configuration for `InMemoryExporter` +* Supports OpenTelemetry.Extensions.Hosting based configuration for + `InMemoryExporter` ([#2129](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2129)) ## 1.1.0-rc1 @@ -52,8 +57,8 @@ Released 2021-Feb-04 Released 2021-Jan-29 -* `AddInMemoryExporter` extension method for traces moved from - `OpenTelemetry` namespace to `OpenTelemetry.Trace` namespace. +* `AddInMemoryExporter` extension method for traces moved from `OpenTelemetry` + namespace to `OpenTelemetry.Trace` namespace. ([#1576](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1576)) * `AddInMemoryExporter` extension method for logs moved from `Microsoft.Extensions.Logging` namespace to `OpenTelemetry.Logs` namespace. diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs new file mode 100644 index 00000000000..9fbcda9eba4 --- /dev/null +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs @@ -0,0 +1,50 @@ +// +// 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.Exporter; + +namespace OpenTelemetry.Metrics +{ + public static class InMemoryExporterMetricHelperExtensions + { + /// + /// Adds InMemory exporter to the TracerProvider. + /// + /// builder to use. + /// Collection which will be populated with the exported MetricItem. + /// Exporter configuration options. + /// The instance of to chain the calls. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")] + public static MeterProviderBuilder AddInMemoryExporter(this MeterProviderBuilder builder, ICollection exportedItems, Action configure = null) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (exportedItems == null) + { + throw new ArgumentNullException(nameof(exportedItems)); + } + + var options = new InMemoryExporterOptions(); + configure?.Invoke(options); + return builder.AddMetricProcessor(new PushMetricProcessor(new InMemoryExporter(exportedItems), options.MetricExportInterval, options.IsDelta)); + } + } +} diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs new file mode 100644 index 00000000000..17df3ec92ee --- /dev/null +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs @@ -0,0 +1,32 @@ +// +// 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.Exporter +{ + public class InMemoryExporterOptions + { + /// + /// Gets or sets the metric export interval. + /// + public int MetricExportInterval { get; set; } = 1000; + + /// + /// Gets or sets a value indicating whether to export Delta + /// values or not (Cumulative). + /// + public bool IsDelta { get; set; } = true; + } +} From d99db276cd6776a5031f9a40096eff4ddaff1b60 Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Mon, 26 Jul 2021 15:56:17 -0700 Subject: [PATCH 02/45] Rename MetricExportInterval to MtricExportIntervalMilliSeconds (#2193) --- examples/Console/TestMetrics.cs | 4 ++-- .../ConsoleExporterMetricHelperExtensions.cs | 2 +- src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs | 4 ++-- .../InMemoryExporterMetricHelperExtensions.cs | 2 +- .../InMemoryExporterOptions.cs | 4 ++-- .../OtlpExporterOptions.cs | 4 ++-- .../OtlpMetricExporterHelperExtensions.cs | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/Console/TestMetrics.cs b/examples/Console/TestMetrics.cs index e4ece9e12ed..2d700812989 100644 --- a/examples/Console/TestMetrics.cs +++ b/examples/Console/TestMetrics.cs @@ -66,7 +66,7 @@ internal static object Run(MetricsOptions options) providerBuilder .AddOtlpExporter(o => { - o.MetricExportInterval = options.DefaultCollectionPeriodMilliseconds; + o.MetricExportIntervalMilliSeconds = options.DefaultCollectionPeriodMilliseconds; o.IsDelta = options.IsDelta; }); } @@ -75,7 +75,7 @@ internal static object Run(MetricsOptions options) providerBuilder .AddConsoleExporter(o => { - o.MetricExportInterval = options.DefaultCollectionPeriodMilliseconds; + o.MetricExportIntervalMilliSeconds = options.DefaultCollectionPeriodMilliseconds; o.IsDelta = options.IsDelta; }); } diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricHelperExtensions.cs b/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricHelperExtensions.cs index 2e8c4d56d07..b7b993c35ae 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricHelperExtensions.cs @@ -37,7 +37,7 @@ public static MeterProviderBuilder AddConsoleExporter(this MeterProviderBuilder var options = new ConsoleExporterOptions(); configure?.Invoke(options); - return builder.AddMetricProcessor(new PushMetricProcessor(new ConsoleMetricExporter(options), options.MetricExportInterval, options.IsDelta)); + return builder.AddMetricProcessor(new PushMetricProcessor(new ConsoleMetricExporter(options), options.MetricExportIntervalMilliSeconds, options.IsDelta)); } } } diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs b/src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs index 917b0fe4124..4e787a2f096 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs @@ -24,9 +24,9 @@ public class ConsoleExporterOptions public ConsoleExporterOutputTargets Targets { get; set; } = ConsoleExporterOutputTargets.Console; /// - /// Gets or sets the metric export interval. + /// Gets or sets the metric export interval in milliseconds. The default value is 1000 milliseconds. /// - public int MetricExportInterval { get; set; } = 1000; + public int MetricExportIntervalMilliSeconds { get; set; } = 1000; /// /// Gets or sets a value indicating whether to export Delta diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs index 9fbcda9eba4..efebbb40d61 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs @@ -44,7 +44,7 @@ public static MeterProviderBuilder AddInMemoryExporter(this MeterProviderBuilder var options = new InMemoryExporterOptions(); configure?.Invoke(options); - return builder.AddMetricProcessor(new PushMetricProcessor(new InMemoryExporter(exportedItems), options.MetricExportInterval, options.IsDelta)); + return builder.AddMetricProcessor(new PushMetricProcessor(new InMemoryExporter(exportedItems), options.MetricExportIntervalMilliSeconds, options.IsDelta)); } } } diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs index 17df3ec92ee..e427dd0110d 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs @@ -19,9 +19,9 @@ namespace OpenTelemetry.Exporter public class InMemoryExporterOptions { /// - /// Gets or sets the metric export interval. + /// Gets or sets the metric export interval in milliseconds. The default value is 1000 milliseconds. /// - public int MetricExportInterval { get; set; } = 1000; + public int MetricExportIntervalMilliSeconds { get; set; } = 1000; /// /// Gets or sets a value indicating whether to export Delta diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs index 438400ac3ab..507326cff46 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs @@ -54,9 +54,9 @@ public class OtlpExporterOptions public BatchExportProcessorOptions BatchExportProcessorOptions { get; set; } = new BatchExportProcessorOptions(); /// - /// Gets or sets the metric export interval. + /// Gets or sets the metric export interval in milliseconds. The default value is 1000 milliseconds. /// - public int MetricExportInterval { get; set; } = 1000; + public int MetricExportIntervalMilliSeconds { get; set; } = 1000; /// /// Gets or sets a value indicating whether to export Delta diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterHelperExtensions.cs index b87d8ae5aca..f22d024e0e3 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterHelperExtensions.cs @@ -39,7 +39,7 @@ public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder bui var options = new OtlpExporterOptions(); configure?.Invoke(options); - return builder.AddMetricProcessor(new PushMetricProcessor(new OtlpMetricsExporter(options), options.MetricExportInterval, options.IsDelta)); + return builder.AddMetricProcessor(new PushMetricProcessor(new OtlpMetricsExporter(options), options.MetricExportIntervalMilliSeconds, options.IsDelta)); } } } From 4df429898e0185547131f3276e75f8169d782c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Tue, 27 Jul 2021 01:30:23 +0200 Subject: [PATCH 03/45] Update supported .NET Versions docs (#2190) Co-authored-by: Cijo Thomas --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ea1a7297e88..57de8832bcd 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Packages shipped from this repository generally support all the officially supported versions of [.NET Core](https://dotnet.microsoft.com/download/dotnet-core), and [.NET Framework](https://dotnet.microsoft.com/download/dotnet-framework) except for -`.NET Framework 3.5 SP1`. Any exceptions to this are noted in the individual -`README.md` files. +versions lower than `.NET Framework 4.6.1`. +Any exceptions to this are noted in the individual `README.md` files. ## Getting Started From 58dc7eae5e8e1a65d1c10902f1c2a688d9389794 Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Mon, 26 Jul 2021 17:28:27 -0700 Subject: [PATCH 04/45] Rename MetricExportIntervalMilliSeconds to MetricExportIntervalMilliseconds (#2194) Co-authored-by: Cijo Thomas --- examples/Console/TestMetrics.cs | 4 ++-- .../ConsoleExporterMetricHelperExtensions.cs | 2 +- src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs | 2 +- .../InMemoryExporterMetricHelperExtensions.cs | 2 +- .../InMemoryExporterOptions.cs | 2 +- .../OtlpExporterOptions.cs | 2 +- .../OtlpMetricExporterHelperExtensions.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/Console/TestMetrics.cs b/examples/Console/TestMetrics.cs index 2d700812989..ba381838b64 100644 --- a/examples/Console/TestMetrics.cs +++ b/examples/Console/TestMetrics.cs @@ -66,7 +66,7 @@ internal static object Run(MetricsOptions options) providerBuilder .AddOtlpExporter(o => { - o.MetricExportIntervalMilliSeconds = options.DefaultCollectionPeriodMilliseconds; + o.MetricExportIntervalMilliseconds = options.DefaultCollectionPeriodMilliseconds; o.IsDelta = options.IsDelta; }); } @@ -75,7 +75,7 @@ internal static object Run(MetricsOptions options) providerBuilder .AddConsoleExporter(o => { - o.MetricExportIntervalMilliSeconds = options.DefaultCollectionPeriodMilliseconds; + o.MetricExportIntervalMilliseconds = options.DefaultCollectionPeriodMilliseconds; o.IsDelta = options.IsDelta; }); } diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricHelperExtensions.cs b/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricHelperExtensions.cs index b7b993c35ae..211add536cc 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleExporterMetricHelperExtensions.cs @@ -37,7 +37,7 @@ public static MeterProviderBuilder AddConsoleExporter(this MeterProviderBuilder var options = new ConsoleExporterOptions(); configure?.Invoke(options); - return builder.AddMetricProcessor(new PushMetricProcessor(new ConsoleMetricExporter(options), options.MetricExportIntervalMilliSeconds, options.IsDelta)); + return builder.AddMetricProcessor(new PushMetricProcessor(new ConsoleMetricExporter(options), options.MetricExportIntervalMilliseconds, options.IsDelta)); } } } diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs b/src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs index 4e787a2f096..777a11dd327 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs @@ -26,7 +26,7 @@ public class ConsoleExporterOptions /// /// Gets or sets the metric export interval in milliseconds. The default value is 1000 milliseconds. /// - public int MetricExportIntervalMilliSeconds { get; set; } = 1000; + public int MetricExportIntervalMilliseconds { get; set; } = 1000; /// /// Gets or sets a value indicating whether to export Delta diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs index efebbb40d61..8240113e77f 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricHelperExtensions.cs @@ -44,7 +44,7 @@ public static MeterProviderBuilder AddInMemoryExporter(this MeterProviderBuilder var options = new InMemoryExporterOptions(); configure?.Invoke(options); - return builder.AddMetricProcessor(new PushMetricProcessor(new InMemoryExporter(exportedItems), options.MetricExportIntervalMilliSeconds, options.IsDelta)); + return builder.AddMetricProcessor(new PushMetricProcessor(new InMemoryExporter(exportedItems), options.MetricExportIntervalMilliseconds, options.IsDelta)); } } } diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs index e427dd0110d..767741cb527 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterOptions.cs @@ -21,7 +21,7 @@ public class InMemoryExporterOptions /// /// Gets or sets the metric export interval in milliseconds. The default value is 1000 milliseconds. /// - public int MetricExportIntervalMilliSeconds { get; set; } = 1000; + public int MetricExportIntervalMilliseconds { get; set; } = 1000; /// /// Gets or sets a value indicating whether to export Delta diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs index 507326cff46..7102bab456c 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs @@ -56,7 +56,7 @@ public class OtlpExporterOptions /// /// Gets or sets the metric export interval in milliseconds. The default value is 1000 milliseconds. /// - public int MetricExportIntervalMilliSeconds { get; set; } = 1000; + public int MetricExportIntervalMilliseconds { get; set; } = 1000; /// /// Gets or sets a value indicating whether to export Delta diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterHelperExtensions.cs index f22d024e0e3..de2bac2424c 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricExporterHelperExtensions.cs @@ -39,7 +39,7 @@ public static MeterProviderBuilder AddOtlpExporter(this MeterProviderBuilder bui var options = new OtlpExporterOptions(); configure?.Invoke(options); - return builder.AddMetricProcessor(new PushMetricProcessor(new OtlpMetricsExporter(options), options.MetricExportIntervalMilliSeconds, options.IsDelta)); + return builder.AddMetricProcessor(new PushMetricProcessor(new OtlpMetricsExporter(options), options.MetricExportIntervalMilliseconds, options.IsDelta)); } } } From 89a0bce3bfb2aa46743ef71a7bbc2257f6db700f Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 26 Jul 2021 20:40:06 -0700 Subject: [PATCH 05/45] Do not call export when no metrics (#2191) Co-authored-by: Cijo Thomas --- src/OpenTelemetry/Metrics/Processors/PullMetricProcessor.cs | 2 +- src/OpenTelemetry/Metrics/Processors/PushMetricProcessor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry/Metrics/Processors/PullMetricProcessor.cs b/src/OpenTelemetry/Metrics/Processors/PullMetricProcessor.cs index fc970cfadc3..e9ce9412b98 100644 --- a/src/OpenTelemetry/Metrics/Processors/PullMetricProcessor.cs +++ b/src/OpenTelemetry/Metrics/Processors/PullMetricProcessor.cs @@ -42,7 +42,7 @@ public void PullRequest() if (this.getMetrics != null) { var metricsToExport = this.getMetrics(this.isDelta); - if (metricsToExport != null) + if (metricsToExport != null && metricsToExport.Metrics.Count > 0) { Batch batch = new Batch(metricsToExport); this.exporter.Export(batch); diff --git a/src/OpenTelemetry/Metrics/Processors/PushMetricProcessor.cs b/src/OpenTelemetry/Metrics/Processors/PushMetricProcessor.cs index a6201c8688b..33bc8c5959f 100644 --- a/src/OpenTelemetry/Metrics/Processors/PushMetricProcessor.cs +++ b/src/OpenTelemetry/Metrics/Processors/PushMetricProcessor.cs @@ -77,7 +77,7 @@ private void Export(bool isDelta) if (this.getMetrics != null) { var metricsToExport = this.getMetrics(isDelta); - if (metricsToExport != null) + if (metricsToExport != null && metricsToExport.Metrics.Count > 0) { Batch batch = new Batch(metricsToExport); this.exporter.Export(batch); From 334689e885d8a419540c37cb24491823e69b5f83 Mon Sep 17 00:00:00 2001 From: Dawid Szmigielski Date: Tue, 27 Jul 2021 06:10:35 +0200 Subject: [PATCH 06/45] Add support for OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS and OTEL_EXPORTER_OTLP_TIMEOUT env vars (#2188) --- .../CHANGELOG.md | 6 + ...penTelemetryProtocolExporterEventSource.cs | 22 ++++ .../OtlpExporterOptions.cs | 53 +++++++++ .../OtlpExporterOptionsTests.cs | 112 ++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index d42846d6996..c464714f603 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +* The `OtlpExporterOptions` defaults can be overridden using + `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS` and `OTEL_EXPORTER_OTLP_TIMEOUT` + envionmental variables as defined in the + [specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md). + ([#2188](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2188)) + ## 1.2.0-alpha1 Released 2021-Jul-23 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs index 27d4ec3de9e..1824c82fb69 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OpenTelemetryProtocolExporterEventSource.cs @@ -16,6 +16,7 @@ using System; using System.Diagnostics.Tracing; +using System.Security; using OpenTelemetry.Internal; namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation @@ -25,6 +26,15 @@ internal class OpenTelemetryProtocolExporterEventSource : EventSource { public static readonly OpenTelemetryProtocolExporterEventSource Log = new OpenTelemetryProtocolExporterEventSource(); + [NonEvent] + public void MissingPermissionsToReadEnvironmentVariable(SecurityException ex) + { + if (this.IsEnabled(EventLevel.Warning, EventKeywords.All)) + { + this.MissingPermissionsToReadEnvironmentVariable(ex.ToInvariantString()); + } + } + [NonEvent] public void FailedToConvertToProtoDefinitionError(Exception ex) { @@ -81,5 +91,17 @@ public void CouldNotTranslateMetric(string className, string methodName) { this.WriteEvent(5, className, methodName); } + + [Event(6, Message = "Failed to parse environment variable: '{0}', value: '{1}'.", Level = EventLevel.Warning)] + public void FailedToParseEnvironmentVariable(string name, string value) + { + this.WriteEvent(6, name, value); + } + + [Event(7, Message = "Missing permissions to read environment variable: '{0}'", Level = EventLevel.Warning)] + public void MissingPermissionsToReadEnvironmentVariable(string exception) + { + this.WriteEvent(7, exception); + } } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs index 7102bab456c..f112e76a773 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs @@ -16,6 +16,8 @@ using System; using System.Diagnostics; +using System.Security; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; namespace OpenTelemetry.Exporter { @@ -24,6 +26,57 @@ namespace OpenTelemetry.Exporter /// public class OtlpExporterOptions { + internal const string EndpointEnvVarName = "OTEL_EXPORTER_OTLP_ENDPOINT"; + internal const string HeadersEnvVarName = "OTEL_EXPORTER_OTLP_HEADERS"; + internal const string TimeoutEnvVarName = "OTEL_EXPORTER_OTLP_TIMEOUT"; + + /// + /// Initializes a new instance of the class. + /// + public OtlpExporterOptions() + { + try + { + string endpointEnvVar = Environment.GetEnvironmentVariable(EndpointEnvVarName); + if (!string.IsNullOrEmpty(endpointEnvVar)) + { + if (Uri.TryCreate(endpointEnvVar, UriKind.Absolute, out var endpoint)) + { + this.Endpoint = endpoint; + } + else + { + OpenTelemetryProtocolExporterEventSource.Log.FailedToParseEnvironmentVariable(EndpointEnvVarName, endpointEnvVar); + } + } + + string headersEnvVar = Environment.GetEnvironmentVariable(HeadersEnvVarName); + if (!string.IsNullOrEmpty(headersEnvVar)) + { + this.Headers = headersEnvVar; + } + + string timeoutEnvVar = Environment.GetEnvironmentVariable(TimeoutEnvVarName); + if (!string.IsNullOrEmpty(timeoutEnvVar)) + { + if (int.TryParse(timeoutEnvVar, out var timeout)) + { + this.TimeoutMilliseconds = timeout; + } + else + { + OpenTelemetryProtocolExporterEventSource.Log.FailedToParseEnvironmentVariable(TimeoutEnvVarName, timeoutEnvVar); + } + } + } + catch (SecurityException ex) + { + // The caller does not have the required permission to + // retrieve the value of an environment variable from the current process. + OpenTelemetryProtocolExporterEventSource.Log.MissingPermissionsToReadEnvironmentVariable(ex); + } + } + /// /// Gets or sets the target to which the exporter is going to send traces. /// Must be a valid Uri with scheme (http) and host, and diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs new file mode 100644 index 00000000000..d7a698a0d6c --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.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. +// + +using System; +using Xunit; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests +{ + public class OtlpExporterOptionsTests : IDisposable + { + public OtlpExporterOptionsTests() + { + ClearEnvVars(); + } + + public void Dispose() + { + ClearEnvVars(); + } + + [Fact] + public void OtlpExporterOptions_Defaults() + { + var options = new OtlpExporterOptions(); + + Assert.Equal(new Uri("http://localhost:4317"), options.Endpoint); + Assert.Null(options.Headers); + Assert.Equal(10000, options.TimeoutMilliseconds); + } + + [Fact] + public void OtlpExporterOptions_EnvironmentVariableOverride() + { + Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, "http://test:8888"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, "A=2,B=3"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, "2000"); + + var options = new OtlpExporterOptions(); + + Assert.Equal(new Uri("http://test:8888"), options.Endpoint); + Assert.Equal("A=2,B=3", options.Headers); + Assert.Equal(2000, options.TimeoutMilliseconds); + } + + [Fact] + public void OtlpExporterOptions_InvalidEndpointVariableOverride() + { + Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, "invalid"); + + var options = new OtlpExporterOptions(); + + Assert.Equal(new Uri("http://localhost:4317"), options.Endpoint); // use default + } + + [Fact] + public void OtlpExporterOptions_InvalidTimeoutVariableOverride() + { + Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, "invalid"); + + var options = new OtlpExporterOptions(); + + Assert.Equal(10000, options.TimeoutMilliseconds); // use default + } + + [Fact] + public void OtlpExporterOptions_SetterOverridesEnvironmentVariable() + { + Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, "http://test:8888"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, "A=2,B=3"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, "2000"); + + var options = new OtlpExporterOptions + { + Endpoint = new Uri("http://localhost:200"), + Headers = "C=3", + TimeoutMilliseconds = 40000, + }; + + Assert.Equal(new Uri("http://localhost:200"), options.Endpoint); + Assert.Equal("C=3", options.Headers); + Assert.Equal(40000, options.TimeoutMilliseconds); + } + + [Fact] + public void OtlpExporterOptions_EnvironmentVariableNames() + { + Assert.Equal("OTEL_EXPORTER_OTLP_ENDPOINT", OtlpExporterOptions.EndpointEnvVarName); + Assert.Equal("OTEL_EXPORTER_OTLP_HEADERS", OtlpExporterOptions.HeadersEnvVarName); + Assert.Equal("OTEL_EXPORTER_OTLP_TIMEOUT", OtlpExporterOptions.TimeoutEnvVarName); + } + + private static void ClearEnvVars() + { + Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, null); + Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, null); + Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, null); + } + } +} From 4df5121e810127ac330c0a1eb71ffd94d70ec4d6 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Tue, 27 Jul 2021 09:03:33 -0700 Subject: [PATCH 07/45] Remove upper constraint for Ms.Extensions. dependencies (#2179) keep minimum as 2.1 to suport asp.net core 2.1 on full framework scenario --- build/Common.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build/Common.props b/build/Common.props index b974dea70cd..600f86dcbf6 100644 --- a/build/Common.props +++ b/build/Common.props @@ -31,10 +31,10 @@ [1.0.7,2.0) [3.3.1] [16.10.0] - [2.1.0,6.0) - [2.1.0,6.0) - [2.1.0,6.0) - [2.1.0,6.0) + [2.1.0,) + [2.1.0,) + [2.1.0,) + [2.1.0,) [1.0.0,2.0) [1.0.0,2.0) [0.12.1,0.13) From 56d945e1b5ccb5672f32b0336e34e201e330ddc1 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Tue, 27 Jul 2021 11:59:10 -0700 Subject: [PATCH 08/45] Fix changelog (#2197) --- src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md | 3 +++ .../CHANGELOG.md | 3 +++ src/OpenTelemetry/CHANGELOG.md | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md index 92ce5acad97..e3801c6ffbb 100644 --- a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Removes upper constraint for Microsoft.Extensions.Hosting.Abstractions + dependency. ([#2179](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2179)) + ## 1.0.0-rc7 Released 2021-Jul-12 diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md index f4be314874b..bbced768f45 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Removes upper constraint for Microsoft.Extensions.Options + dependency. ([#2179](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2179)) + ## 1.0.0-rc7 Released 2021-Jul-12 diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 237bfaa952b..988d8b0216e 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Removes upper constraint for Microsoft.Extensions.Logging + dependencies. ([#2179](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2179)) + ## 1.2.0-alpha1 Released 2021-Jul-23 From 4ff9a5099f3670a87c666e312367b6e4c5a77067 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Tue, 27 Jul 2021 12:47:20 -0700 Subject: [PATCH 09/45] Add basic test of ASP.NET Core request metric (#2186) * Add basic test of ASP.NET Core request metric * Remove netcoreapp2.1 * Clarify comments Co-authored-by: Cijo Thomas --- .../MetricTests.cs | 129 ++++++++++++++++++ ...ry.Instrumentation.AspNetCore.Tests.csproj | 1 + 2 files changed, 130 insertions(+) create mode 100644 test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs new file mode 100644 index 00000000000..8dab7611f68 --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs @@ -0,0 +1,129 @@ +// +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Testing; +using OpenTelemetry.Metrics; +using OpenTelemetry.Tests; +using OpenTelemetry.Trace; +#if NETCOREAPP3_1 +using TestApp.AspNetCore._3._1; +#else +using TestApp.AspNetCore._5._0; +#endif +using Xunit; + +namespace OpenTelemetry.Instrumentation.AspNetCore.Tests +{ + public class MetricTests + : IClassFixture>, IDisposable + { + private readonly WebApplicationFactory factory; + private MeterProvider meterProvider = null; + + public MetricTests(WebApplicationFactory factory) + { + this.factory = factory; + } + + [Fact] + public void AddAspNetCoreInstrumentation_BadArgs() + { + MeterProviderBuilder builder = null; + Assert.Throws(() => builder.AddAspNetCoreInstrumentation()); + } + + [Fact] + public async Task RequestMetricIsCaptured() + { + var metricItems = new List(); + var metricExporter = new TestExporter(ProcessExport); + + void ProcessExport(Batch batch) + { + foreach (var metricItem in batch) + { + metricItems.Add(metricItem); + } + } + + this.meterProvider = Sdk.CreateMeterProviderBuilder() + .AddAspNetCoreInstrumentation() + .AddMetricProcessor(new PushMetricProcessor(exporter: metricExporter, exportIntervalMs: 10, isDelta: true)) + .Build(); + + using (var client = this.factory.CreateClient()) + { + var response = await client.GetAsync("/api/values"); + response.EnsureSuccessStatusCode(); + } + + // Wait for at least two exporter invocations + WaitForMetricItems(metricItems, 2); + + this.meterProvider.Dispose(); + + // There should be more than one result here since we waited for at least two exporter invocations. + // The exporter continues to receive a metric even if it has not changed since the last export. + var requestMetrics = metricItems + .SelectMany(item => item.Metrics.Where(metric => metric.Name == "http.server.request_count")) + .ToArray(); + + Assert.True(requestMetrics.Length > 1); + + var metric = requestMetrics[0] as ISumMetric; + Assert.NotNull(metric); + Assert.Equal(1L, metric.Sum.Value); + + 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"); + Assert.Contains(method, metric.Attributes); + Assert.Contains(scheme, metric.Attributes); + Assert.Contains(statusCode, metric.Attributes); + Assert.Contains(flavor, metric.Attributes); + Assert.Equal(4, metric.Attributes.Length); + + for (var i = 1; i < requestMetrics.Length; ++i) + { + metric = requestMetrics[i] as ISumMetric; + Assert.NotNull(metric); + Assert.Equal(0L, metric.Sum.Value); + } + } + + public void Dispose() + { + this.meterProvider?.Dispose(); + } + + private static void WaitForMetricItems(List metricItems, int count) + { + Assert.True(SpinWait.SpinUntil( + () => + { + Thread.Sleep(10); + return metricItems.Count >= count; + }, + TimeSpan.FromSeconds(1))); + } + } +} diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/OpenTelemetry.Instrumentation.AspNetCore.Tests.csproj b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/OpenTelemetry.Instrumentation.AspNetCore.Tests.csproj index 4b953483608..a035f5da834 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/OpenTelemetry.Instrumentation.AspNetCore.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/OpenTelemetry.Instrumentation.AspNetCore.Tests.csproj @@ -19,6 +19,7 @@ + From a12fddd54bab11bdf6e1e04e6f1a92adfe85d656 Mon Sep 17 00:00:00 2001 From: Dawid Szmigielski Date: Wed, 28 Jul 2021 18:35:05 +0200 Subject: [PATCH 10/45] Otlp exporter documentation update (#2199) --- .../README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md index 2f4d0639e4d..5f39d810b80 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md @@ -18,7 +18,11 @@ dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol ## Configuration -You can configure the `OtlpExporter` through `OtlpExporterOptions` properties: +You can configure the `OtlpExporter` through `OtlpExporterOptions` +properties and environment variables. The `OtlpExporterOptions` +setters take precedence over the environment variables. + +## Options Properties * `Endpoint`: Target to which the exporter is going to send traces or metrics. The endpoint must be a valid Uri with scheme (http or https) and host, and MAY @@ -35,6 +39,18 @@ You can configure the `OtlpExporter` through `OtlpExporterOptions` properties: See the [`TestOtlpExporter.cs`](../../examples/Console/TestOtlpExporter.cs) for an example of how to use the exporter. +## Environment Variables + +The following environment variables can be used to override the default +values of the `OtlpExporterOptions` +(following the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md)). + +| Environment variable | `OtlpExporterOptions` property | +| ------------------------------| -------------------------------| +| `OTEL_EXPORTER_OTLP_ENDPOINT` | `Endpoint` | +| `OTEL_EXPORTER_OTLP_HEADERS` | `Headers` | +| `OTEL_EXPORTER_OTLP_TIMEOUT` | `TimeoutMilliseconds` | + ## Special case when using insecure channel If your application is [.NET Standard From f03cdddbe8c09bc0e248701f3040f0d8762c2803 Mon Sep 17 00:00:00 2001 From: Vishwesh Bankwar Date: Wed, 28 Jul 2021 10:01:24 -0700 Subject: [PATCH 11/45] Enable zipkin endpoint configuration via environment variable (#1924) --- .../CHANGELOG.md | 4 ++ .../ZipkinExporterEventSource.cs | 15 ++++++ src/OpenTelemetry.Exporter.Zipkin/README.md | 18 +++++-- .../ZipkinExporterOptions.cs | 22 +++++++- .../ZipkinExporterTests.cs | 54 +++++++++++++++++++ 5 files changed, 109 insertions(+), 4 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md index 6b692170103..1e1dac0d3ff 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +* Enabling endpoint configuration in ZipkinExporterOptions via + `OTEL_EXPORTER_ZIPKIN_ENDPOINT` environment variable. + ([#1453](https://github.com/open-telemetry/opentelemetry-dotnet/issues/1453)) + ## 1.2.0-alpha1 Released 2021-Jul-23 diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinExporterEventSource.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinExporterEventSource.cs index f2d1bd94f40..eb8a6af0b45 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinExporterEventSource.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinExporterEventSource.cs @@ -37,10 +37,25 @@ public void FailedExport(Exception ex) } } + [NonEvent] + public void FailedEndpointInitialization(Exception ex) + { + if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + { + this.FailedEndpointInitialization(ex.ToInvariantString()); + } + } + [Event(1, Message = "Failed to export activities: '{0}'", Level = EventLevel.Error)] public void FailedExport(string exception) { this.WriteEvent(1, exception); } + + [Event(2, Message = "Error initializing Zipkin endpoint, falling back to default value: '{0}'", Level = EventLevel.Error)] + public void FailedEndpointInitialization(string exception) + { + this.WriteEvent(2, exception); + } } } diff --git a/src/OpenTelemetry.Exporter.Zipkin/README.md b/src/OpenTelemetry.Exporter.Zipkin/README.md index b73fa16c26d..779d464be55 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/README.md +++ b/src/OpenTelemetry.Exporter.Zipkin/README.md @@ -20,8 +20,11 @@ method on `TracerProviderBuilder`. ## Configuration -You can configure the `ZipkinExporter` through -`ZipkinExporterOptions` properties: +You can configure the `ZipkinExporter` through `ZipkinExporterOptions` +and environment variables. The `ZipkinExporterOptions` setters +take precedence over the environment variables. + +### Configuration using Properties * `ServiceName`: Name of the service reporting telemetry. If the `Resource` associated with the telemetry has "service.name" defined, then it'll be @@ -41,7 +44,7 @@ See [`TestZipkinExporter.cs`](../../examples/Console/TestZipkinExporter.cs) for example use. -## Configuration using Dependency Injection +### Configuration using Dependency Injection This exporter allows easy configuration of `ZipkinExporterOptions` from dependency injection container, when used in conjunction with @@ -50,6 +53,15 @@ dependency injection container, when used in conjunction with See the [Startup](../../examples/AspNetCore/Startup.cs) class of the ASP.NET Core application for example use. +### Configuration using Environment Variables + +The following environment variables can be used to override the default +values of the `ZipkinExporterOptions`. + +| Environment variable | `ZipkinExporterOptions` property | +| --------------------------------| -------------------------------- | +| `OTEL_EXPORTER_ZIPKIN_ENDPOINT` | `Endpoint` | + ## References * [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs index ac1682017ae..21df9181fd7 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs @@ -16,6 +16,7 @@ using System; using System.Diagnostics; +using OpenTelemetry.Exporter.Zipkin.Implementation; namespace OpenTelemetry.Exporter { @@ -25,12 +26,31 @@ namespace OpenTelemetry.Exporter public sealed class ZipkinExporterOptions { internal const int DefaultMaxPayloadSizeInBytes = 4096; + internal const string ZipkinEndpointEnvVar = "OTEL_EXPORTER_ZIPKIN_ENDPOINT"; + internal const string DefaultZipkinEndpoint = "http://localhost:9411/api/v2/spans"; + + /// + /// Initializes a new instance of the class. + /// Initializes zipkin endpoint. + /// + public ZipkinExporterOptions() + { + try + { + this.Endpoint = new Uri(Environment.GetEnvironmentVariable(ZipkinEndpointEnvVar) ?? DefaultZipkinEndpoint); + } + catch (Exception ex) + { + this.Endpoint = new Uri(DefaultZipkinEndpoint); + ZipkinExporterEventSource.Log.FailedEndpointInitialization(ex); + } + } /// /// Gets or sets Zipkin endpoint address. See https://zipkin.io/zipkin-api/#/default/post_spans. /// Typically https://zipkin-server-name:9411/api/v2/spans. /// - public Uri Endpoint { get; set; } = new Uri("http://localhost:9411/api/v2/spans"); + public Uri Endpoint { get; set; } /// /// Gets or sets a value indicating whether short trace id should be used. diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs index 47829b712b1..8b4a6482b65 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs @@ -131,6 +131,60 @@ public void SuppresssesInstrumentation() Assert.Equal(1, endCalledCount); } + [Fact] + public void EndpointConfigurationUsingEnvironmentVariable() + { + try + { + Environment.SetEnvironmentVariable(ZipkinExporterOptions.ZipkinEndpointEnvVar, "http://urifromenvironmentvariable"); + + var exporterOptions = new ZipkinExporterOptions(); + + Assert.Equal(new Uri(Environment.GetEnvironmentVariable(ZipkinExporterOptions.ZipkinEndpointEnvVar)).AbsoluteUri, exporterOptions.Endpoint.AbsoluteUri); + } + finally + { + Environment.SetEnvironmentVariable(ZipkinExporterOptions.ZipkinEndpointEnvVar, null); + } + } + + [Fact] + public void IncodeEndpointConfigTakesPrecedenceOverEnvironmentVariable() + { + try + { + Environment.SetEnvironmentVariable(ZipkinExporterOptions.ZipkinEndpointEnvVar, "http://urifromenvironmentvariable"); + + var exporterOptions = new ZipkinExporterOptions + { + Endpoint = new Uri("http://urifromcode"), + }; + + Assert.Equal(new Uri("http://urifromcode").AbsoluteUri, exporterOptions.Endpoint.AbsoluteUri); + } + finally + { + Environment.SetEnvironmentVariable(ZipkinExporterOptions.ZipkinEndpointEnvVar, null); + } + } + + [Fact] + public void ErrorGettingUriFromEnvVarSetsDefaultEndpointValue() + { + try + { + Environment.SetEnvironmentVariable(ZipkinExporterOptions.ZipkinEndpointEnvVar, "InvalidUri"); + + var exporterOptions = new ZipkinExporterOptions(); + + Assert.Equal(new Uri(ZipkinExporterOptions.DefaultZipkinEndpoint).AbsoluteUri, exporterOptions.Endpoint.AbsoluteUri); + } + finally + { + Environment.SetEnvironmentVariable(ZipkinExporterOptions.ZipkinEndpointEnvVar, null); + } + } + [Theory] [InlineData(true, false, false)] [InlineData(false, false, false)] From 7db5ec8dcb2567c1bbadf71d5c81bf2ae6d29120 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Wed, 28 Jul 2021 10:52:20 -0700 Subject: [PATCH 12/45] Modify benchmarks to not use static tags (#2201) --- test/Benchmarks/Metrics/MetricsBenchmarks.cs | 83 ++++++++------------ 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/test/Benchmarks/Metrics/MetricsBenchmarks.cs b/test/Benchmarks/Metrics/MetricsBenchmarks.cs index 0e20f42b281..64ba7d5294d 100644 --- a/test/Benchmarks/Metrics/MetricsBenchmarks.cs +++ b/test/Benchmarks/Metrics/MetricsBenchmarks.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System; using System.Collections.Generic; using System.Diagnostics.Metrics; using BenchmarkDotNet.Attributes; @@ -22,40 +23,22 @@ /* BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19043 -Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores -.NET Core SDK=5.0.202 - [Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT - DefaultJob : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT - - -| Method | WithSDK | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -|-------------------------- |-------- |-----------:|-----------:|----------:|-------:|------:|------:|----------:| -| CounterHotPath | False | 15.126 ns | 0.3228 ns | 0.3965 ns | - | - | - | - | -| CounterWith1LabelsHotPath | False | 9.766 ns | 0.2268 ns | 0.3530 ns | - | - | - | - | -| CounterWith3LabelsHotPath | False | 25.240 ns | 0.2876 ns | 0.2690 ns | - | - | - | - | -| CounterWith5LabelsHotPath | False | 37.929 ns | 0.7512 ns | 0.5865 ns | 0.0249 | - | - | 104 B | -| CounterHotPath | True | 44.790 ns | 0.9101 ns | 1.3621 ns | - | - | - | - | -| CounterWith1LabelsHotPath | True | 115.023 ns | 2.1001 ns | 1.9644 ns | - | - | - | - | -| CounterWith3LabelsHotPath | True | 436.527 ns | 6.5121 ns | 5.7728 ns | - | - | - | - | -| CounterWith5LabelsHotPath | True | 586.498 ns | 11.4783 ns | 9.5849 ns | 0.0553 | - | - | 232 B | - - -BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19043 -Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores - [Host] : .NET Framework 4.8 (4.8.4360.0), X64 RyuJIT - DefaultJob : .NET Framework 4.8 (4.8.4360.0), X64 RyuJIT - - -| Method | WithSDK | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -|-------------------------- |-------- |------------:|----------:|----------:|-------:|------:|------:|----------:| -| CounterHotPath | False | 23.53 ns | 0.480 ns | 0.401 ns | - | - | - | - | -| CounterWith1LabelsHotPath | False | 28.70 ns | 0.592 ns | 0.770 ns | - | - | - | - | -| CounterWith3LabelsHotPath | False | 46.27 ns | 0.942 ns | 1.157 ns | - | - | - | - | -| CounterWith5LabelsHotPath | False | 51.66 ns | 1.060 ns | 1.857 ns | 0.0249 | - | - | 104 B | -| CounterHotPath | True | 70.44 ns | 1.029 ns | 0.912 ns | - | - | - | - | -| CounterWith1LabelsHotPath | True | 151.92 ns | 3.067 ns | 3.651 ns | - | - | - | - | -| CounterWith3LabelsHotPath | True | 876.20 ns | 15.920 ns | 14.892 ns | - | - | - | - | -| CounterWith5LabelsHotPath | True | 1,973.64 ns | 38.393 ns | 45.705 ns | 0.0534 | - | - | 233 B | +Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +.NET Core SDK=5.0.302 + [Host] : .NET Core 3.1.17 (CoreCLR 4.700.21.31506, CoreFX 4.700.21.31502), X64 RyuJIT + DefaultJob : .NET Core 3.1.17 (CoreCLR 4.700.21.31506, CoreFX 4.700.21.31502), X64 RyuJIT + + +| Method | WithSDK | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Allocated | +|-------------------------- |-------- |----------:|----------:|-----------:|----------:|-------:|------:|------:|----------:| +| CounterHotPath | False | 18.48 ns | 0.366 ns | 0.570 ns | 18.52 ns | - | - | - | - | +| CounterWith1LabelsHotPath | False | 30.25 ns | 1.274 ns | 3.530 ns | 29.14 ns | - | - | - | - | +| CounterWith3LabelsHotPath | False | 82.93 ns | 2.586 ns | 7.124 ns | 81.79 ns | - | - | - | - | +| CounterWith5LabelsHotPath | False | 134.94 ns | 4.756 ns | 13.491 ns | 132.45 ns | 0.0248 | - | - | 104 B | +| CounterHotPath | True | 68.58 ns | 1.417 ns | 3.228 ns | 68.40 ns | - | - | - | - | +| CounterWith1LabelsHotPath | True | 192.19 ns | 8.114 ns | 23.151 ns | 184.06 ns | - | - | - | - | +| CounterWith3LabelsHotPath | True | 799.33 ns | 47.442 ns | 136.882 ns | 757.73 ns | - | - | - | - | +| CounterWith5LabelsHotPath | True | 972.16 ns | 45.809 ns | 133.626 ns | 939.95 ns | 0.0553 | - | - | 232 B | */ namespace Benchmarks.Metrics @@ -63,15 +46,11 @@ namespace Benchmarks.Metrics [MemoryDiagnoser] public class MetricsBenchmarks { - private readonly KeyValuePair tag1 = new KeyValuePair("attrib1", "value1"); - private readonly KeyValuePair tag2 = new KeyValuePair("attrib2", "value2"); - private readonly KeyValuePair tag3 = new KeyValuePair("attrib3", "value3"); - private readonly KeyValuePair tag4 = new KeyValuePair("attrib4", "value4"); - private readonly KeyValuePair tag5 = new KeyValuePair("attrib5", "value5"); - - private Counter counter; + private Counter counter; private MeterProvider provider; private Meter meter; + private Random random = new Random(); + private string[] dimensionValues = new string[] { "DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10" }; [Params(false, true)] public bool WithSDK { get; set; } @@ -87,7 +66,7 @@ public void Setup() } this.meter = new Meter("TestMeter"); - this.counter = this.meter.CreateCounter("counter"); + this.counter = this.meter.CreateCounter("counter"); } [GlobalCleanup] @@ -106,24 +85,28 @@ public void CounterHotPath() [Benchmark] public void CounterWith1LabelsHotPath() { - this.counter?.Add(100, this.tag1); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); + this.counter?.Add(100, tag1); } [Benchmark] - public void CounterWith1LabelLocal() - { - this.counter?.Add(100, new KeyValuePair("key", "value")); - } - public void CounterWith3LabelsHotPath() { - this.counter?.Add(100, this.tag1, this.tag2, this.tag3); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 10)]); + var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 10)]); + var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 10)]); + this.counter?.Add(100, tag1, tag2, tag3); } [Benchmark] public void CounterWith5LabelsHotPath() { - this.counter?.Add(100, this.tag1, this.tag2, this.tag3, this.tag4, this.tag5); + var tag1 = new KeyValuePair("DimName1", this.dimensionValues[this.random.Next(0, 2)]); + var tag2 = new KeyValuePair("DimName2", this.dimensionValues[this.random.Next(0, 2)]); + var tag3 = new KeyValuePair("DimName3", this.dimensionValues[this.random.Next(0, 5)]); + var tag4 = new KeyValuePair("DimName4", this.dimensionValues[this.random.Next(0, 5)]); + var tag5 = new KeyValuePair("DimName4", this.dimensionValues[this.random.Next(0, 10)]); + this.counter?.Add(100, tag1, tag2, tag3, tag4, tag5); } } } From 175154e1950441ab73544dfa5c2cc59cf338b38f Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Wed, 28 Jul 2021 17:35:33 -0700 Subject: [PATCH 13/45] Add a basic metric test for multi-thread update (#2205) * Add a basic metric test for multi-thread update * nulklable --- src/OpenTelemetry/Metrics/AggregatorStore.cs | 12 ++ .../Metrics/MetricAPITest.cs | 109 +++++++++++++++++- 2 files changed, 118 insertions(+), 3 deletions(-) diff --git a/src/OpenTelemetry/Metrics/AggregatorStore.cs b/src/OpenTelemetry/Metrics/AggregatorStore.cs index c13c01d1961..20aa4c8760e 100644 --- a/src/OpenTelemetry/Metrics/AggregatorStore.cs +++ b/src/OpenTelemetry/Metrics/AggregatorStore.cs @@ -157,6 +157,18 @@ internal List Collect(bool isDelta, DateTimeOffset dt) { var collectedMetrics = new List(); + if (this.tag0Metrics != null) + { + foreach (var aggregator in this.tag0Metrics) + { + var m = aggregator.Collect(dt, isDelta); + if (m != null) + { + collectedMetrics.Add(m); + } + } + } + // Lock to prevent new time series from being added // until collect is done. lock (this.lockKeyValue2MetricAggs) diff --git a/test/OpenTelemetry.Tests/Metrics/MetricAPITest.cs b/test/OpenTelemetry.Tests/Metrics/MetricAPITest.cs index 2a4efce8fc1..1f0d8424d6a 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricAPITest.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricAPITest.cs @@ -14,20 +14,123 @@ // limitations under the License. // +using System; using System.Collections.Generic; using System.Diagnostics.Metrics; -using System.Threading.Tasks; +using System.Threading; +using OpenTelemetry.Tests; using Xunit; -#nullable enable - namespace OpenTelemetry.Metrics.Tests { public class MetricApiTest { + private static int numberOfThreads = 10; + private static long deltaValueUpdatedByEachCall = 10; + private static int numberOfMetricUpdateByEachThread = 1000000; + [Fact] public void SimpleTest() { + var metricItems = new List(); + var metricExporter = new TestExporter(ProcessExport); + void ProcessExport(Batch batch) + { + foreach (var metricItem in batch) + { + metricItems.Add(metricItem); + } + } + + var meter = new Meter("TestMeter"); + var counterLong = meter.CreateCounter("mycounter"); + var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddSource("TestMeter") + .AddMetricProcessor(new PushMetricProcessor(metricExporter, 100, isDelta: true)) + .Build(); + + // setup args to threads. + var mreToBlockUpdateThreads = new ManualResetEvent(false); + var mreToEnsureAllThreadsStarted = new ManualResetEvent(false); + + var argToThread = new UpdateThreadArguments(); + argToThread.Counter = counterLong; + argToThread.ThreadsStartedCount = 0; + argToThread.MreToBlockUpdateThread = mreToBlockUpdateThreads; + argToThread.MreToEnsureAllThreadsStart = mreToEnsureAllThreadsStarted; + + Thread[] t = new Thread[numberOfThreads]; + for (int i = 0; i < numberOfThreads; i++) + { + t[i] = new Thread(CounterUpdateThread); + t[i].Start(argToThread); + } + + // Block until all threads started. + mreToEnsureAllThreadsStarted.WaitOne(); + + // unblock all the threads. + // (i.e let them start counter.Add) + mreToBlockUpdateThreads.Set(); + + for (int i = 0; i < numberOfThreads; i++) + { + // wait for all threads to complete + t[i].Join(); + } + + meterProvider.Dispose(); + + // TODO: Once Dispose does flush, we may not need this + // unknown sleep below. + Thread.Sleep(1000); + + long sumReceived = 0; + foreach (var metricItem in metricItems) + { + var metrics = metricItem.Metrics; + foreach (var metric in metrics) + { + sumReceived += (long)(metric as ISumMetric).Sum.Value; + } + } + + var expectedSum = deltaValueUpdatedByEachCall * numberOfMetricUpdateByEachThread * numberOfThreads; + Assert.Equal(expectedSum, sumReceived); + } + + private static void CounterUpdateThread(object obj) + { + var arguments = obj as UpdateThreadArguments; + if (arguments == null) + { + throw new Exception("Invalid args"); + } + + var mre = arguments.MreToBlockUpdateThread; + var mreToEnsureAllThreadsStart = arguments.MreToEnsureAllThreadsStart; + var counter = arguments.Counter; + + if (Interlocked.Increment(ref arguments.ThreadsStartedCount) == numberOfThreads) + { + mreToEnsureAllThreadsStart.Set(); + } + + // Wait until signalled to start calling update on aggregator + mre.WaitOne(); + + for (int i = 0; i < numberOfMetricUpdateByEachThread; i++) + { + counter.Add(deltaValueUpdatedByEachCall, new KeyValuePair("verb", "GET")); + } + } + + private class UpdateThreadArguments + { + public ManualResetEvent MreToBlockUpdateThread; + public ManualResetEvent MreToEnsureAllThreadsStart; + public int ThreadsStartedCount; + public Counter Counter; } } } From 138035bc22791adbb0ae4d76e263db6bbfd5372f Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Wed, 28 Jul 2021 18:24:32 -0700 Subject: [PATCH 14/45] OtlpMetricsExporter: Add test transforming metrics to OTLP metrics (#2206) --- .../Implementation/MetricItemExtensions.cs | 16 +- .../Implementation/MetricsService.cs | 50 ++++++ .../OtlpMetricsExporter.cs | 6 +- .../OtlpMetricsExporterTests.cs | 150 ++++++++++++++++++ .../OtlpTestHelpers.cs | 120 ++++++++++++++ ...orterTest.cs => OtlpTraceExporterTests.cs} | 104 +----------- 6 files changed, 341 insertions(+), 105 deletions(-) create mode 100644 src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricsService.cs create mode 100644 test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs create mode 100644 test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs rename test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/{OtlpExporterTest.cs => OtlpTraceExporterTests.cs} (80%) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs index f470e9a3c7a..3fa9c2a76f5 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs @@ -120,10 +120,18 @@ internal static OtlpMetrics.Metric ToOtlpMetric(this IMetric metric) var otlpMetric = new OtlpMetrics.Metric { Name = metric.Name, - Description = metric.Description, - Unit = metric.Unit, }; + if (metric.Description != null) + { + otlpMetric.Description = metric.Description; + } + + if (metric.Unit != null) + { + otlpMetric.Unit = metric.Unit; + } + if (metric is ISumMetric sumMetric) { var sum = new OtlpMetrics.Sum @@ -133,7 +141,7 @@ internal static OtlpMetrics.Metric ToOtlpMetric(this IMetric metric) ? OtlpMetrics.AggregationTemporality.Delta : OtlpMetrics.AggregationTemporality.Cumulative, }; - var dataPoint = metric.ToNumberDataPoint(sumMetric.Sum, sumMetric.Exemplars); + var dataPoint = metric.ToNumberDataPoint(sumMetric.Sum.Value, sumMetric.Exemplars); sum.DataPoints.Add(dataPoint); otlpMetric.Sum = sum; } @@ -238,7 +246,7 @@ private static OtlpMetrics.NumberDataPoint ToNumberDataPoint(this IMetric metric { // TODO: Determine how we want to handle exceptions here. // Do we want to just skip this metric and move on? - throw new ArgumentException(); + throw new ArgumentException($"Value must be a long or a double.", nameof(value)); } // TODO: Do TagEnumerationState thing. diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricsService.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricsService.cs new file mode 100644 index 00000000000..051c70d4c18 --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricsService.cs @@ -0,0 +1,50 @@ +// +// 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.Threading; +using Grpc.Core; + +namespace Opentelemetry.Proto.Collector.Metrics.V1 +{ + /// + /// MetricsService extensions. + /// + internal static partial class MetricsService + { + /// Interface for MetricService. + public interface IMetricsServiceClient + { + /// + /// For performance reasons, it is recommended to keep this RPC + /// alive for the entire life of the application. + /// + /// The request to send to the server. + /// The initial metadata to send with the call. This parameter is optional. + /// An optional deadline for the call. The call will be cancelled if deadline is hit. + /// An optional token for canceling the call. + /// The response received from the server. + ExportMetricsServiceResponse Export(ExportMetricsServiceRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default); + } + + /// + /// MetricServiceClient extensions. + /// + public partial class MetricsServiceClient : IMetricsServiceClient + { + } + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricsExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricsExporter.cs index b43cfdad41a..37aa8afcf71 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricsExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricsExporter.cs @@ -43,7 +43,7 @@ public class OtlpMetricsExporter : BaseExporter #else private readonly Channel channel; #endif - private readonly OtlpCollector.MetricsService.MetricsServiceClient metricsClient; + private readonly OtlpCollector.MetricsService.IMetricsServiceClient metricsClient; private readonly Metadata headers; /// @@ -59,8 +59,8 @@ public OtlpMetricsExporter(OtlpExporterOptions options) /// Initializes a new instance of the class. /// /// Configuration options for the exporter. - /// . - internal OtlpMetricsExporter(OtlpExporterOptions options, OtlpCollector.MetricsService.MetricsServiceClient metricsServiceClient = null) + /// . + internal OtlpMetricsExporter(OtlpExporterOptions options, OtlpCollector.MetricsService.IMetricsServiceClient metricsServiceClient = null) { this.options = options ?? throw new ArgumentNullException(nameof(options)); this.headers = GetMetadataFromHeaders(options.Headers); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs new file mode 100644 index 00000000000..2c2eda9f717 --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.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. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Threading; +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Tests; +using OpenTelemetry.Trace; +using Xunit; +using GrpcCore = Grpc.Core; +using OtlpCollector = Opentelemetry.Proto.Collector.Metrics.V1; +using OtlpMetrics = Opentelemetry.Proto.Metrics.V1; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests +{ + public class OtlpMetricsExporterTests + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ToOtlpResourceMetricsTest(bool addResource) + { + using var exporter = new OtlpMetricsExporter( + new OtlpExporterOptions(), + new NoopMetricsServiceClient()); + + if (addResource) + { + exporter.SetResource( + ResourceBuilder.CreateEmpty().AddAttributes( + new List> + { + new KeyValuePair(ResourceSemanticConventions.AttributeServiceName, "service-name"), + new KeyValuePair(ResourceSemanticConventions.AttributeServiceNamespace, "ns1"), + }).Build()); + } + else + { + exporter.SetResource(Resource.Empty); + } + + var tags = new KeyValuePair[] + { + new KeyValuePair("key1", "value1"), + new KeyValuePair("key2", "value2"), + }; + + var processor = new PullMetricProcessor(new TestExporter(RunTest), true); + + using var provider = Sdk.CreateMeterProviderBuilder() + .AddSource("TestMeter") + .AddMetricProcessor(processor) + .Build(); + + using var meter = new Meter("TestMeter", "0.0.1"); + + var counter = meter.CreateCounter("counter"); + + counter.Add(100, tags); + + var testCompleted = false; + + // Invokes the TestExporter which will invoke RunTest + processor.PullRequest(); + + Assert.True(testCompleted); + + void RunTest(Batch metricItem) + { + var request = new OtlpCollector.ExportMetricsServiceRequest(); + request.AddBatch(exporter.ProcessResource, metricItem); + + Assert.Single(request.ResourceMetrics); + var resourceMetric = request.ResourceMetrics.First(); + var oltpResource = resourceMetric.Resource; + + if (addResource) + { + Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service-name"); + Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns1"); + } + else + { + Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:")); + } + + Assert.Single(resourceMetric.InstrumentationLibraryMetrics); + var instrumentationLibraryMetrics = resourceMetric.InstrumentationLibraryMetrics.First(); + Assert.Equal(string.Empty, instrumentationLibraryMetrics.SchemaUrl); + Assert.Equal("TestMeter", instrumentationLibraryMetrics.InstrumentationLibrary.Name); + Assert.Equal("0.0.1", instrumentationLibraryMetrics.InstrumentationLibrary.Version); + + Assert.Single(instrumentationLibraryMetrics.Metrics); + + foreach (var metric in instrumentationLibraryMetrics.Metrics) + { + Assert.Equal(string.Empty, metric.Description); + Assert.Equal(string.Empty, metric.Unit); + Assert.Equal("counter", metric.Name); + + Assert.Equal(OtlpMetrics.Metric.DataOneofCase.Sum, metric.DataCase); + Assert.True(metric.Sum.IsMonotonic); + Assert.Equal(OtlpMetrics.AggregationTemporality.Delta, metric.Sum.AggregationTemporality); + + Assert.Single(metric.Sum.DataPoints); + var dataPoint = metric.Sum.DataPoints.First(); + Assert.True(dataPoint.StartTimeUnixNano > 0); + Assert.True(dataPoint.TimeUnixNano > 0); + Assert.Equal(OtlpMetrics.NumberDataPoint.ValueOneofCase.AsInt, dataPoint.ValueCase); + Assert.Equal(100, dataPoint.AsInt); + +#pragma warning disable CS0612 // Type or member is obsolete + Assert.Empty(dataPoint.Labels); +#pragma warning restore CS0612 // Type or member is obsolete + OtlpTestHelpers.AssertOtlpAttributes(tags.ToList(), dataPoint.Attributes); + + Assert.Empty(dataPoint.Exemplars); + } + + testCompleted = true; + } + } + + private class NoopMetricsServiceClient : OtlpCollector.MetricsService.IMetricsServiceClient + { + public OtlpCollector.ExportMetricsServiceResponse Export(OtlpCollector.ExportMetricsServiceRequest request, GrpcCore.Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) + { + return null; + } + } + } +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs new file mode 100644 index 00000000000..7b0da3aeee2 --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs @@ -0,0 +1,120 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using System.Linq; +using Google.Protobuf.Collections; +using Xunit; +using OtlpCommon = Opentelemetry.Proto.Common.V1; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests +{ + internal static class OtlpTestHelpers + { + public static void AssertOtlpAttributes( + IEnumerable> expected, + RepeatedField actual) + { + var expectedAttributes = expected.ToList(); + int expectedSize = 0; + for (int i = 0; i < expectedAttributes.Count; i++) + { + var current = expectedAttributes[i].Value; + + if (current.GetType().IsArray) + { + if (current is bool[] boolArray) + { + int index = 0; + foreach (var item in boolArray) + { + Assert.Equal(expectedAttributes[i].Key, actual[i + index].Key); + AssertOtlpAttributeValue(item, actual[i + index]); + index++; + expectedSize++; + } + } + else if (current is int[] intArray) + { + int index = 1; + foreach (var item in intArray) + { + Assert.Equal(expectedAttributes[i].Key, actual[i + index].Key); + AssertOtlpAttributeValue(item, actual[i + index]); + index++; + expectedSize++; + } + } + else if (current is double[] doubleArray) + { + int index = 2; + foreach (var item in doubleArray) + { + Assert.Equal(expectedAttributes[i].Key, actual[i + index].Key); + AssertOtlpAttributeValue(item, actual[i + index]); + index++; + expectedSize++; + } + } + else if (current is string[] stringArray) + { + int index = 3; + foreach (var item in stringArray) + { + Assert.Equal(expectedAttributes[i].Key, actual[i + index].Key); + AssertOtlpAttributeValue(item, actual[i + index]); + index++; + expectedSize++; + } + } + } + else + { + Assert.Equal(expectedAttributes[i].Key, actual[i].Key); + AssertOtlpAttributeValue(current, actual[i]); + expectedSize++; + } + } + + Assert.Equal(expectedSize, actual.Count); + } + + private static void AssertOtlpAttributeValue(object expected, OtlpCommon.KeyValue actual) + { + switch (expected) + { + case string s: + Assert.Equal(s, actual.Value.StringValue); + break; + case bool b: + Assert.Equal(b, actual.Value.BoolValue); + break; + case long l: + Assert.Equal(l, actual.Value.IntValue); + break; + case double d: + Assert.Equal(d, actual.Value.DoubleValue); + break; + case int i: + Assert.Equal(i, actual.Value.IntValue); + break; + default: + Assert.Equal(expected.ToString(), actual.Value.StringValue); + break; + } + } + } +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs similarity index 80% rename from test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs rename to test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs index f8a2499f2d0..832d163090a 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,9 +36,9 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests { - public class OtlpExporterTest + public class OtlpTraceExporterTests { - static OtlpExporterTest() + static OtlpTraceExporterTests() { Activity.DefaultIdFormat = ActivityIdFormat.W3C; Activity.ForceDefaultIdFormat = true; @@ -213,7 +213,7 @@ public void ToOtlpSpanTest() Assert.Null(otlpSpan.Status); Assert.Empty(otlpSpan.Events); Assert.Empty(otlpSpan.Links); - AssertOtlpAttributes(attributes, otlpSpan.Attributes); + OtlpTestHelpers.AssertOtlpAttributes(attributes, otlpSpan.Attributes); var expectedStartTimeUnixNano = 100 * expectedUnixTimeTicks; Assert.Equal(expectedStartTimeUnixNano, otlpSpan.StartTimeUnixNano); @@ -254,14 +254,14 @@ public void ToOtlpSpanTest() for (var i = 0; i < childEvents.Count; i++) { Assert.Equal(childEvents[i].Name, otlpSpan.Events[i].Name); - AssertOtlpAttributes(childEvents[i].Tags.ToList(), otlpSpan.Events[i].Attributes); + OtlpTestHelpers.AssertOtlpAttributes(childEvents[i].Tags.ToList(), otlpSpan.Events[i].Attributes); } childLinks.Reverse(); Assert.Equal(childLinks.Count, otlpSpan.Links.Count); for (var i = 0; i < childLinks.Count; i++) { - AssertOtlpAttributes(childLinks[i].Tags.ToList(), otlpSpan.Links[i].Attributes); + OtlpTestHelpers.AssertOtlpAttributes(childLinks[i].Tags.ToList(), otlpSpan.Links[i].Attributes); } } @@ -389,98 +389,6 @@ public void GetMetadataFromHeadersThrowsExceptionOnOnvalidFormat(string headers) throw new XunitException("GetMetadataFromHeaders did not throw an exception for invalid input headers"); } - private static void AssertOtlpAttributes( - List> expectedAttributes, - RepeatedField otlpAttributes) - { - int expectedSize = 0; - for (int i = 0; i < expectedAttributes.Count; i++) - { - var current = expectedAttributes[i].Value; - - if (current.GetType().IsArray) - { - if (current is bool[] boolArray) - { - int index = 0; - foreach (var item in boolArray) - { - Assert.Equal(expectedAttributes[i].Key, otlpAttributes[i + index].Key); - AssertOtlpAttributeValue(item, otlpAttributes[i + index]); - index++; - expectedSize++; - } - } - else if (current is int[] intArray) - { - int index = 1; - foreach (var item in intArray) - { - Assert.Equal(expectedAttributes[i].Key, otlpAttributes[i + index].Key); - AssertOtlpAttributeValue(item, otlpAttributes[i + index]); - index++; - expectedSize++; - } - } - else if (current is double[] doubleArray) - { - int index = 2; - foreach (var item in doubleArray) - { - Assert.Equal(expectedAttributes[i].Key, otlpAttributes[i + index].Key); - AssertOtlpAttributeValue(item, otlpAttributes[i + index]); - index++; - expectedSize++; - } - } - else if (current is string[] stringArray) - { - int index = 3; - foreach (var item in stringArray) - { - Assert.Equal(expectedAttributes[i].Key, otlpAttributes[i + index].Key); - AssertOtlpAttributeValue(item, otlpAttributes[i + index]); - index++; - expectedSize++; - } - } - } - else - { - Assert.Equal(expectedAttributes[i].Key, otlpAttributes[i].Key); - AssertOtlpAttributeValue(current, otlpAttributes[i]); - expectedSize++; - } - } - - Assert.Equal(expectedSize, otlpAttributes.Count); - } - - private static void AssertOtlpAttributeValue(object originalValue, OtlpCommon.KeyValue akv) - { - switch (originalValue) - { - case string s: - Assert.Equal(s, akv.Value.StringValue); - break; - case bool b: - Assert.Equal(b, akv.Value.BoolValue); - break; - case long l: - Assert.Equal(l, akv.Value.IntValue); - break; - case double d: - Assert.Equal(d, akv.Value.DoubleValue); - break; - case int i: - Assert.Equal(i, akv.Value.IntValue); - break; - default: - Assert.Equal(originalValue.ToString(), akv.Value.StringValue); - break; - } - } - private class NoopTraceServiceClient : OtlpCollector.TraceService.ITraceServiceClient { public OtlpCollector.ExportTraceServiceResponse Export(OtlpCollector.ExportTraceServiceRequest request, GrpcCore.Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) From ceaaa4026281d33e30a8b7d4fa359e51c0bc8682 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Thu, 29 Jul 2021 10:31:08 -0700 Subject: [PATCH 15/45] Logformatter null check (#2200) --- src/OpenTelemetry/CHANGELOG.md | 3 ++ src/OpenTelemetry/Logs/OpenTelemetryLogger.cs | 2 +- .../BasicTests.cs | 4 +- .../DependencyInjectionConfigTests.cs | 4 +- ...stsCollectionsIsAccordingToTheSpecTests.cs | 4 +- .../OpenTelemetry.Tests/Logs/LogRecordTest.cs | 47 ++++++++++--------- 6 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 988d8b0216e..89595afcbd8 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -5,6 +5,9 @@ * 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)) + ## 1.2.0-alpha1 Released 2021-Jul-23 diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index bc0cfeb25ba..11ca152d64a 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -53,7 +53,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except this.categoryName, logLevel, eventId, - options.IncludeFormattedMessage ? formatter(state, exception) : null, + options.IncludeFormattedMessage ? formatter?.Invoke(state, exception) : null, options.ParseStateValues ? null : (object)state, exception, options.ParseStateValues ? this.ParseState(state) : null); diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs index 2bb95b82f8a..5fb47ca84cb 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs @@ -31,9 +31,7 @@ using OpenTelemetry.Instrumentation.AspNetCore.Implementation; using OpenTelemetry.Tests; using OpenTelemetry.Trace; -#if NETCOREAPP2_1 -using TestApp.AspNetCore._2._1; -#elif NETCOREAPP3_1 +#if NETCOREAPP3_1 using TestApp.AspNetCore._3._1; #else using TestApp.AspNetCore._5._0; diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs index 5c615b6d462..dfd8ad2b77b 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs @@ -20,9 +20,7 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Trace; -#if NETCOREAPP2_1 -using TestApp.AspNetCore._2._1; -#elif NETCOREAPP3_1 +#if NETCOREAPP3_1 using TestApp.AspNetCore._3._1; #else using TestApp.AspNetCore._5._0; diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs index cbfb66ea766..8f5ba8bd95c 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs @@ -25,9 +25,7 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using OpenTelemetry.Trace; -#if NETCOREAPP2_1 -using TestApp.AspNetCore._2._1; -#elif NETCOREAPP3_1 +#if NETCOREAPP3_1 using TestApp.AspNetCore._3._1; #else using TestApp.AspNetCore._5._0; diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs index 3e733bc37b8..f0e40724e2d 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs @@ -13,11 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // - #if !NET461 -#if NETCOREAPP2_1 -using Microsoft.Extensions.DependencyInjection; -#endif using System; using System.Collections; using System.Collections.Generic; @@ -36,11 +32,7 @@ public sealed class LogRecordTest : IDisposable { private readonly ILogger logger; private readonly List exportedItems = new List(); -#if NETCOREAPP2_1 - private readonly ServiceProvider serviceProvider; -#else private readonly ILoggerFactory loggerFactory; -#endif private readonly BaseExportProcessor processor; private readonly BaseExporter exporter; private OpenTelemetryLoggerOptions options; @@ -49,11 +41,7 @@ public LogRecordTest() { this.exporter = new InMemoryExporter(this.exportedItems); this.processor = new TestLogRecordProcessor(this.exporter); -#if NETCOREAPP2_1 - var serviceCollection = new ServiceCollection().AddLogging(builder => -#else this.loggerFactory = LoggerFactory.Create(builder => -#endif { builder.AddOpenTelemetry(options => { @@ -64,12 +52,7 @@ public LogRecordTest() builder.AddFilter(typeof(LogRecordTest).FullName, LogLevel.Trace); }); -#if NETCOREAPP2_1 - this.serviceProvider = serviceCollection.BuildServiceProvider(); - this.logger = this.serviceProvider.GetRequiredService>(); -#else this.logger = this.loggerFactory.CreateLogger(); -#endif } [Fact] @@ -339,6 +322,32 @@ public void IncludeFormattedMessageTest() } } + [Fact] + public void IncludeFormattedMessageTestWhenFormatterNull() + { + this.logger.Log(LogLevel.Information, default, "Hello World!", null, null); + var logRecord = this.exportedItems[0]; + Assert.Null(logRecord.FormattedMessage); + + this.options.IncludeFormattedMessage = true; + try + { + // Pass null as formatter function + this.logger.Log(LogLevel.Information, default, "Hello World!", null, null); + logRecord = this.exportedItems[1]; + Assert.Null(logRecord.FormattedMessage); + + var expectedFormattedMessage = "formatted message"; + this.logger.Log(LogLevel.Information, default, "Hello World!", null, (state, ex) => expectedFormattedMessage); + logRecord = this.exportedItems[2]; + Assert.Equal(expectedFormattedMessage, logRecord.FormattedMessage); + } + finally + { + this.options.IncludeFormattedMessage = false; + } + } + [Fact] public void IncludeScopesTest() { @@ -598,11 +607,7 @@ public void ParseStateValuesUsingCustomTest() public void Dispose() { -#if NETCOREAPP2_1 - this.serviceProvider?.Dispose(); -#else this.loggerFactory?.Dispose(); -#endif } internal struct Food From 29a4a791f2f9c3e6cb9b3a6d45efa78b4268f42b Mon Sep 17 00:00:00 2001 From: Michael Maxwell Date: Thu, 29 Jul 2021 16:13:44 -0700 Subject: [PATCH 16/45] Change `paths-ignore` to `paths` in `markdownlint.yml` (#2211) Markdown lint should only run when a `*.md` file has changed. This was likely a copy paste mistake when this Github action was created. --- .github/workflows/markdownlint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index 6f4faf7cb21..887b96a65f0 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -3,7 +3,7 @@ name: markdownlint on: push: branches: [ main, metrics ] - paths-ignore: + paths: - '**.md' pull_request: branches: [ main, metrics ] From 4323c93e14c1aa56e9f81717321818786d80ccd7 Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Thu, 29 Jul 2021 16:56:36 -0700 Subject: [PATCH 17/45] Refactor UnitTests (#2212) Co-authored-by: Cijo Thomas --- .../Implementation/Int128Test.cs | 3 +-- .../Implementation/JaegerActivityConversionTest.cs | 4 +--- .../Implementation/ThriftUdpClientTransportTests.cs | 3 +-- .../JaegerExporterTests.cs | 2 +- .../OtlpTraceExporterTests.cs | 1 - .../Implementation/ZipkinActivityConversionExtensionsTest.cs | 2 +- .../Implementation/ZipkinActivityConversionTest.cs | 4 ++-- .../ZipkinActivityExporterRemoteEndpointTests.cs | 4 ++-- .../GrpcTests.server.cs | 2 +- .../Services/GreeterService.cs | 2 +- test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs | 3 +-- test/OpenTelemetry.Tests/Logs/LogRecordTest.cs | 3 ++- 12 files changed, 14 insertions(+), 19 deletions(-) diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/Int128Test.cs b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/Int128Test.cs index 165f34fdc96..09a2cf8ccc8 100644 --- a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/Int128Test.cs +++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/Int128Test.cs @@ -15,10 +15,9 @@ // using System.Diagnostics; -using OpenTelemetry.Exporter.Jaeger.Implementation; using Xunit; -namespace OpenTelemetry.Exporter.Jaeger.Tests.Implementation +namespace OpenTelemetry.Exporter.Jaeger.Implementation.Tests { public class Int128Test { diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs index b691a66b5ec..1670e37876b 100644 --- a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs +++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs @@ -17,14 +17,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; - -using OpenTelemetry.Exporter.Jaeger.Implementation; using OpenTelemetry.Internal; using OpenTelemetry.Resources; using OpenTelemetry.Trace; using Xunit; -namespace OpenTelemetry.Exporter.Jaeger.Tests.Implementation +namespace OpenTelemetry.Exporter.Jaeger.Implementation.Tests { public class JaegerActivityConversionTest { diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/ThriftUdpClientTransportTests.cs b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/ThriftUdpClientTransportTests.cs index 730a4754d30..01764860d3d 100644 --- a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/ThriftUdpClientTransportTests.cs +++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/ThriftUdpClientTransportTests.cs @@ -18,11 +18,10 @@ using System.Threading; using System.Threading.Tasks; using Moq; -using OpenTelemetry.Exporter.Jaeger.Implementation; using Thrift.Transport; using Xunit; -namespace OpenTelemetry.Exporter.Jaeger.Tests.Implementation +namespace OpenTelemetry.Exporter.Jaeger.Implementation.Tests { public class ThriftUdpClientTransportTests : IDisposable { diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/JaegerExporterTests.cs b/test/OpenTelemetry.Exporter.Jaeger.Tests/JaegerExporterTests.cs index 6e301fb40c3..2fef9caf334 100644 --- a/test/OpenTelemetry.Exporter.Jaeger.Tests/JaegerExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/JaegerExporterTests.cs @@ -19,7 +19,7 @@ using System.Diagnostics; using System.Linq; using OpenTelemetry.Exporter.Jaeger.Implementation; -using OpenTelemetry.Exporter.Jaeger.Tests.Implementation; +using OpenTelemetry.Exporter.Jaeger.Implementation.Tests; using OpenTelemetry.Resources; using OpenTelemetry.Trace; using Thrift.Protocol; diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs index 832d163090a..94a13f28060 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs @@ -20,7 +20,6 @@ using System.Linq; using System.Reflection; using System.Threading; -using Google.Protobuf.Collections; using Grpc.Core; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Resources; diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs index 8a2ab8de7d2..db112f92a90 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionExtensionsTest.cs @@ -19,7 +19,7 @@ using Xunit; using static OpenTelemetry.Exporter.Zipkin.Implementation.ZipkinActivityConversionExtensions; -namespace OpenTelemetry.Exporter.Zipkin.Tests.Implementation +namespace OpenTelemetry.Exporter.Zipkin.Implementation.Tests { public class ZipkinActivityConversionExtensionsTest { diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs index b1986e23f14..e422e31436a 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs @@ -15,12 +15,12 @@ // using System.Linq; -using OpenTelemetry.Exporter.Zipkin.Implementation; +using OpenTelemetry.Exporter.Zipkin.Tests; using OpenTelemetry.Internal; using OpenTelemetry.Trace; using Xunit; -namespace OpenTelemetry.Exporter.Zipkin.Tests.Implementation +namespace OpenTelemetry.Exporter.Zipkin.Implementation.Tests { public class ZipkinActivityConversionTest { diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs index 240e8daca4b..9d1aeb644f5 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs @@ -15,11 +15,11 @@ // using System.Collections.Generic; -using OpenTelemetry.Exporter.Zipkin.Implementation; +using OpenTelemetry.Exporter.Zipkin.Tests; using OpenTelemetry.Trace; using Xunit; -namespace OpenTelemetry.Exporter.Zipkin.Tests.Implementation +namespace OpenTelemetry.Exporter.Zipkin.Implementation.Tests { public class ZipkinActivityExporterRemoteEndpointTests { diff --git a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs index bfe23c2c71c..08c3e0290e1 100644 --- a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs +++ b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs @@ -24,7 +24,7 @@ using Grpc.Net.Client; using Moq; using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Instrumentation.Grpc.Tests.Services; +using OpenTelemetry.Instrumentation.Grpc.Services.Tests; using OpenTelemetry.Instrumentation.GrpcNetClient; using OpenTelemetry.Trace; using Xunit; diff --git a/test/OpenTelemetry.Instrumentation.Grpc.Tests/Services/GreeterService.cs b/test/OpenTelemetry.Instrumentation.Grpc.Tests/Services/GreeterService.cs index d2b60d5ae66..f7411f6a1a1 100644 --- a/test/OpenTelemetry.Instrumentation.Grpc.Tests/Services/GreeterService.cs +++ b/test/OpenTelemetry.Instrumentation.Grpc.Tests/Services/GreeterService.cs @@ -18,7 +18,7 @@ using Grpc.Core; using Microsoft.Extensions.Logging; -namespace OpenTelemetry.Instrumentation.Grpc.Tests.Services +namespace OpenTelemetry.Instrumentation.Grpc.Services.Tests { public class GreeterService : Greeter.GreeterBase { diff --git a/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs b/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs index f5047a1e5f1..387a9162623 100644 --- a/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs +++ b/test/OpenTelemetry.Tests/Internal/CircularBufferTest.cs @@ -15,10 +15,9 @@ // using System; -using OpenTelemetry.Internal; using Xunit; -namespace OpenTelemetry.Tests.Internal +namespace OpenTelemetry.Internal.Tests { public class CircularBufferTest { diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs index f0e40724e2d..29f6c2b3878 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs @@ -23,10 +23,11 @@ using Microsoft.Extensions.Logging; using OpenTelemetry.Exporter; using OpenTelemetry.Logs; +using OpenTelemetry.Tests; using OpenTelemetry.Trace; using Xunit; -namespace OpenTelemetry.Tests.Logs +namespace OpenTelemetry.Logs.Tests { public sealed class LogRecordTest : IDisposable { From 54d46d80a03acfa0813021e5875a00a7ef423274 Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Thu, 29 Jul 2021 17:27:28 -0700 Subject: [PATCH 18/45] Remove unncessary usings (#2213) --- docs/metrics/getting-started/Program.cs | 1 - examples/Console/TestPrometheusExporter.cs | 2 -- src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs | 3 --- src/OpenTelemetry/Logs/LogRecord.cs | 1 - src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs | 1 - src/OpenTelemetry/Metrics/DataPoint/DataValue.cs | 2 -- src/OpenTelemetry/Metrics/Processors/PullMetricProcessor.cs | 2 -- src/OpenTelemetry/Metrics/ThreadStaticStorage.cs | 1 - .../DependencyInjectionConfigTests.cs | 2 -- 9 files changed, 15 deletions(-) diff --git a/docs/metrics/getting-started/Program.cs b/docs/metrics/getting-started/Program.cs index e8aa9c63267..dec6c5a3889 100644 --- a/docs/metrics/getting-started/Program.cs +++ b/docs/metrics/getting-started/Program.cs @@ -14,7 +14,6 @@ // limitations under the License. // -using System; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Threading; diff --git a/examples/Console/TestPrometheusExporter.cs b/examples/Console/TestPrometheusExporter.cs index 34ef6efcf08..17fbe05e435 100644 --- a/examples/Console/TestPrometheusExporter.cs +++ b/examples/Console/TestPrometheusExporter.cs @@ -14,9 +14,7 @@ // limitations under the License. // -using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.Metrics; using System.Threading; using System.Threading.Tasks; diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs index ea51da4cf56..7f85f1ba7e8 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporter.cs @@ -15,9 +15,6 @@ // using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using OpenTelemetry.Metrics; namespace OpenTelemetry.Exporter diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index a0aa6fff8c0..a73d11c9cbd 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -17,7 +17,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; namespace OpenTelemetry.Logs diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs index 5e957170bf7..7affea1ef02 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs @@ -19,7 +19,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Configuration; -using OpenTelemetry; using OpenTelemetry.Logs; namespace Microsoft.Extensions.Logging diff --git a/src/OpenTelemetry/Metrics/DataPoint/DataValue.cs b/src/OpenTelemetry/Metrics/DataPoint/DataValue.cs index e3db6eef5a0..cfbe935a226 100644 --- a/src/OpenTelemetry/Metrics/DataPoint/DataValue.cs +++ b/src/OpenTelemetry/Metrics/DataPoint/DataValue.cs @@ -14,8 +14,6 @@ // limitations under the License. // -using System; - namespace OpenTelemetry.Metrics { public readonly struct DataValue : IDataValue diff --git a/src/OpenTelemetry/Metrics/Processors/PullMetricProcessor.cs b/src/OpenTelemetry/Metrics/Processors/PullMetricProcessor.cs index e9ce9412b98..c0383278dc2 100644 --- a/src/OpenTelemetry/Metrics/Processors/PullMetricProcessor.cs +++ b/src/OpenTelemetry/Metrics/Processors/PullMetricProcessor.cs @@ -15,8 +15,6 @@ // using System; -using System.Threading; -using System.Threading.Tasks; namespace OpenTelemetry.Metrics { diff --git a/src/OpenTelemetry/Metrics/ThreadStaticStorage.cs b/src/OpenTelemetry/Metrics/ThreadStaticStorage.cs index 78053e1166e..d0c109b5ea1 100644 --- a/src/OpenTelemetry/Metrics/ThreadStaticStorage.cs +++ b/src/OpenTelemetry/Metrics/ThreadStaticStorage.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.Metrics; using System.Runtime.CompilerServices; namespace OpenTelemetry.Metrics diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs index dfd8ad2b77b..cce05338e77 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs @@ -14,8 +14,6 @@ // limitations under the License. // -using System; -using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; From d6d815e6a8fd59e9fc71248e4ff838e1341c6afd Mon Sep 17 00:00:00 2001 From: Victor Lu Date: Fri, 30 Jul 2021 20:11:53 -0700 Subject: [PATCH 19/45] Add Explicit Boundary Histogram Aggregator (#2216) --- .../HistogramMetricAggregator.cs | 103 ++++++++++++++- .../Metrics/AggregatorTest.cs | 124 ++++++++++++++++++ 2 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs b/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs index 742c53a7386..6fc3b556757 100644 --- a/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs +++ b/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs @@ -22,10 +22,19 @@ namespace OpenTelemetry.Metrics { internal class HistogramMetricAggregator : IHistogramMetric, IAggregator { + private static readonly double[] DefaultBoundaries = new double[] { 0, 5, 10, 25, 50, 75, 100, 250, 500, 1000 }; + private readonly object lockUpdate = new object(); - private List buckets = new List(); + private HistogramBucket[] buckets; + + private double[] boundaries; internal HistogramMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair[] attributes) + : this(name, description, unit, meter, startTimeExclusive, attributes, DefaultBoundaries) + { + } + + internal HistogramMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair[] attributes, double[] boundaries) { this.Name = name; this.Description = description; @@ -33,6 +42,14 @@ internal HistogramMetricAggregator(string name, string description, string unit, this.Meter = meter; this.StartTimeExclusive = startTimeExclusive; this.Attributes = attributes; + + if (boundaries.Length == 0) + { + boundaries = DefaultBoundaries; + } + + this.boundaries = boundaries; + this.buckets = this.InitializeBucket(boundaries); } public string Name { get; private set; } @@ -62,11 +79,38 @@ internal HistogramMetricAggregator(string name, string description, string unit, public void Update(T value) where T : struct { - // TODO: Implement Histogram! + // promote value to be a double + + double val; + if (typeof(T) == typeof(long)) + { + val = (long)(object)value; + } + else if (typeof(T) == typeof(double)) + { + val = (double)(object)value; + } + else + { + throw new Exception("Unsupported Type!"); + } + + // Determine the bucket index + + int i; + for (i = 0; i < this.boundaries.Length; i++) + { + if (val < this.boundaries[i]) + { + break; + } + } lock (this.lockUpdate) { this.PopulationCount++; + this.PopulationSum += val; + this.buckets[i].Count++; } } @@ -78,7 +122,7 @@ public IMetric Collect(DateTimeOffset dt, bool isDelta) return null; } - var cloneItem = new HistogramMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes); + var cloneItem = new HistogramMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes, this.boundaries); lock (this.lockUpdate) { @@ -86,7 +130,8 @@ public IMetric Collect(DateTimeOffset dt, bool isDelta) cloneItem.EndTimeInclusive = dt; cloneItem.PopulationCount = this.PopulationCount; cloneItem.PopulationSum = this.PopulationSum; - cloneItem.buckets = this.buckets; + cloneItem.boundaries = this.boundaries; + this.buckets.CopyTo(cloneItem.buckets, 0); cloneItem.IsDeltaTemporality = isDelta; if (isDelta) @@ -94,6 +139,10 @@ public IMetric Collect(DateTimeOffset dt, bool isDelta) this.StartTimeExclusive = dt; this.PopulationCount = 0; this.PopulationSum = 0; + for (int i = 0; i < this.buckets.Length; i++) + { + this.buckets[i].Count = 0; + } } } @@ -104,5 +153,51 @@ public string ToDisplayString() { return $"Count={this.PopulationCount},Sum={this.PopulationSum}"; } + + private HistogramBucket[] InitializeBucket(double[] boundaries) + { + var buckets = new HistogramBucket[boundaries.Length + 1]; + + var lastBoundary = boundaries[0]; + for (int i = 0; i < buckets.Length; i++) + { + if (i == 0) + { + // LowBoundary is inclusive + buckets[i].LowBoundary = double.NegativeInfinity; + + // HighBoundary is exclusive + buckets[i].HighBoundary = boundaries[i]; + } + else if (i < boundaries.Length) + { + // LowBoundary is inclusive + buckets[i].LowBoundary = lastBoundary; + + // HighBoundary is exclusive + buckets[i].HighBoundary = boundaries[i]; + } + else + { + // LowBoundary and HighBoundary are inclusive + buckets[i].LowBoundary = lastBoundary; + buckets[i].HighBoundary = double.PositiveInfinity; + } + + buckets[i].Count = 0; + + if (i < boundaries.Length) + { + if (boundaries[i] < lastBoundary) + { + throw new ArgumentException("Boundary values must be increasing.", nameof(boundaries)); + } + + lastBoundary = boundaries[i]; + } + } + + return buckets; + } } } diff --git a/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs b/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs new file mode 100644 index 00000000000..fb8437dd687 --- /dev/null +++ b/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs @@ -0,0 +1,124 @@ +// +// 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.Metrics; +using Xunit; + +namespace OpenTelemetry.Metrics.Tests +{ + public class AggregatorTest + { + [Fact] + public void HistogramDistributeToAllBuckets() + { + using var meter = new Meter("TestMeter", "0.0.1"); + + var hist = new HistogramMetricAggregator("test", "desc", "1", meter, DateTimeOffset.UtcNow, new KeyValuePair[0]); + + hist.Update(-1); + hist.Update(0); + hist.Update(5); + hist.Update(10); + hist.Update(25); + hist.Update(50); + hist.Update(75); + hist.Update(100); + hist.Update(250); + hist.Update(500); + hist.Update(1000); + var metric = hist.Collect(DateTimeOffset.UtcNow, false); + + Assert.NotNull(metric); + Assert.IsType(metric); + + if (metric is HistogramMetricAggregator agg) + { + int len = 0; + foreach (var bucket in agg.Buckets) + { + Assert.Equal(1, bucket.Count); + len++; + } + + Assert.Equal(11, len); + } + } + + [Fact] + public void HistogramCustomBoundaries() + { + using var meter = new Meter("TestMeter", "0.0.1"); + + var hist = new HistogramMetricAggregator("test", "desc", "1", meter, DateTimeOffset.UtcNow, new KeyValuePair[0], new double[] { 0 }); + + hist.Update(-1); + hist.Update(0); + var metric = hist.Collect(DateTimeOffset.UtcNow, false); + + Assert.NotNull(metric); + Assert.IsType(metric); + + if (metric is HistogramMetricAggregator agg) + { + int len = 0; + foreach (var bucket in agg.Buckets) + { + Assert.Equal(1, bucket.Count); + len++; + } + + Assert.Equal(2, len); + } + } + + [Fact] + public void HistogramWithEmptyBuckets() + { + using var meter = new Meter("TestMeter", "0.0.1"); + + var hist = new HistogramMetricAggregator("test", "desc", "1", meter, DateTimeOffset.UtcNow, new KeyValuePair[0], new double[] { 0, 5, 10 }); + + hist.Update(-3); + hist.Update(-2); + hist.Update(-1); + hist.Update(6); + hist.Update(7); + hist.Update(12); + var metric = hist.Collect(DateTimeOffset.UtcNow, false); + + Assert.NotNull(metric); + Assert.IsType(metric); + + if (metric is HistogramMetricAggregator agg) + { + var expectedCounts = new int[] { 3, 0, 2, 1 }; + int len = 0; + foreach (var bucket in agg.Buckets) + { + if (len < expectedCounts.Length) + { + Assert.Equal(expectedCounts[len], bucket.Count); + len++; + } + } + + Assert.Equal(4, len); + } + } + } +} From bd69bd6d29764434bbe08cede8d8003e2f2dccb8 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Mon, 2 Aug 2021 09:15:18 -0700 Subject: [PATCH 20/45] Refactor Aggregator and Export data structure (#2214) --- .../ConsoleMetricExporter.cs | 73 ++++--- .../Implementation/MetricItemExtensions.cs | 196 +++++++++++------- .../PrometheusExporterExtensions.cs | 16 +- src/OpenTelemetry/Metrics/AggregatorStore.cs | 15 +- .../GaugeMetricAggregator.cs | 5 + .../HistogramMetricAggregator.cs | 3 + .../Metrics/MetricAggregators/IMetric.cs | 2 + .../Metrics/MetricAggregators/ISumMetric.cs | 2 - .../MetricAggregators/ISumMetricDouble.cs | 23 ++ .../MetricAggregators/ISumMetricLong.cs | 23 ++ .../Metrics/MetricAggregators/MetricType.cs | 51 +++++ .../MetricAggregators/SumMetricAggregator.cs | 147 ------------- .../SumMetricAggregatorDouble.cs | 93 +++++++++ .../SumMetricAggregatorLong.cs | 92 ++++++++ .../MetricAggregators/SumMetricDouble.cs | 66 ++++++ .../MetricAggregators/SumMetricLong.cs | 66 ++++++ .../SummaryMetricAggregator.cs | 3 + .../MetricTests.cs | 22 +- .../Metrics/MetricAPITest.cs | 29 ++- 19 files changed, 634 insertions(+), 293 deletions(-) create mode 100644 src/OpenTelemetry/Metrics/MetricAggregators/ISumMetricDouble.cs create mode 100644 src/OpenTelemetry/Metrics/MetricAggregators/ISumMetricLong.cs create mode 100644 src/OpenTelemetry/Metrics/MetricAggregators/MetricType.cs delete mode 100644 src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregator.cs create mode 100644 src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregatorDouble.cs create mode 100644 src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregatorLong.cs create mode 100644 src/OpenTelemetry/Metrics/MetricAggregators/SumMetricDouble.cs create mode 100644 src/OpenTelemetry/Metrics/MetricAggregators/SumMetricLong.cs diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs index a2904f886d7..e641db63d81 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs @@ -56,44 +56,53 @@ public override ExportResult Export(in Batch batch) var tags = metric.Attributes.ToArray().Select(k => $"{k.Key}={k.Value?.ToString()}"); string valueDisplay = string.Empty; - if (metric is ISumMetric sumMetric) - { - if (sumMetric.Sum.Value is double doubleSum) - { - valueDisplay = ((double)doubleSum).ToString(CultureInfo.InvariantCulture); - } - else if (sumMetric.Sum.Value is long longSum) - { - valueDisplay = ((long)longSum).ToString(); - } - } - else if (metric is IGaugeMetric gaugeMetric) - { - if (gaugeMetric.LastValue.Value is double doubleValue) - { - valueDisplay = ((double)doubleValue).ToString(); - } - else if (gaugeMetric.LastValue.Value is long longValue) - { - valueDisplay = ((long)longValue).ToString(); - } - // Qn: tags again ? gaugeMetric.LastValue.Tags - } - else if (metric is ISummaryMetric summaryMetric) + // Switch would be faster than the if.else ladder + // of try and cast. + switch (metric.MetricType) { - valueDisplay = string.Format("Sum: {0} Count: {1}", summaryMetric.PopulationSum, summaryMetric.PopulationCount); + case MetricType.LongSum: + { + valueDisplay = (metric as ISumMetricLong).LongSum.ToString(CultureInfo.InvariantCulture); + break; + } + + case MetricType.DoubleSum: + { + valueDisplay = (metric as ISumMetricDouble).DoubleSum.ToString(CultureInfo.InvariantCulture); + break; + } + + case MetricType.LongGauge: + { + // TODOs + break; + } + + case MetricType.DoubleGauge: + { + // TODOs + break; + } + + case MetricType.Histogram: + { + var histogramMetric = metric as IHistogramMetric; + valueDisplay = string.Format("Sum: {0} Count: {1}", histogramMetric.PopulationSum, histogramMetric.PopulationCount); + break; + } + + case MetricType.Summary: + { + var summaryMetric = metric as ISummaryMetric; + valueDisplay = string.Format("Sum: {0} Count: {1}", summaryMetric.PopulationSum, summaryMetric.PopulationCount); + break; + } } - else if (metric is IHistogramMetric histogramMetric) - { - valueDisplay = string.Format("Sum: {0} Count: {1}", histogramMetric.PopulationSum, histogramMetric.PopulationCount); - } - - var kind = metric.GetType().Name; string time = $"{metric.StartTimeExclusive.ToLocalTime().ToString("HH:mm:ss.fff")} {metric.EndTimeInclusive.ToLocalTime().ToString("HH:mm:ss.fff")}"; - var msg = new StringBuilder($"Export {time} {metric.Name} [{string.Join(";", tags)}] {kind} Value: {valueDisplay}"); + var msg = new StringBuilder($"Export {time} {metric.Name} [{string.Join(";", tags)}] {metric.MetricType} Value: {valueDisplay}"); if (!string.IsNullOrEmpty(metric.Description)) { diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs index 3fa9c2a76f5..f2bd7010b16 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs @@ -132,95 +132,135 @@ internal static OtlpMetrics.Metric ToOtlpMetric(this IMetric metric) otlpMetric.Unit = metric.Unit; } - if (metric is ISumMetric sumMetric) + switch (metric.MetricType) { - var sum = new OtlpMetrics.Sum - { - IsMonotonic = sumMetric.IsMonotonic, - AggregationTemporality = sumMetric.IsDeltaTemporality + case MetricType.LongSum: + { + var sumMetric = metric as ISumMetricLong; + var sum = new OtlpMetrics.Sum + { + IsMonotonic = sumMetric.IsMonotonic, + AggregationTemporality = sumMetric.IsDeltaTemporality ? OtlpMetrics.AggregationTemporality.Delta : OtlpMetrics.AggregationTemporality.Cumulative, - }; - var dataPoint = metric.ToNumberDataPoint(sumMetric.Sum.Value, sumMetric.Exemplars); - sum.DataPoints.Add(dataPoint); - otlpMetric.Sum = sum; - } - else if (metric is IGaugeMetric gaugeMetric) - { - var gauge = new OtlpMetrics.Gauge(); - var dataPoint = metric.ToNumberDataPoint(gaugeMetric.LastValue.Value, gaugeMetric.Exemplars); - gauge.DataPoints.Add(dataPoint); - otlpMetric.Gauge = gauge; - } - else if (metric is ISummaryMetric summaryMetric) - { - var summary = new OtlpMetrics.Summary(); - - var dataPoint = new OtlpMetrics.SummaryDataPoint - { - StartTimeUnixNano = (ulong)metric.StartTimeExclusive.ToUnixTimeNanoseconds(), - TimeUnixNano = (ulong)metric.EndTimeInclusive.ToUnixTimeNanoseconds(), - Count = (ulong)summaryMetric.PopulationCount, - Sum = summaryMetric.PopulationSum, - }; - - // TODO: Do TagEnumerationState thing. - foreach (var attribute in metric.Attributes) - { - dataPoint.Attributes.Add(attribute.ToOtlpAttribute()); - } + }; + var dataPoint = metric.ToNumberDataPoint(sumMetric.LongSum, sumMetric.Exemplars); + sum.DataPoints.Add(dataPoint); + otlpMetric.Sum = sum; + break; + } - foreach (var quantile in summaryMetric.Quantiles) - { - var quantileValue = new OtlpMetrics.SummaryDataPoint.Types.ValueAtQuantile + case MetricType.DoubleSum: { - Quantile = quantile.Quantile, - Value = quantile.Value, - }; - dataPoint.QuantileValues.Add(quantileValue); - } - - otlpMetric.Summary = summary; - } - else if (metric is IHistogramMetric histogramMetric) - { - var histogram = new OtlpMetrics.Histogram - { - AggregationTemporality = histogramMetric.IsDeltaTemporality + var sumMetric = metric as ISumMetricDouble; + var sum = new OtlpMetrics.Sum + { + IsMonotonic = sumMetric.IsMonotonic, + AggregationTemporality = sumMetric.IsDeltaTemporality ? OtlpMetrics.AggregationTemporality.Delta : OtlpMetrics.AggregationTemporality.Cumulative, - }; - - var dataPoint = new OtlpMetrics.HistogramDataPoint - { - StartTimeUnixNano = (ulong)metric.StartTimeExclusive.ToUnixTimeNanoseconds(), - TimeUnixNano = (ulong)metric.EndTimeInclusive.ToUnixTimeNanoseconds(), - Count = (ulong)histogramMetric.PopulationCount, - Sum = histogramMetric.PopulationSum, - }; - - foreach (var bucket in histogramMetric.Buckets) - { - dataPoint.BucketCounts.Add((ulong)bucket.Count); + }; + var dataPoint = metric.ToNumberDataPoint(sumMetric.DoubleSum, sumMetric.Exemplars); + sum.DataPoints.Add(dataPoint); + otlpMetric.Sum = sum; + break; + } - // TODO: Verify how to handle the bounds. We've modeled things with - // a LowBoundary and HighBoundary. OTLP data model has modeled this - // differently: https://github.com/open-telemetry/opentelemetry-proto/blob/bacfe08d84e21fb2a779e302d12e8dfeb67e7b86/opentelemetry/proto/metrics/v1/metrics.proto#L554-L568 - dataPoint.ExplicitBounds.Add(bucket.HighBoundary); - } + case MetricType.LongGauge: + { + var gaugeMetric = metric as IGaugeMetric; + var gauge = new OtlpMetrics.Gauge(); + var dataPoint = metric.ToNumberDataPoint(gaugeMetric.LastValue.Value, gaugeMetric.Exemplars); + gauge.DataPoints.Add(dataPoint); + otlpMetric.Gauge = gauge; + break; + } - // TODO: Do TagEnumerationState thing. - foreach (var attribute in metric.Attributes) - { - dataPoint.Attributes.Add(attribute.ToOtlpAttribute()); - } + case MetricType.DoubleGauge: + { + var gaugeMetric = metric as IGaugeMetric; + var gauge = new OtlpMetrics.Gauge(); + var dataPoint = metric.ToNumberDataPoint(gaugeMetric.LastValue.Value, gaugeMetric.Exemplars); + gauge.DataPoints.Add(dataPoint); + otlpMetric.Gauge = gauge; + break; + } - foreach (var exemplar in histogramMetric.Exemplars) - { - dataPoint.Exemplars.Add(exemplar.ToOtlpExemplar()); - } + case MetricType.Histogram: + { + var histogramMetric = metric as IHistogramMetric; + var histogram = new OtlpMetrics.Histogram + { + AggregationTemporality = histogramMetric.IsDeltaTemporality + ? OtlpMetrics.AggregationTemporality.Delta + : OtlpMetrics.AggregationTemporality.Cumulative, + }; + + var dataPoint = new OtlpMetrics.HistogramDataPoint + { + StartTimeUnixNano = (ulong)metric.StartTimeExclusive.ToUnixTimeNanoseconds(), + TimeUnixNano = (ulong)metric.EndTimeInclusive.ToUnixTimeNanoseconds(), + Count = (ulong)histogramMetric.PopulationCount, + Sum = histogramMetric.PopulationSum, + }; + + foreach (var bucket in histogramMetric.Buckets) + { + dataPoint.BucketCounts.Add((ulong)bucket.Count); + + // TODO: Verify how to handle the bounds. We've modeled things with + // a LowBoundary and HighBoundary. OTLP data model has modeled this + // differently: https://github.com/open-telemetry/opentelemetry-proto/blob/bacfe08d84e21fb2a779e302d12e8dfeb67e7b86/opentelemetry/proto/metrics/v1/metrics.proto#L554-L568 + dataPoint.ExplicitBounds.Add(bucket.HighBoundary); + } + + // TODO: Do TagEnumerationState thing. + foreach (var attribute in metric.Attributes) + { + dataPoint.Attributes.Add(attribute.ToOtlpAttribute()); + } + + foreach (var exemplar in histogramMetric.Exemplars) + { + dataPoint.Exemplars.Add(exemplar.ToOtlpExemplar()); + } + + otlpMetric.Histogram = histogram; + break; + } - otlpMetric.Histogram = histogram; + case MetricType.Summary: + { + var summaryMetric = metric as ISummaryMetric; + var summary = new OtlpMetrics.Summary(); + + var dataPoint = new OtlpMetrics.SummaryDataPoint + { + StartTimeUnixNano = (ulong)metric.StartTimeExclusive.ToUnixTimeNanoseconds(), + TimeUnixNano = (ulong)metric.EndTimeInclusive.ToUnixTimeNanoseconds(), + Count = (ulong)summaryMetric.PopulationCount, + Sum = summaryMetric.PopulationSum, + }; + + // TODO: Do TagEnumerationState thing. + foreach (var attribute in metric.Attributes) + { + dataPoint.Attributes.Add(attribute.ToOtlpAttribute()); + } + + foreach (var quantile in summaryMetric.Quantiles) + { + var quantileValue = new OtlpMetrics.SummaryDataPoint.Types.ValueAtQuantile + { + Quantile = quantile.Quantile, + Value = quantile.Value, + }; + dataPoint.QuantileValues.Add(quantileValue); + } + + otlpMetric.Summary = summary; + break; + } } return otlpMetric; diff --git a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs index 675acb6c55e..c2ddc69fd06 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/PrometheusExporterExtensions.cs @@ -50,16 +50,14 @@ public static void WriteMetricsCollection(this PrometheusExporter exporter, Stre .WithName(metric.Name) .WithDescription(metric.Name); - if (metric is ISumMetric sumMetric) + // TODO: Use switch case for higher perf. + if (metric.MetricType == MetricType.LongSum) { - if (sumMetric.Sum.Value is double doubleSum) - { - WriteSum(writer, builder, metric.Attributes, doubleSum); - } - else if (sumMetric.Sum.Value is long longSum) - { - WriteSum(writer, builder, metric.Attributes, longSum); - } + WriteSum(writer, builder, metric.Attributes, (metric as ISumMetricLong).LongSum); + } + else if (metric.MetricType == MetricType.DoubleSum) + { + WriteSum(writer, builder, metric.Attributes, (metric as ISumMetricDouble).DoubleSum); } } } diff --git a/src/OpenTelemetry/Metrics/AggregatorStore.cs b/src/OpenTelemetry/Metrics/AggregatorStore.cs index 20aa4c8760e..e749e13fdc0 100644 --- a/src/OpenTelemetry/Metrics/AggregatorStore.cs +++ b/src/OpenTelemetry/Metrics/AggregatorStore.cs @@ -51,9 +51,20 @@ internal IAggregator[] MapToMetrics(string[] seqKey, object[] seqVal) var dt = DateTimeOffset.UtcNow; // TODO: Need to map each instrument to metrics (based on View API) - if (this.instrument.GetType().Name.Contains("Counter")) + // TODO: move most of this logic out of hotpath, and to MeterProvider's + // InstrumentPublished event, which is once per instrument creation. + + if (this.instrument.GetType() == typeof(Counter) + || this.instrument.GetType() == typeof(Counter) + || this.instrument.GetType() == typeof(Counter) + || this.instrument.GetType() == typeof(Counter)) + { + aggregators.Add(new SumMetricAggregatorLong(this.instrument.Name, this.instrument.Description, this.instrument.Unit, this.instrument.Meter, dt, tags)); + } + else if (this.instrument.GetType() == typeof(Counter) + || this.instrument.GetType() == typeof(Counter)) { - aggregators.Add(new SumMetricAggregator(this.instrument.Name, this.instrument.Description, this.instrument.Unit, this.instrument.Meter, dt, tags)); + aggregators.Add(new SumMetricAggregatorDouble(this.instrument.Name, this.instrument.Description, this.instrument.Unit, this.instrument.Meter, dt, tags)); } else if (this.instrument.GetType().Name.Contains("Gauge")) { diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/GaugeMetricAggregator.cs b/src/OpenTelemetry/Metrics/MetricAggregators/GaugeMetricAggregator.cs index 67eadd8286f..2a7848a2b54 100644 --- a/src/OpenTelemetry/Metrics/MetricAggregators/GaugeMetricAggregator.cs +++ b/src/OpenTelemetry/Metrics/MetricAggregators/GaugeMetricAggregator.cs @@ -33,6 +33,9 @@ internal GaugeMetricAggregator(string name, string description, string unit, Met this.Meter = meter; this.StartTimeExclusive = startTimeExclusive; this.Attributes = attributes; + + // TODO: Split this class into two or leverage generic + this.MetricType = MetricType.LongGauge; } public string Name { get; private set; } @@ -53,6 +56,8 @@ internal GaugeMetricAggregator(string name, string description, string unit, Met public IDataValue LastValue => this.value; + public MetricType MetricType { get; private set; } + public void Update(T value) where T : struct { diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs b/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs index 6fc3b556757..0f1c79aec4c 100644 --- a/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs +++ b/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs @@ -50,6 +50,7 @@ internal HistogramMetricAggregator(string name, string description, string unit, this.boundaries = boundaries; this.buckets = this.InitializeBucket(boundaries); + this.MetricType = MetricType.Summary; } public string Name { get; private set; } @@ -74,6 +75,8 @@ internal HistogramMetricAggregator(string name, string description, string unit, public double PopulationSum { get; private set; } + public MetricType MetricType { get; private set; } + public IEnumerable Buckets => this.buckets; public void Update(T value) diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/IMetric.cs b/src/OpenTelemetry/Metrics/MetricAggregators/IMetric.cs index f77a1599859..079172dd6ce 100644 --- a/src/OpenTelemetry/Metrics/MetricAggregators/IMetric.cs +++ b/src/OpenTelemetry/Metrics/MetricAggregators/IMetric.cs @@ -36,6 +36,8 @@ public interface IMetric KeyValuePair[] Attributes { get; } + MetricType MetricType { get; } + string ToDisplayString(); } } diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/ISumMetric.cs b/src/OpenTelemetry/Metrics/MetricAggregators/ISumMetric.cs index 4fc7c55fff4..36d2f4f648c 100644 --- a/src/OpenTelemetry/Metrics/MetricAggregators/ISumMetric.cs +++ b/src/OpenTelemetry/Metrics/MetricAggregators/ISumMetric.cs @@ -25,7 +25,5 @@ public interface ISumMetric : IMetric bool IsMonotonic { get; } IEnumerable Exemplars { get; } - - IDataValue Sum { get; } } } diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/ISumMetricDouble.cs b/src/OpenTelemetry/Metrics/MetricAggregators/ISumMetricDouble.cs new file mode 100644 index 00000000000..5b04e1d9db8 --- /dev/null +++ b/src/OpenTelemetry/Metrics/MetricAggregators/ISumMetricDouble.cs @@ -0,0 +1,23 @@ +// +// 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 +{ + public interface ISumMetricDouble : ISumMetric + { + double DoubleSum { get; } + } +} diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/ISumMetricLong.cs b/src/OpenTelemetry/Metrics/MetricAggregators/ISumMetricLong.cs new file mode 100644 index 00000000000..033956fdf83 --- /dev/null +++ b/src/OpenTelemetry/Metrics/MetricAggregators/ISumMetricLong.cs @@ -0,0 +1,23 @@ +// +// 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 +{ + public interface ISumMetricLong : ISumMetric + { + long LongSum { get; } + } +} diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/MetricType.cs b/src/OpenTelemetry/Metrics/MetricAggregators/MetricType.cs new file mode 100644 index 00000000000..c5af3b5d6ce --- /dev/null +++ b/src/OpenTelemetry/Metrics/MetricAggregators/MetricType.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. +// + +namespace OpenTelemetry.Metrics +{ + public enum MetricType + { + /// + /// Sum of Long type. + /// + LongSum = 0, + + /// + /// Sum of Double type. + /// + DoubleSum = 1, + + /// + /// Gauge of Long type. + /// + LongGauge = 2, + + /// + /// Gauge of Double type. + /// + DoubleGauge = 3, + + /// + /// Histogram. + /// + Histogram = 4, + + /// + /// Summary. + /// + Summary = 5, + } +} diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregator.cs b/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregator.cs deleted file mode 100644 index eea048b5195..00000000000 --- a/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregator.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.Collections.Generic; -using System.Diagnostics.Metrics; - -namespace OpenTelemetry.Metrics -{ - internal class SumMetricAggregator : ISumMetric, IAggregator - { - private readonly object lockUpdate = new object(); - private Type valueType; - private long sumLong = 0; - private double sumDouble = 0; - - internal SumMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair[] attributes) - { - this.Name = name; - this.Description = description; - this.Unit = unit; - this.Meter = meter; - this.StartTimeExclusive = startTimeExclusive; - this.Attributes = attributes; - this.IsMonotonic = true; - } - - public string Name { get; private set; } - - public string Description { get; private set; } - - public string Unit { get; private set; } - - public Meter Meter { get; private set; } - - public DateTimeOffset StartTimeExclusive { get; private set; } - - public DateTimeOffset EndTimeInclusive { get; private set; } - - public KeyValuePair[] Attributes { get; private set; } - - public bool IsDeltaTemporality { get; private set; } - - public bool IsMonotonic { get; } - - public IEnumerable Exemplars { get; private set; } = new List(); - - public IDataValue Sum - { - get - { - if (this.valueType == typeof(long)) - { - return new DataValue(this.sumLong); - } - else if (this.valueType == typeof(double)) - { - return new DataValue(this.sumDouble); - } - - throw new Exception("Unsupported Type"); - } - } - - public void Update(T value) - where T : struct - { - lock (this.lockUpdate) - { - if (typeof(T) == typeof(long)) - { - this.valueType = typeof(T); - var val = (long)(object)value; - if (val < 0) - { - // TODO: log? - // Also, this validation can be done in earlier stage. - } - else - { - this.sumLong += val; - } - } - else if (typeof(T) == typeof(double)) - { - this.valueType = typeof(T); - var val = (double)(object)value; - if (val < 0) - { - // TODO: log? - // Also, this validation can be done in earlier stage. - } - else - { - this.sumDouble += val; - } - } - else - { - throw new Exception("Unsupported Type"); - } - } - } - - public IMetric Collect(DateTimeOffset dt, bool isDelta) - { - var cloneItem = new SumMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes); - - lock (this.lockUpdate) - { - cloneItem.Exemplars = this.Exemplars; - cloneItem.EndTimeInclusive = dt; - cloneItem.valueType = this.valueType; - cloneItem.sumLong = this.sumLong; - cloneItem.sumDouble = this.sumDouble; - cloneItem.IsDeltaTemporality = isDelta; - - if (isDelta) - { - this.StartTimeExclusive = dt; - this.sumLong = 0; - this.sumDouble = 0; - } - } - - return cloneItem; - } - - public string ToDisplayString() - { - return $"Sum={this.Sum.Value}"; - } - } -} diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregatorDouble.cs b/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregatorDouble.cs new file mode 100644 index 00000000000..d266973c3ad --- /dev/null +++ b/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregatorDouble.cs @@ -0,0 +1,93 @@ +// +// 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.Metrics; + +namespace OpenTelemetry.Metrics +{ + internal class SumMetricAggregatorDouble : IAggregator + { + private readonly object lockUpdate = new object(); + private double sumDouble = 0; + private SumMetricDouble sumMetricDouble; + private DateTimeOffset startTimeExclusive; + + internal SumMetricAggregatorDouble(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair[] attributes) + { + this.startTimeExclusive = startTimeExclusive; + this.sumMetricDouble = new SumMetricDouble(name, description, unit, meter, startTimeExclusive, attributes); + } + + public void Update(T value) + where T : struct + { + // TODO: Replace Lock with + // TryAdd..{Spin..TryAdd..Repeat} if "lost race to another thread" + lock (this.lockUpdate) + { + if (typeof(T) == typeof(double)) + { + // TODO: Confirm this doesn't cause boxing. + var val = (double)(object)value; + if (val < 0) + { + // TODO: log? + // Also, this validation can be done in earlier stage. + } + else + { + this.sumDouble += val; + } + } + else + { + throw new Exception("Unsupported Type"); + } + } + } + + public IMetric Collect(DateTimeOffset dt, bool isDelta) + { + lock (this.lockUpdate) + { + this.sumMetricDouble.StartTimeExclusive = this.startTimeExclusive; + this.sumMetricDouble.EndTimeInclusive = dt; + this.sumMetricDouble.DoubleSum = this.sumDouble; + this.sumMetricDouble.IsDeltaTemporality = isDelta; + if (isDelta) + { + this.startTimeExclusive = dt; + this.sumDouble = 0; + } + } + + // TODO: Confirm that this approach of + // re-using the same instance is correct. + // This avoids allocating a new instance. + // It is read only for Exporters, + // and also there is no parallel + // Collect allowed. + return this.sumMetricDouble; + } + + public string ToDisplayString() + { + return $"Sum={this.sumDouble}"; + } + } +} diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregatorLong.cs b/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregatorLong.cs new file mode 100644 index 00000000000..048cbc93a2c --- /dev/null +++ b/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricAggregatorLong.cs @@ -0,0 +1,92 @@ +// +// 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.Metrics; + +namespace OpenTelemetry.Metrics +{ + internal class SumMetricAggregatorLong : IAggregator + { + private readonly object lockUpdate = new object(); + private long sumLong = 0; + private SumMetricLong sumMetricLong; + private DateTimeOffset startTimeExclusive; + + internal SumMetricAggregatorLong(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair[] attributes) + { + this.startTimeExclusive = startTimeExclusive; + this.sumMetricLong = new SumMetricLong(name, description, unit, meter, startTimeExclusive, attributes); + } + + public void Update(T value) + where T : struct + { + // TODO: Replace Lock with Interlocked.Add + lock (this.lockUpdate) + { + if (typeof(T) == typeof(long)) + { + // TODO: Confirm this doesn't cause boxing. + var val = (long)(object)value; + if (val < 0) + { + // TODO: log? + // Also, this validation can be done in earlier stage. + } + else + { + this.sumLong += val; + } + } + else + { + throw new Exception("Unsupported Type"); + } + } + } + + public IMetric Collect(DateTimeOffset dt, bool isDelta) + { + lock (this.lockUpdate) + { + this.sumMetricLong.StartTimeExclusive = this.startTimeExclusive; + this.sumMetricLong.EndTimeInclusive = dt; + this.sumMetricLong.LongSum = this.sumLong; + this.sumMetricLong.IsDeltaTemporality = isDelta; + if (isDelta) + { + this.startTimeExclusive = dt; + this.sumLong = 0; + } + } + + // TODO: Confirm that this approach of + // re-using the same instance is correct. + // This avoids allocating a new instance. + // It is read only for Exporters, + // and also there is no parallel + // Collect allowed. + return this.sumMetricLong; + } + + public string ToDisplayString() + { + return $"Sum={this.sumLong}"; + } + } +} diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricDouble.cs b/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricDouble.cs new file mode 100644 index 00000000000..3b5486ad6d3 --- /dev/null +++ b/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricDouble.cs @@ -0,0 +1,66 @@ +// +// 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.Metrics; + +namespace OpenTelemetry.Metrics +{ + internal class SumMetricDouble : ISumMetricDouble + { + internal SumMetricDouble(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair[] attributes) + { + this.Name = name; + this.Description = description; + this.Unit = unit; + this.Meter = meter; + this.StartTimeExclusive = startTimeExclusive; + this.Attributes = attributes; + this.IsMonotonic = true; + this.MetricType = MetricType.DoubleSum; + } + + public string Name { get; private set; } + + public string Description { get; private set; } + + public string Unit { get; private set; } + + public Meter Meter { get; private set; } + + public DateTimeOffset StartTimeExclusive { get; internal set; } + + public DateTimeOffset EndTimeInclusive { get; internal set; } + + public KeyValuePair[] Attributes { get; private set; } + + public bool IsDeltaTemporality { get; internal set; } + + public bool IsMonotonic { get; } + + public IEnumerable Exemplars { get; private set; } = new List(); + + public double DoubleSum { get; internal set; } + + public MetricType MetricType { get; private set; } + + public string ToDisplayString() + { + return $"Sum={this.DoubleSum}"; + } + } +} diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricLong.cs b/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricLong.cs new file mode 100644 index 00000000000..1398cb694af --- /dev/null +++ b/src/OpenTelemetry/Metrics/MetricAggregators/SumMetricLong.cs @@ -0,0 +1,66 @@ +// +// 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.Metrics; + +namespace OpenTelemetry.Metrics +{ + internal class SumMetricLong : ISumMetricLong + { + internal SumMetricLong(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair[] attributes) + { + this.Name = name; + this.Description = description; + this.Unit = unit; + this.Meter = meter; + this.StartTimeExclusive = startTimeExclusive; + this.Attributes = attributes; + this.IsMonotonic = true; + this.MetricType = MetricType.LongSum; + } + + public string Name { get; private set; } + + public string Description { get; private set; } + + public string Unit { get; private set; } + + public Meter Meter { get; private set; } + + public DateTimeOffset StartTimeExclusive { get; internal set; } + + public DateTimeOffset EndTimeInclusive { get; internal set; } + + public KeyValuePair[] Attributes { get; private set; } + + public bool IsDeltaTemporality { get; internal set; } + + public bool IsMonotonic { get; } + + public IEnumerable Exemplars { get; private set; } = new List(); + + public long LongSum { get; internal set; } + + public MetricType MetricType { get; private set; } + + public string ToDisplayString() + { + return $"Sum={this.LongSum}"; + } + } +} diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/SummaryMetricAggregator.cs b/src/OpenTelemetry/Metrics/MetricAggregators/SummaryMetricAggregator.cs index c06874a3c11..0e7fe400636 100644 --- a/src/OpenTelemetry/Metrics/MetricAggregators/SummaryMetricAggregator.cs +++ b/src/OpenTelemetry/Metrics/MetricAggregators/SummaryMetricAggregator.cs @@ -35,6 +35,7 @@ internal SummaryMetricAggregator(string name, string description, string unit, M this.StartTimeExclusive = startTimeExclusive; this.Attributes = attributes; this.IsMonotonic = isMonotonic; + this.MetricType = MetricType.Summary; } public string Name { get; private set; } @@ -59,6 +60,8 @@ internal SummaryMetricAggregator(string name, string description, string unit, M public IEnumerable Quantiles => this.quantiles; + public MetricType MetricType { get; private set; } + public void Update(T value) where T : struct { diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs index 8dab7611f68..10f0e6b0185 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs @@ -64,9 +64,10 @@ void ProcessExport(Batch batch) } } + var processor = new PullMetricProcessor(metricExporter, true); this.meterProvider = Sdk.CreateMeterProviderBuilder() .AddAspNetCoreInstrumentation() - .AddMetricProcessor(new PushMetricProcessor(exporter: metricExporter, exportIntervalMs: 10, isDelta: true)) + .AddMetricProcessor(processor) .Build(); using (var client = this.factory.CreateClient()) @@ -75,22 +76,20 @@ void ProcessExport(Batch batch) response.EnsureSuccessStatusCode(); } - // Wait for at least two exporter invocations - WaitForMetricItems(metricItems, 2); + // Invokes the TestExporter which will invoke ProcessExport + processor.PullRequest(); this.meterProvider.Dispose(); - // There should be more than one result here since we waited for at least two exporter invocations. - // The exporter continues to receive a metric even if it has not changed since the last export. var requestMetrics = metricItems .SelectMany(item => item.Metrics.Where(metric => metric.Name == "http.server.request_count")) .ToArray(); - Assert.True(requestMetrics.Length > 1); + Assert.True(requestMetrics.Length == 1); - var metric = requestMetrics[0] as ISumMetric; + var metric = requestMetrics[0] as ISumMetricLong; Assert.NotNull(metric); - Assert.Equal(1L, metric.Sum.Value); + Assert.Equal(1L, metric.LongSum); var method = new KeyValuePair(SemanticConventions.AttributeHttpMethod, "GET"); var scheme = new KeyValuePair(SemanticConventions.AttributeHttpScheme, "http"); @@ -101,13 +100,6 @@ void ProcessExport(Batch batch) Assert.Contains(statusCode, metric.Attributes); Assert.Contains(flavor, metric.Attributes); Assert.Equal(4, metric.Attributes.Length); - - for (var i = 1; i < requestMetrics.Length; ++i) - { - metric = requestMetrics[i] as ISumMetric; - Assert.NotNull(metric); - Assert.Equal(0L, metric.Sum.Value); - } } public void Dispose() diff --git a/test/OpenTelemetry.Tests/Metrics/MetricAPITest.cs b/test/OpenTelemetry.Tests/Metrics/MetricAPITest.cs index 1f0d8424d6a..425576bd400 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricAPITest.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricAPITest.cs @@ -16,18 +16,26 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Metrics; using System.Threading; using OpenTelemetry.Tests; using Xunit; +using Xunit.Abstractions; namespace OpenTelemetry.Metrics.Tests { public class MetricApiTest { - private static int numberOfThreads = 10; + private static int numberOfThreads = Environment.ProcessorCount; private static long deltaValueUpdatedByEachCall = 10; - private static int numberOfMetricUpdateByEachThread = 1000000; + private static int numberOfMetricUpdateByEachThread = 100000; + private readonly ITestOutputHelper output; + + public MetricApiTest(ITestOutputHelper output) + { + this.output = output; + } [Fact] public void SimpleTest() @@ -42,11 +50,13 @@ void ProcessExport(Batch batch) } } + var pullProcessor = new PullMetricProcessor(metricExporter, true); + var meter = new Meter("TestMeter"); var counterLong = meter.CreateCounter("mycounter"); var meterProvider = Sdk.CreateMeterProviderBuilder() .AddSource("TestMeter") - .AddMetricProcessor(new PushMetricProcessor(metricExporter, 100, isDelta: true)) + .AddMetricProcessor(pullProcessor) .Build(); // setup args to threads. @@ -69,6 +79,9 @@ void ProcessExport(Batch batch) // Block until all threads started. mreToEnsureAllThreadsStarted.WaitOne(); + Stopwatch sw = new Stopwatch(); + sw.Start(); + // unblock all the threads. // (i.e let them start counter.Add) mreToBlockUpdateThreads.Set(); @@ -79,11 +92,11 @@ void ProcessExport(Batch batch) t[i].Join(); } - meterProvider.Dispose(); + var timeTakenInMilliseconds = sw.ElapsedMilliseconds; + this.output.WriteLine($"Took {timeTakenInMilliseconds} msecs. Total threads: {numberOfThreads}, each thread doing {numberOfMetricUpdateByEachThread} recordings."); - // TODO: Once Dispose does flush, we may not need this - // unknown sleep below. - Thread.Sleep(1000); + meterProvider.Dispose(); + pullProcessor.PullRequest(); long sumReceived = 0; foreach (var metricItem in metricItems) @@ -91,7 +104,7 @@ void ProcessExport(Batch batch) var metrics = metricItem.Metrics; foreach (var metric in metrics) { - sumReceived += (long)(metric as ISumMetric).Sum.Value; + sumReceived += (metric as ISumMetricLong).LongSum; } } From c84af7c80790d52fc731b41837a9e14d79b07e88 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Wed, 4 Aug 2021 09:57:09 -0700 Subject: [PATCH 21/45] OTLP exporter refactor - pulling common code into BaseOtlpExporter class (#2220) --- .../BaseOtlpExporter.cs | 83 ++++++++++++ .../Implementation/ResourceExtensions.cs | 54 ++++++++ .../OtlpExporterOptionsGrpcExtensions.cs | 82 ++++++++++++ .../OtlpMetricsExporter.cs | 125 +----------------- .../OtlpTraceExporter.cs | 125 +----------------- .../OtlpExporterOptionsGrpcExtensionsTests.cs | 67 ++++++++++ .../OtlpMetricsExporterTests.cs | 27 ++-- .../OtlpTraceExporterTests.cs | 73 ++-------- 8 files changed, 326 insertions(+), 310 deletions(-) create mode 100644 src/OpenTelemetry.Exporter.OpenTelemetryProtocol/BaseOtlpExporter.cs create mode 100644 src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs create mode 100644 src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsGrpcExtensions.cs create mode 100644 test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsGrpcExtensionsTests.cs diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/BaseOtlpExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/BaseOtlpExporter.cs new file mode 100644 index 00000000000..aad3503b2a2 --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/BaseOtlpExporter.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 System; +using System.Threading.Tasks; +using Grpc.Core; +using OpenTelemetry.Exporter.OpenTelemetryProtocol; +#if NETSTANDARD2_1 +using Grpc.Net.Client; +#endif +using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; +using OtlpResource = Opentelemetry.Proto.Resource.V1; + +namespace OpenTelemetry.Exporter +{ + /// + /// Implements exporter that exports telemetry objects over OTLP/gRPC. + /// + /// The type of telemetry object to be exported. + public abstract class BaseOtlpExporter : BaseExporter + where T : class + { + private OtlpResource.Resource processResource; + + /// + /// Initializes a new instance of the class. + /// + /// The for configuring the exporter. + protected BaseOtlpExporter(OtlpExporterOptions options) + { + this.Options = options ?? throw new ArgumentNullException(nameof(options)); + this.Headers = options.GetMetadataFromHeaders(); + if (this.Options.TimeoutMilliseconds <= 0) + { + throw new ArgumentException("Timeout value provided is not a positive number.", nameof(this.Options.TimeoutMilliseconds)); + } + } + + internal OtlpResource.Resource ProcessResource => this.processResource ??= this.ParentProvider.GetResource().ToOtlpResource(); + +#if NETSTANDARD2_1 + internal GrpcChannel Channel { get; set; } +#else + internal Channel Channel { get; set; } +#endif + + internal OtlpExporterOptions Options { get; } + + internal Metadata Headers { get; } + + /// + protected override bool OnShutdown(int timeoutMilliseconds) + { + if (this.Channel == null) + { + return true; + } + + if (timeoutMilliseconds == -1) + { + this.Channel.ShutdownAsync().Wait(); + return true; + } + else + { + return Task.WaitAny(new Task[] { this.Channel.ShutdownAsync(), Task.Delay(timeoutMilliseconds) }) == 0; + } + } + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs new file mode 100644 index 00000000000..8c6cd63d73e --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.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 System.Collections.Generic; +using System.Linq; +using OpenTelemetry.Resources; +using OtlpCommon = Opentelemetry.Proto.Common.V1; +using OtlpResource = Opentelemetry.Proto.Resource.V1; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation +{ + internal static class ResourceExtensions + { + public static OtlpResource.Resource ToOtlpResource(this Resource resource) + { + var processResource = new OtlpResource.Resource(); + + foreach (KeyValuePair attribute in resource.Attributes) + { + var oltpAttribute = attribute.ToOtlpAttribute(); + if (oltpAttribute != null) + { + processResource.Attributes.Add(oltpAttribute); + } + } + + if (!processResource.Attributes.Any(kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName)) + { + var serviceName = (string)ResourceBuilder.CreateDefault().Build().Attributes.FirstOrDefault( + kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName).Value; + processResource.Attributes.Add(new OtlpCommon.KeyValue + { + Key = ResourceSemanticConventions.AttributeServiceName, + Value = new OtlpCommon.AnyValue { StringValue = serviceName }, + }); + } + + return processResource; + } + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsGrpcExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsGrpcExtensions.cs new file mode 100644 index 00000000000..f3750e654db --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsGrpcExtensions.cs @@ -0,0 +1,82 @@ +// +// 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 Grpc.Core; +#if NETSTANDARD2_1 +using Grpc.Net.Client; +#endif + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol +{ + internal static class OtlpExporterOptionsGrpcExtensions + { +#if NETSTANDARD2_1 + public static GrpcChannel CreateChannel(this OtlpExporterOptions options) +#else + public static Channel CreateChannel(this OtlpExporterOptions options) +#endif + { + if (options.Endpoint.Scheme != Uri.UriSchemeHttp && options.Endpoint.Scheme != Uri.UriSchemeHttps) + { + throw new NotSupportedException($"Endpoint URI scheme ({options.Endpoint.Scheme}) is not supported. Currently only \"http\" and \"https\" are supported."); + } + +#if NETSTANDARD2_1 + return GrpcChannel.ForAddress(options.Endpoint); +#else + ChannelCredentials channelCredentials; + if (options.Endpoint.Scheme == Uri.UriSchemeHttps) + { + channelCredentials = new SslCredentials(); + } + else + { + channelCredentials = ChannelCredentials.Insecure; + } + + return new Channel(options.Endpoint.Authority, channelCredentials); +#endif + } + + public static Metadata GetMetadataFromHeaders(this OtlpExporterOptions options) + { + var headers = options.Headers; + var metadata = new Metadata(); + if (!string.IsNullOrEmpty(headers)) + { + Array.ForEach( + headers.Split(','), + (pair) => + { + // Specify the maximum number of substrings to return to 2 + // This treats everything that follows the first `=` in the string as the value to be added for the metadata key + var keyValueData = pair.Split(new char[] { '=' }, 2); + if (keyValueData.Length != 2) + { + throw new ArgumentException("Headers provided in an invalid format."); + } + + var key = keyValueData[0].Trim(); + var value = keyValueData[1].Trim(); + metadata.Add(key, value); + }); + } + + return metadata; + } + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricsExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricsExporter.cs index 37aa8afcf71..46d87418d40 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricsExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpMetricsExporter.cs @@ -15,19 +15,11 @@ // using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Grpc.Core; -#if NETSTANDARD2_1 -using Grpc.Net.Client; -#endif +using OpenTelemetry.Exporter.OpenTelemetryProtocol; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Metrics; -using OpenTelemetry.Resources; using OtlpCollector = Opentelemetry.Proto.Collector.Metrics.V1; -using OtlpCommon = Opentelemetry.Proto.Common.V1; -using OtlpResource = Opentelemetry.Proto.Resource.V1; namespace OpenTelemetry.Exporter { @@ -35,16 +27,9 @@ namespace OpenTelemetry.Exporter /// Exporter consuming and exporting the data using /// the OpenTelemetry protocol (OTLP). /// - public class OtlpMetricsExporter : BaseExporter + public class OtlpMetricsExporter : BaseOtlpExporter { - private readonly OtlpExporterOptions options; -#if NETSTANDARD2_1 - private readonly GrpcChannel channel; -#else - private readonly Channel channel; -#endif private readonly OtlpCollector.MetricsService.IMetricsServiceClient metricsClient; - private readonly Metadata headers; /// /// Initializes a new instance of the class. @@ -61,65 +46,33 @@ public OtlpMetricsExporter(OtlpExporterOptions options) /// Configuration options for the exporter. /// . internal OtlpMetricsExporter(OtlpExporterOptions options, OtlpCollector.MetricsService.IMetricsServiceClient metricsServiceClient = null) + : base(options) { - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.headers = GetMetadataFromHeaders(options.Headers); - if (this.options.TimeoutMilliseconds <= 0) - { - throw new ArgumentException("Timeout value provided is not a positive number.", nameof(this.options.TimeoutMilliseconds)); - } - if (metricsServiceClient != null) { this.metricsClient = metricsServiceClient; } else { - if (options.Endpoint.Scheme != Uri.UriSchemeHttp && options.Endpoint.Scheme != Uri.UriSchemeHttps) - { - throw new NotSupportedException($"Endpoint URI scheme ({options.Endpoint.Scheme}) is not supported. Currently only \"http\" and \"https\" are supported."); - } - -#if NETSTANDARD2_1 - this.channel = GrpcChannel.ForAddress(options.Endpoint); -#else - ChannelCredentials channelCredentials; - if (options.Endpoint.Scheme == Uri.UriSchemeHttps) - { - channelCredentials = new SslCredentials(); - } - else - { - channelCredentials = ChannelCredentials.Insecure; - } - - this.channel = new Channel(options.Endpoint.Authority, channelCredentials); -#endif - this.metricsClient = new OtlpCollector.MetricsService.MetricsServiceClient(this.channel); + this.Channel = options.CreateChannel(); + this.metricsClient = new OtlpCollector.MetricsService.MetricsServiceClient(this.Channel); } } - internal OtlpResource.Resource ProcessResource { get; private set; } - /// public override ExportResult Export(in Batch batch) { - if (this.ProcessResource == null) - { - this.SetResource(this.ParentProvider.GetResource()); - } - // Prevents the exporter's gRPC and HTTP operations from being instrumented. using var scope = SuppressInstrumentationScope.Begin(); var request = new OtlpCollector.ExportMetricsServiceRequest(); request.AddBatch(this.ProcessResource, batch); - var deadline = DateTime.UtcNow.AddMilliseconds(this.options.TimeoutMilliseconds); + var deadline = DateTime.UtcNow.AddMilliseconds(this.Options.TimeoutMilliseconds); try { - this.metricsClient.Export(request, headers: this.headers, deadline: deadline); + this.metricsClient.Export(request, headers: this.Headers, deadline: deadline); } catch (RpcException ex) { @@ -138,69 +91,5 @@ public override ExportResult Export(in Batch batch) return ExportResult.Success; } - - internal void SetResource(Resource resource) - { - OtlpResource.Resource processResource = new OtlpResource.Resource(); - - foreach (KeyValuePair attribute in resource.Attributes) - { - var oltpAttribute = attribute.ToOtlpAttribute(); - if (oltpAttribute != null) - { - processResource.Attributes.Add(oltpAttribute); - } - } - - if (!processResource.Attributes.Any(kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName)) - { - var serviceName = (string)this.ParentProvider.GetDefaultResource().Attributes.Where( - kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName).FirstOrDefault().Value; - processResource.Attributes.Add(new OtlpCommon.KeyValue - { - Key = ResourceSemanticConventions.AttributeServiceName, - Value = new OtlpCommon.AnyValue { StringValue = serviceName }, - }); - } - - this.ProcessResource = processResource; - } - - /// - protected override bool OnShutdown(int timeoutMilliseconds) - { - if (this.channel == null) - { - return true; - } - - return Task.WaitAny(new Task[] { this.channel.ShutdownAsync(), Task.Delay(timeoutMilliseconds) }) == 0; - } - - private static Metadata GetMetadataFromHeaders(string headers) - { - var metadata = new Metadata(); - if (!string.IsNullOrEmpty(headers)) - { - Array.ForEach( - headers.Split(','), - (pair) => - { - // Specify the maximum number of substrings to return to 2 - // This treats everything that follows the first `=` in the string as the value to be added for the metadata key - var keyValueData = pair.Split(new char[] { '=' }, 2); - if (keyValueData.Length != 2) - { - throw new ArgumentException("Headers provided in an invalid format."); - } - - var key = keyValueData[0].Trim(); - var value = keyValueData[1].Trim(); - metadata.Add(key, value); - }); - } - - return metadata; - } } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs index 507688c1730..086216008ce 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpTraceExporter.cs @@ -15,19 +15,11 @@ // using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; using Grpc.Core; -#if NETSTANDARD2_1 -using Grpc.Net.Client; -#endif +using OpenTelemetry.Exporter.OpenTelemetryProtocol; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; -using OpenTelemetry.Resources; using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1; -using OtlpCommon = Opentelemetry.Proto.Common.V1; -using OtlpResource = Opentelemetry.Proto.Resource.V1; namespace OpenTelemetry.Exporter { @@ -35,16 +27,9 @@ namespace OpenTelemetry.Exporter /// Exporter consuming and exporting the data using /// the OpenTelemetry protocol (OTLP). /// - public class OtlpTraceExporter : BaseExporter + public class OtlpTraceExporter : BaseOtlpExporter { - private readonly OtlpExporterOptions options; -#if NETSTANDARD2_1 - private readonly GrpcChannel channel; -#else - private readonly Channel channel; -#endif private readonly OtlpCollector.TraceService.ITraceServiceClient traceClient; - private readonly Metadata headers; /// /// Initializes a new instance of the class. @@ -61,65 +46,33 @@ public OtlpTraceExporter(OtlpExporterOptions options) /// Configuration options for the exporter. /// . internal OtlpTraceExporter(OtlpExporterOptions options, OtlpCollector.TraceService.ITraceServiceClient traceServiceClient = null) + : base(options) { - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.headers = GetMetadataFromHeaders(options.Headers); - if (this.options.TimeoutMilliseconds <= 0) - { - throw new ArgumentException("Timeout value provided is not a positive number.", nameof(this.options.TimeoutMilliseconds)); - } - if (traceServiceClient != null) { this.traceClient = traceServiceClient; } else { - if (options.Endpoint.Scheme != Uri.UriSchemeHttp && options.Endpoint.Scheme != Uri.UriSchemeHttps) - { - throw new NotSupportedException($"Endpoint URI scheme ({options.Endpoint.Scheme}) is not supported. Currently only \"http\" and \"https\" are supported."); - } - -#if NETSTANDARD2_1 - this.channel = GrpcChannel.ForAddress(options.Endpoint); -#else - ChannelCredentials channelCredentials; - if (options.Endpoint.Scheme == Uri.UriSchemeHttps) - { - channelCredentials = new SslCredentials(); - } - else - { - channelCredentials = ChannelCredentials.Insecure; - } - - this.channel = new Channel(options.Endpoint.Authority, channelCredentials); -#endif - this.traceClient = new OtlpCollector.TraceService.TraceServiceClient(this.channel); + this.Channel = options.CreateChannel(); + this.traceClient = new OtlpCollector.TraceService.TraceServiceClient(this.Channel); } } - internal OtlpResource.Resource ProcessResource { get; private set; } - /// public override ExportResult Export(in Batch activityBatch) { - if (this.ProcessResource == null) - { - this.SetResource(this.ParentProvider.GetResource()); - } - // Prevents the exporter's gRPC and HTTP operations from being instrumented. using var scope = SuppressInstrumentationScope.Begin(); OtlpCollector.ExportTraceServiceRequest request = new OtlpCollector.ExportTraceServiceRequest(); request.AddBatch(this.ProcessResource, activityBatch); - var deadline = DateTime.UtcNow.AddMilliseconds(this.options.TimeoutMilliseconds); + var deadline = DateTime.UtcNow.AddMilliseconds(this.Options.TimeoutMilliseconds); try { - this.traceClient.Export(request, headers: this.headers, deadline: deadline); + this.traceClient.Export(request, headers: this.Headers, deadline: deadline); } catch (RpcException ex) { @@ -140,69 +93,5 @@ public override ExportResult Export(in Batch activityBatch) return ExportResult.Success; } - - internal void SetResource(Resource resource) - { - OtlpResource.Resource processResource = new OtlpResource.Resource(); - - foreach (KeyValuePair attribute in resource.Attributes) - { - var oltpAttribute = attribute.ToOtlpAttribute(); - if (oltpAttribute != null) - { - processResource.Attributes.Add(oltpAttribute); - } - } - - if (!processResource.Attributes.Any(kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName)) - { - var serviceName = (string)this.ParentProvider.GetDefaultResource().Attributes.Where( - kvp => kvp.Key == ResourceSemanticConventions.AttributeServiceName).FirstOrDefault().Value; - processResource.Attributes.Add(new OtlpCommon.KeyValue - { - Key = ResourceSemanticConventions.AttributeServiceName, - Value = new OtlpCommon.AnyValue { StringValue = serviceName }, - }); - } - - this.ProcessResource = processResource; - } - - /// - protected override bool OnShutdown(int timeoutMilliseconds) - { - if (this.channel == null) - { - return true; - } - - return Task.WaitAny(new Task[] { this.channel.ShutdownAsync(), Task.Delay(timeoutMilliseconds) }) == 0; - } - - private static Metadata GetMetadataFromHeaders(string headers) - { - var metadata = new Metadata(); - if (!string.IsNullOrEmpty(headers)) - { - Array.ForEach( - headers.Split(','), - (pair) => - { - // Specify the maximum number of substrings to return to 2 - // This treats everything that follows the first `=` in the string as the value to be added for the metadata key - var keyValueData = pair.Split(new char[] { '=' }, 2); - if (keyValueData.Length != 2) - { - throw new ArgumentException("Headers provided in an invalid format."); - } - - var key = keyValueData[0].Trim(); - var value = keyValueData[1].Trim(); - metadata.Add(key, value); - }); - } - - return metadata; - } } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsGrpcExtensionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsGrpcExtensionsTests.cs new file mode 100644 index 00000000000..c75384cb0aa --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsGrpcExtensionsTests.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. +// + +using System; +using Xunit; +using Xunit.Sdk; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests +{ + public class OtlpExporterOptionsGrpcExtensionsTests + { + [Theory] + [InlineData("key=value", new string[] { "key" }, new string[] { "value" })] + [InlineData("key1=value1,key2=value2", new string[] { "key1", "key2" }, new string[] { "value1", "value2" })] + [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) + public void GetMetadataFromHeadersWorksCorrectFormat(string headers, string[] keys, string[] values) + { + var options = new OtlpExporterOptions(); + options.Headers = headers; + var metadata = options.GetMetadataFromHeaders(); + + Assert.Equal(keys.Length, metadata.Count); + + for (int i = 0; i < keys.Length; i++) + { + Assert.Contains(metadata, entry => entry.Key == keys[i] && entry.Value == values[i]); + } + } + + [Theory] + [InlineData("headers")] + [InlineData("key,value")] + public void GetMetadataFromHeadersThrowsExceptionOnOnvalidFormat(string headers) + { + try + { + var options = new OtlpExporterOptions(); + options.Headers = headers; + var metadata = options.GetMetadataFromHeaders(); + } + catch (Exception ex) + { + Assert.IsType(ex); + Assert.Equal("Headers provided in an invalid format.", ex.Message); + return; + } + + throw new XunitException("GetMetadataFromHeaders did not throw an exception for invalid input headers"); + } + } +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs index 2c2eda9f717..1d6c286a993 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs @@ -36,25 +36,21 @@ public class OtlpMetricsExporterTests [Theory] [InlineData(true)] [InlineData(false)] - public void ToOtlpResourceMetricsTest(bool addResource) + public void ToOtlpResourceMetricsTest(bool includeServiceNameInResource) { using var exporter = new OtlpMetricsExporter( new OtlpExporterOptions(), new NoopMetricsServiceClient()); - if (addResource) + var resourceBuilder = ResourceBuilder.CreateEmpty(); + if (includeServiceNameInResource) { - exporter.SetResource( - ResourceBuilder.CreateEmpty().AddAttributes( - new List> - { - new KeyValuePair(ResourceSemanticConventions.AttributeServiceName, "service-name"), - new KeyValuePair(ResourceSemanticConventions.AttributeServiceNamespace, "ns1"), - }).Build()); - } - else - { - exporter.SetResource(Resource.Empty); + resourceBuilder.AddAttributes( + new List> + { + new KeyValuePair(ResourceSemanticConventions.AttributeServiceName, "service-name"), + new KeyValuePair(ResourceSemanticConventions.AttributeServiceNamespace, "ns1"), + }); } var tags = new KeyValuePair[] @@ -66,10 +62,13 @@ public void ToOtlpResourceMetricsTest(bool addResource) var processor = new PullMetricProcessor(new TestExporter(RunTest), true); using var provider = Sdk.CreateMeterProviderBuilder() + .SetResourceBuilder(resourceBuilder) .AddSource("TestMeter") .AddMetricProcessor(processor) .Build(); + exporter.ParentProvider = provider; + using var meter = new Meter("TestMeter", "0.0.1"); var counter = meter.CreateCounter("counter"); @@ -92,7 +91,7 @@ void RunTest(Batch metricItem) var resourceMetric = request.ResourceMetrics.First(); var oltpResource = resourceMetric.Resource; - if (addResource) + if (includeServiceNameInResource) { Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service-name"); Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns1"); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs index 94a13f28060..62e0d50f978 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs @@ -61,7 +61,7 @@ public void OtlpExporter_BadArgs() [Theory] [InlineData(true)] [InlineData(false)] - public void ToOtlpResourceSpansTest(bool addResource) + public void ToOtlpResourceSpansTest(bool includeServiceNameInResource) { var evenTags = new[] { new KeyValuePair("k0", "v0") }; var oddTags = new[] { new KeyValuePair("k1", "v1") }; @@ -75,27 +75,26 @@ public void ToOtlpResourceSpansTest(bool addResource) new OtlpExporterOptions(), new NoopTraceServiceClient()); - if (addResource) + var resourceBuilder = ResourceBuilder.CreateEmpty(); + if (includeServiceNameInResource) { - exporter.SetResource( - ResourceBuilder.CreateEmpty().AddAttributes( - new List> - { - new KeyValuePair(Resources.ResourceSemanticConventions.AttributeServiceName, "service-name"), - new KeyValuePair(Resources.ResourceSemanticConventions.AttributeServiceNamespace, "ns1"), - }).Build()); - } - else - { - exporter.SetResource(Resources.Resource.Empty); + resourceBuilder.AddAttributes( + new List> + { + new KeyValuePair(ResourceSemanticConventions.AttributeServiceName, "service-name"), + new KeyValuePair(ResourceSemanticConventions.AttributeServiceNamespace, "ns1"), + }); } var builder = Sdk.CreateTracerProviderBuilder() + .SetResourceBuilder(resourceBuilder) .AddSource(sources[0].Name) .AddSource(sources[1].Name); using var openTelemetrySdk = builder.Build(); + exporter.ParentProvider = openTelemetrySdk; + var processor = new BatchActivityExportProcessor(new TestExporter(RunTest)); const int numOfSpans = 10; bool isEven; @@ -120,7 +119,7 @@ void RunTest(Batch batch) Assert.Single(request.ResourceSpans); var oltpResource = request.ResourceSpans.First().Resource; - if (addResource) + if (includeServiceNameInResource) { Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service-name"); Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == Resources.ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns1"); @@ -342,52 +341,6 @@ public void UseOpenTelemetryProtocolActivityExporterWithCustomActivityProcessor( Assert.True(endCalled); } - [Theory] - [InlineData("key=value", new string[] { "key" }, new string[] { "value" })] - [InlineData("key1=value1,key2=value2", new string[] { "key1", "key2" }, new string[] { "value1", "value2" })] - [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) - public void GetMetadataFromHeadersWorksCorrectFormat(string headers, string[] keys, string[] values) - { - var otlpExporter = new OtlpTraceExporter(new OtlpExporterOptions()); - var metadata = typeof(OtlpTraceExporter) - .GetMethod("GetMetadataFromHeaders", BindingFlags.NonPublic | BindingFlags.Static) - .Invoke(otlpExporter, new object[] { headers }) as Metadata; - - Assert.Equal(keys.Length, metadata.Count); - - for (int i = 0; i < keys.Length; i++) - { - Assert.Contains(metadata, entry => entry.Key == keys[i] && entry.Value == values[i]); - } - } - - [Theory] - [InlineData("headers")] - [InlineData("key,value")] - public void GetMetadataFromHeadersThrowsExceptionOnOnvalidFormat(string headers) - { - var otlpExporter = new OtlpTraceExporter(new OtlpExporterOptions()); - try - { - var metadata = typeof(OtlpTraceExporter) - .GetMethod("GetMetadataFromHeaders", BindingFlags.NonPublic | BindingFlags.Static) - .Invoke(otlpExporter, new object[] { headers }) as Metadata; - } - catch (Exception ex) - { - // The exception thrown here is System.Reflection.TargetInvocationException. The exception thrown by the method GetMetadataFromHeaders - // is captured in the inner exception - Assert.IsType(ex.InnerException); - Assert.Equal("Headers provided in an invalid format.", ex.InnerException.Message); - return; - } - - throw new XunitException("GetMetadataFromHeaders did not throw an exception for invalid input headers"); - } - private class NoopTraceServiceClient : OtlpCollector.TraceService.ITraceServiceClient { public OtlpCollector.ExportTraceServiceResponse Export(OtlpCollector.ExportTraceServiceRequest request, GrpcCore.Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) From 159ee9ddab26eae62793a8a7529bcd9b38e941d9 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 4 Aug 2021 15:44:08 -0700 Subject: [PATCH 22/45] use EventKeywords.All rather than (-1) (#2227) --- .../Internal/OpenTelemetryApiEventSource.cs | 8 ++++---- .../Implementation/PrometheusExporterEventSource.cs | 6 +++--- .../Implementation/ZipkinExporterEventSource.cs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs b/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs index 369943daf6e..9e2add81c75 100644 --- a/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs +++ b/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs @@ -40,7 +40,7 @@ public void ActivityContextExtractException(string format, Exception ex) [NonEvent] public void BaggageExtractException(string format, Exception ex) { - if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Warning, EventKeywords.All)) { this.FailedToExtractBaggage(format, ex.ToInvariantString()); } @@ -49,7 +49,7 @@ public void BaggageExtractException(string format, Exception ex) [NonEvent] public void TracestateExtractException(Exception ex) { - if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Warning, EventKeywords.All)) { this.TracestateExtractError(ex.ToInvariantString()); } @@ -58,7 +58,7 @@ public void TracestateExtractException(Exception ex) [NonEvent] public void TracestateKeyIsInvalid(ReadOnlySpan key) { - if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Warning, EventKeywords.All)) { this.TracestateKeyIsInvalid(key.ToString()); } @@ -67,7 +67,7 @@ public void TracestateKeyIsInvalid(ReadOnlySpan key) [NonEvent] public void TracestateValueIsInvalid(ReadOnlySpan value) { - if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Warning, EventKeywords.All)) { this.TracestateValueIsInvalid(value.ToString()); } diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs index f533884005d..cd577f4f5aa 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusExporterEventSource.cs @@ -31,7 +31,7 @@ internal class PrometheusExporterEventSource : EventSource [NonEvent] public void FailedExport(Exception ex) { - if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) { this.FailedExport(ex.ToInvariantString()); } @@ -40,7 +40,7 @@ public void FailedExport(Exception ex) [NonEvent] public void FailedShutdown(Exception ex) { - if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) { this.FailedShutdown(ex.ToInvariantString()); } @@ -49,7 +49,7 @@ public void FailedShutdown(Exception ex) [NonEvent] public void CanceledExport(Exception ex) { - if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) { this.CanceledExport(ex.ToInvariantString()); } diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinExporterEventSource.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinExporterEventSource.cs index eb8a6af0b45..de9d6856fe1 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinExporterEventSource.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinExporterEventSource.cs @@ -40,7 +40,7 @@ public void FailedExport(Exception ex) [NonEvent] public void FailedEndpointInitialization(Exception ex) { - if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) { this.FailedEndpointInitialization(ex.ToInvariantString()); } From 71dcc6be208f2fe0c5820ae3ef26fa94764048b3 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Thu, 5 Aug 2021 11:16:40 -0700 Subject: [PATCH 23/45] Fix aspnetcore metric test stability (#2236) --- .../MetricTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs index 10f0e6b0185..8250204ef75 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs @@ -76,6 +76,11 @@ void ProcessExport(Batch batch) response.EnsureSuccessStatusCode(); } + // We need to let End callback execute as it is executed AFTER response was returned. + // In unit tests environment there may be a lot of parallel unit tests executed, so + // giving some breezing room for the End callback to complete + await Task.Delay(TimeSpan.FromSeconds(1)); + // Invokes the TestExporter which will invoke ProcessExport processor.PullRequest(); From 67b6a83932392d39553e06471f837bae1c463c95 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Tue, 10 Aug 2021 09:20:18 -0700 Subject: [PATCH 24/45] HttpClient http.client.duration metric (#2243) --- .../HttpClientMetrics.cs | 53 ++++++++++++++++ .../HttpHandlerMetricsDiagnosticListener.cs | 61 +++++++++++++++++++ .../MeterProviderBuilderExtensions.cs | 53 ++++++++++++++++ .../HttpClientTests.netcore31.cs | 52 ++++++++++++++++ ...elemetry.Instrumentation.Http.Tests.csproj | 1 + 5 files changed, 220 insertions(+) create mode 100644 src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs create mode 100644 src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs create mode 100644 src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs new file mode 100644 index 00000000000..c0502219e3b --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs @@ -0,0 +1,53 @@ +// +// 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.Http.Implementation; + +namespace OpenTelemetry.Instrumentation.Http +{ + /// + /// HttpClient instrumentation. + /// + internal class HttpClientMetrics : IDisposable + { + internal static readonly AssemblyName AssemblyName = typeof(HttpClientMetrics).Assembly.GetName(); + internal static readonly string InstrumentationName = AssemblyName.Name; + internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString(); + + private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber; + private readonly Meter meter; + + /// + /// Initializes a new instance of the class. + /// + public HttpClientMetrics() + { + this.meter = new Meter(InstrumentationName, InstrumentationVersion); + this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpHandlerMetricsDiagnosticListener("HttpHandlerDiagnosticListener", this.meter), null); + this.diagnosticSourceSubscriber.Subscribe(); + } + + /// + public void Dispose() + { + this.diagnosticSourceSubscriber?.Dispose(); + this.meter?.Dispose(); + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs new file mode 100644 index 00000000000..2fa58e88b1c --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.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.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Metrics; +using System.Net.Http; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Instrumentation.Http.Implementation +{ + internal class HttpHandlerMetricsDiagnosticListener : ListenerHandler + { + private readonly PropertyFetcher stopResponseFetcher = new PropertyFetcher("Response"); + private readonly Histogram httpClientDuration; + + public HttpHandlerMetricsDiagnosticListener(string name, Meter meter) + : base(name) + { + this.httpClientDuration = meter.CreateHistogram("http.client.duration", "milliseconds", "measure the duration of the outbound HTTP request"); + } + + public override void OnStopActivity(Activity activity, object payload) + { + if (Sdk.SuppressInstrumentation) + { + return; + } + + if (this.stopResponseFetcher.TryFetch(payload, out HttpResponseMessage response) && response != null) + { + var request = response.RequestMessage; + + // 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-client + var tags = new KeyValuePair[] + { + new KeyValuePair(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method)), + new KeyValuePair(SemanticConventions.AttributeHttpScheme, request.RequestUri.Scheme), + new KeyValuePair(SemanticConventions.AttributeHttpStatusCode, (int)response.StatusCode), + new KeyValuePair(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version)), + }; + + this.httpClientDuration.Record(activity.Duration.TotalMilliseconds, tags); + } + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs new file mode 100644 index 00000000000..f7c969e6ebd --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs @@ -0,0 +1,53 @@ +// +// 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.Http; + +namespace OpenTelemetry.Metrics +{ + /// + /// Extension methods to simplify registering of HttpClient instrumentation. + /// + public static class MeterProviderBuilderExtensions + { + /// + /// Enables HttpClient instrumentation. + /// + /// being configured. + /// The instance of to chain the calls. + public static MeterProviderBuilder AddHttpClientInstrumentation( + this MeterProviderBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + // TODO: Implement an IDeferredMeterProviderBuilder + + // TODO: Handle HttpClientInstrumentationOptions + // SetHttpFlavor - seems like this would be handled by views + // Filter - makes sense for metric instrumentation + // Enrich - do we want a similar kind of functionality for metrics? + // RecordException - probably doesn't make sense for metric instrumentation + + var instrumentation = new HttpClientMetrics(); + builder.AddSource(HttpClientMetrics.InstrumentationName); + return builder.AddInstrumentation(() => instrumentation); + } + } +} diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.netcore31.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.netcore31.cs index 3f680ee3af3..937300c6876 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.netcore31.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.netcore31.cs @@ -25,6 +25,7 @@ using System.Threading.Tasks; using Moq; using Newtonsoft.Json; +using OpenTelemetry.Metrics; using OpenTelemetry.Tests; using OpenTelemetry.Trace; using Xunit; @@ -53,6 +54,23 @@ public async Task HttpOutCallsAreCollectedSuccessfullyAsync(HttpTestData.HttpOut var processor = new Mock>(); tc.Url = HttpTestData.NormalizeValues(tc.Url, host, port); + var metricItems = new List(); + var metricExporter = new TestExporter(ProcessExport); + + void ProcessExport(Batch batch) + { + foreach (var metricItem in batch) + { + metricItems.Add(metricItem); + } + } + + var metricProcessor = new PullMetricProcessor(metricExporter, true); + var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddHttpClientInstrumentation() + .AddMetricProcessor(metricProcessor) + .Build(); + using (serverLifeTime) using (Sdk.CreateTracerProviderBuilder() @@ -91,6 +109,15 @@ public async Task HttpOutCallsAreCollectedSuccessfullyAsync(HttpTestData.HttpOut } } + // Invokes the TestExporter which will invoke ProcessExport + metricProcessor.PullRequest(); + + meterProvider.Dispose(); + + var requestMetrics = metricItems + .SelectMany(item => item.Metrics.Where(metric => metric.Name == "http.client.duration")) + .ToArray(); + Assert.Equal(5, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called. var activity = (Activity)processor.Invocations[2].Arguments[0]; @@ -122,6 +149,30 @@ public async Task HttpOutCallsAreCollectedSuccessfullyAsync(HttpTestData.HttpOut { Assert.Single(activity.Events.Where(evt => evt.Name.Equals("exception"))); } + + if (tc.ResponseExpected) + { + Assert.Single(requestMetrics); + + var metric = requestMetrics[0] as IHistogramMetric; + Assert.NotNull(metric); + Assert.Equal(1L, metric.PopulationCount); + Assert.Equal(activity.Duration.TotalMilliseconds, metric.PopulationSum); + + var method = new KeyValuePair(SemanticConventions.AttributeHttpMethod, tc.Method); + var scheme = new KeyValuePair(SemanticConventions.AttributeHttpScheme, "http"); + var statusCode = new KeyValuePair(SemanticConventions.AttributeHttpStatusCode, tc.ResponseCode == 0 ? 200 : tc.ResponseCode); + var flavor = new KeyValuePair(SemanticConventions.AttributeHttpFlavor, "2.0"); + Assert.Contains(method, metric.Attributes); + Assert.Contains(scheme, metric.Attributes); + Assert.Contains(statusCode, metric.Attributes); + Assert.Contains(flavor, metric.Attributes); + Assert.Equal(4, metric.Attributes.Length); + } + else + { + Assert.Empty(requestMetrics); + } } [Fact] @@ -135,6 +186,7 @@ public async Task DebugIndividualTestAsync() ""method"": ""GET"", ""url"": ""http://{host}:{port}/"", ""responseCode"": 399, + ""responseExpected"": true, ""spanName"": ""HTTP GET"", ""spanStatus"": ""UNSET"", ""spanKind"": ""Client"", 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 3864ae46ef8..ce177accfe2 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj @@ -10,6 +10,7 @@ + From f64437301e89601027b70ec5f5429ebdf355364f Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 10 Aug 2021 11:59:17 -0500 Subject: [PATCH 25/45] Add SetVerboseDatabaseStatements and Enrich options to the Redis instrumentation (#2198) --- .../.publicApi/net461/PublicAPI.Unshipped.txt | 6 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 6 +- .../CHANGELOG.md | 4 + .../RedisProfilerEntryToActivityConverter.cs | 100 +++++++++++++- ....Instrumentation.StackExchangeRedis.csproj | 1 + .../README.md | 42 +++++- .../StackExchangeRedisCallsInstrumentation.cs | 4 +- ...xchangeRedisCallsInstrumentationOptions.cs | 17 +++ ...isProfilerEntryToActivityConverterTests.cs | 16 +-- ...kExchangeRedisCallsInstrumentationTests.cs | 129 +++++++++++++++++- 10 files changed, 300 insertions(+), 25 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/net461/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/net461/PublicAPI.Unshipped.txt index f3c586af488..ebca0c81e02 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/net461/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/net461/PublicAPI.Unshipped.txt @@ -1,6 +1,10 @@ OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions +OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.get -> System.Action +OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.set -> void OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.get -> System.TimeSpan OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.set -> void +OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetVerboseDatabaseStatements.get -> bool +OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetVerboseDatabaseStatements.set -> void OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.StackExchangeRedisCallsInstrumentationOptions() -> void OpenTelemetry.Trace.TracerProviderBuilderExtensions -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, StackExchange.Redis.IConnectionMultiplexer connection = null, System.Action configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder \ No newline at end of file +static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, StackExchange.Redis.IConnectionMultiplexer connection = null, System.Action configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index f3c586af488..ebca0c81e02 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,6 +1,10 @@ OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions +OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.get -> System.Action +OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.set -> void OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.get -> System.TimeSpan OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.set -> void +OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetVerboseDatabaseStatements.get -> bool +OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetVerboseDatabaseStatements.set -> void OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.StackExchangeRedisCallsInstrumentationOptions() -> void OpenTelemetry.Trace.TracerProviderBuilderExtensions -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, StackExchange.Redis.IConnectionMultiplexer connection = null, System.Action configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder \ No newline at end of file +static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, StackExchange.Redis.IConnectionMultiplexer connection = null, System.Action configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md index bbced768f45..fb149110925 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +* Adds SetVerboseDatabaseStatements option to allow setting more detailed database + statement tag values. +* Adds Enrich option to allow enriching activities from the source profiled command + objects. * Removes upper constraint for Microsoft.Extensions.Options dependency. ([#2179](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2179)) diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs index b5df1db85a6..4c7cf250c1e 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs @@ -13,9 +13,12 @@ // 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.Net; +using System.Reflection; +using System.Reflection.Emit; using OpenTelemetry.Trace; using StackExchange.Redis.Profiling; @@ -23,7 +26,51 @@ namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation { internal static class RedisProfilerEntryToActivityConverter { - public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfiledCommand command) + private static readonly Lazy> MessageDataGetter = new Lazy>(() => + { + var redisAssembly = typeof(IProfiledCommand).Assembly; + Type profiledCommandType = redisAssembly.GetType("StackExchange.Redis.Profiling.ProfiledCommand"); + Type messageType = redisAssembly.GetType("StackExchange.Redis.Message"); + Type scriptMessageType = redisAssembly.GetType("StackExchange.Redis.RedisDatabase+ScriptEvalMessage"); + + var messageDelegate = CreateFieldGetter(profiledCommandType, "Message", BindingFlags.NonPublic | BindingFlags.Instance); + var scriptDelegate = CreateFieldGetter(scriptMessageType, "script", BindingFlags.NonPublic | BindingFlags.Instance); + var commandAndKeyFetcher = new PropertyFetcher("CommandAndKey"); + + if (messageDelegate == null) + { + return new Func(source => (null, null)); + } + + return new Func(source => + { + if (source == null) + { + return (null, null); + } + + var message = messageDelegate(source); + if (message == null) + { + return (null, null); + } + + string script = null; + if (message.GetType() == scriptMessageType) + { + script = scriptDelegate.Invoke(message); + } + + if (commandAndKeyFetcher.TryFetch(message, out var value)) + { + return (value, script); + } + + return (null, script); + }); + }); + + public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfiledCommand command, StackExchangeRedisCallsInstrumentationOptions options) { var name = command.Command; // Example: SET; if (string.IsNullOrEmpty(name)) @@ -43,6 +90,8 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi return null; } + activity.SetEndTime(command.CommandCreated + command.ElapsedTime); + if (activity.IsAllDataRequested == true) { // see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md @@ -62,7 +111,25 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi activity.SetTag(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName, command.Flags.ToString()); - if (command.Command != null) + if (options.SetVerboseDatabaseStatements) + { + var (commandAndKey, script) = MessageDataGetter.Value.Invoke(command); + + if (!string.IsNullOrEmpty(commandAndKey) && !string.IsNullOrEmpty(script)) + { + activity.SetTag(SemanticConventions.AttributeDbStatement, commandAndKey + " " + script); + } + else if (!string.IsNullOrEmpty(commandAndKey)) + { + activity.SetTag(SemanticConventions.AttributeDbStatement, commandAndKey); + } + else if (command.Command != null) + { + // Example: "db.statement": SET; + activity.SetTag(SemanticConventions.AttributeDbStatement, command.Command); + } + } + else if (command.Command != null) { // Example: "db.statement": SET; activity.SetTag(SemanticConventions.AttributeDbStatement, command.Command); @@ -100,7 +167,7 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi activity.AddEvent(new ActivityEvent("Sent", send)); activity.AddEvent(new ActivityEvent("ResponseReceived", response)); - activity.SetEndTime(command.CommandCreated + command.ElapsedTime); + options.Enrich?.Invoke(activity, command); } activity.Stop(); @@ -108,12 +175,35 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi return activity; } - public static void DrainSession(Activity parentActivity, IEnumerable sessionCommands) + public static void DrainSession(Activity parentActivity, IEnumerable sessionCommands, StackExchangeRedisCallsInstrumentationOptions options) { foreach (var command in sessionCommands) { - ProfilerCommandToActivity(parentActivity, command); + ProfilerCommandToActivity(parentActivity, command, options); } } + + /// + /// Creates getter for a field defined in private or internal type + /// repesented with classType variable. + /// + private static Func CreateFieldGetter(Type classType, string fieldName, BindingFlags flags) + { + FieldInfo field = classType.GetField(fieldName, flags); + if (field != null) + { + string methodName = classType.FullName + ".get_" + field.Name; + DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(TField), new[] { typeof(object) }, true); + ILGenerator generator = getterMethod.GetILGenerator(); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Castclass, classType); + generator.Emit(OpCodes.Ldfld, field); + generator.Emit(OpCodes.Ret); + + return (Func)getterMethod.CreateDelegate(typeof(Func)); + } + + return null; + } } } diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/OpenTelemetry.Instrumentation.StackExchangeRedis.csproj b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/OpenTelemetry.Instrumentation.StackExchangeRedis.csproj index b447cbc7eba..9890827d021 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/OpenTelemetry.Instrumentation.StackExchangeRedis.csproj +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/OpenTelemetry.Instrumentation.StackExchangeRedis.csproj @@ -3,6 +3,7 @@ netstandard2.0;net461 StackExchange.Redis instrumentation for OpenTelemetry .NET $(PackageTags);distributed-tracing;Redis;StackExchange.Redis + true true diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md index cdf212b32a9..5c0dd925211 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md @@ -67,10 +67,10 @@ This instrumentation can be configured to change the default behavior by using ### FlushInterval -StackExchange.Redis has its own internal profiler. OpenTelmetry converts each +StackExchange.Redis has its own internal profiler. OpenTelemetry converts each profiled command from the internal profiler to an Activity for collection. By default, this conversion process flushes profiled commands on a 10 second -interval. The `FlushInterval` option can be used to adjust this internval. +interval. The `FlushInterval` option can be used to adjust this interval. The following example shows how to use `FlushInterval`. @@ -83,6 +83,44 @@ using var tracerProvider = Sdk.CreateTracerProviderBuilder() .Build(); ``` +### SetVerboseDatabaseStatements + +StackExchange.Redis by default does not give detailed database statements like +what key or script was used during an operation. The `SetVerboseDatabaseStatements` +option can be used to enable gathering this more detailed information. + +The following example shows how to use `SetVerboseDatabaseStatements`. + +```csharp +using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddRedisInstrumentation( + connection, + options => options.SetVerboseDatabaseStatements = true) + .AddConsoleExporter() + .Build(); +``` + +## Enrich + +This option allows one to enrich the activity with additional information from the +raw `IProfiledCommand` object. The `Enrich` action is called only when +`activity.IsAllDataRequested` is `true`. It contains the activity itself (which can +be enriched), and the source profiled command object. + +The following code snippet shows how to add additional tags using `Enrich`. + +```csharp +using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddRedisInstrumentation(opt => opt.Enrich = (activity, command) => + { + if (command.ElapsedTime < TimeSpan.FromMilliseconds(100)) + { + activity.SetTag("is_fast", true); + } + }) + .Build(); +``` + ## References * [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentation.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentation.cs index b54cac1988a..5a85e3d416a 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentation.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentation.cs @@ -120,7 +120,7 @@ public void Dispose() internal void Flush() { - RedisProfilerEntryToActivityConverter.DrainSession(null, this.defaultSession.FinishProfiling()); + RedisProfilerEntryToActivityConverter.DrainSession(null, this.defaultSession.FinishProfiling(), this.options); foreach (var entry in this.Cache) { @@ -132,7 +132,7 @@ internal void Flush() } ProfilingSession session = entry.Value.Session; - RedisProfilerEntryToActivityConverter.DrainSession(parent, session.FinishProfiling()); + RedisProfilerEntryToActivityConverter.DrainSession(parent, session.FinishProfiling(), this.options); this.Cache.TryRemove((entry.Key.TraceId, entry.Key.SpanId), out _); } } diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentationOptions.cs index d414d838ccb..735bc172984 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentationOptions.cs @@ -15,7 +15,10 @@ // using System; +using System.Data; using System.Diagnostics; +using OpenTelemetry.Trace; +using StackExchange.Redis.Profiling; namespace OpenTelemetry.Instrumentation.StackExchangeRedis { @@ -28,5 +31,19 @@ public class StackExchangeRedisCallsInstrumentationOptions /// Gets or sets the maximum time that should elapse between flushing the internal buffer of Redis profiling sessions and creating objects. Default value: 00:00:10. /// public TimeSpan FlushInterval { get; set; } = TimeSpan.FromSeconds(10); + + /// + /// Gets or sets a value indicating whether or not the should use reflection to get more detailed tag values. Default value: False. + /// + public bool SetVerboseDatabaseStatements { get; set; } + + /// + /// Gets or sets an action to enrich an Activity. + /// + /// + /// : the activity being enriched. + /// : the profiled redis command from which additional information can be extracted to enrich the activity. + /// + public Action Enrich { get; set; } } } diff --git a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs index 9a67dcb6da0..a385124addb 100644 --- a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs +++ b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs @@ -61,7 +61,7 @@ public void ProfilerCommandToActivity_UsesCommandAsName() profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); profiledCommand.Setup(m => m.Command).Returns("SET"); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); Assert.Equal("SET", result.DisplayName); } @@ -74,7 +74,7 @@ public void ProfilerCommandToActivity_UsesTimestampAsStartTime() var profiledCommand = new Mock(); profiledCommand.Setup(m => m.CommandCreated).Returns(now.DateTime); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); Assert.Equal(now, result.StartTimeUtc); } @@ -86,7 +86,7 @@ public void ProfilerCommandToActivity_SetsDbTypeAttributeAsRedis() var profiledCommand = new Mock(); profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbSystem)); Assert.Equal("redis", result.GetTagValue(SemanticConventions.AttributeDbSystem)); @@ -100,7 +100,7 @@ public void ProfilerCommandToActivity_UsesCommandAsDbStatementAttribute() profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); profiledCommand.Setup(m => m.Command).Returns("SET"); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbStatement)); Assert.Equal("SET", result.GetTagValue(SemanticConventions.AttributeDbStatement)); @@ -116,7 +116,7 @@ public void ProfilerCommandToActivity_UsesFlagsForFlagsAttribute() CommandFlags.NoRedirect; profiledCommand.Setup(m => m.Flags).Returns(expectedFlags); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); Assert.NotNull(result.GetTagValue(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName)); Assert.Equal("PreferMaster, FireAndForget, NoRedirect", result.GetTagValue(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName)); @@ -134,7 +134,7 @@ public void ProfilerCommandToActivity_UsesIpEndPointAsEndPoint() profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); profiledCommand.Setup(m => m.EndPoint).Returns(ipLocalEndPoint); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerIp)); Assert.Equal($"{address}.0.0.0", result.GetTagValue(SemanticConventions.AttributeNetPeerIp)); @@ -152,7 +152,7 @@ public void ProfilerCommandToActivity_UsesDnsEndPointAsEndPoint() profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); profiledCommand.Setup(m => m.EndPoint).Returns(dnsEndPoint); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerName)); Assert.Equal(dnsEndPoint.Host, result.GetTagValue(SemanticConventions.AttributeNetPeerName)); @@ -170,7 +170,7 @@ public void ProfilerCommandToActivity_UsesOtherEndPointAsEndPoint() profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); profiledCommand.Setup(m => m.EndPoint).Returns(unixEndPoint); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributePeerService)); Assert.Equal(unixEndPoint.ToString(), result.GetTagValue(SemanticConventions.AttributePeerService)); diff --git a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs index c99a7091144..46a29aa7b67 100644 --- a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs +++ b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs @@ -41,6 +41,60 @@ To use Docker... private const string RedisEndPointEnvVarName = "OTEL_REDISENDPOINT"; private static readonly string RedisEndPoint = SkipUnlessEnvVarFoundTheoryAttribute.GetEnvironmentVariable(RedisEndPointEnvVarName); + [Trait("CategoryName", "RedisIntegrationTests")] + [SkipUnlessEnvVarFoundTheory(RedisEndPointEnvVarName)] + [InlineData("value1")] + public void SuccessfulCommandTestWithKey(string value) + { + var connectionOptions = new ConfigurationOptions + { + AbortOnConnectFail = true, + }; + connectionOptions.EndPoints.Add(RedisEndPoint); + + using var connection = ConnectionMultiplexer.Connect(connectionOptions); + var db = connection.GetDatabase(); + db.KeyDelete("key1"); + + var activityProcessor = new Mock>(); + var sampler = new TestSampler(); + using (Sdk.CreateTracerProviderBuilder() + .AddProcessor(activityProcessor.Object) + .SetSampler(sampler) + .AddRedisInstrumentation(connection, c => c.SetVerboseDatabaseStatements = true) + .Build()) + { + var prepared = LuaScript.Prepare("redis.call('set', @key, @value)"); + db.ScriptEvaluate(prepared, new { key = (RedisKey)"mykey", value = 123 }); + + var redisValue = db.StringGet("key1"); + + Assert.False(redisValue.HasValue); + + bool set = db.StringSet("key1", value, TimeSpan.FromSeconds(60)); + + Assert.True(set); + + redisValue = db.StringGet("key1"); + + Assert.True(redisValue.HasValue); + Assert.Equal(value, redisValue.ToString()); + } + + // Disposing SDK should flush the Redis profiling session immediately. + + Assert.Equal(11, activityProcessor.Invocations.Count); + + var scriptActivity = (Activity)activityProcessor.Invocations[1].Arguments[0]; + Assert.Equal("EVAL", scriptActivity.DisplayName); + Assert.Equal("EVAL redis.call('set', ARGV[1], ARGV[2])", scriptActivity.GetTagValue(SemanticConventions.AttributeDbStatement)); + + VerifyActivityData((Activity)activityProcessor.Invocations[3].Arguments[0], false, connection.GetEndPoints()[0], true); + VerifyActivityData((Activity)activityProcessor.Invocations[5].Arguments[0], true, connection.GetEndPoints()[0], true); + VerifyActivityData((Activity)activityProcessor.Invocations[7].Arguments[0], false, connection.GetEndPoints()[0], true); + VerifySamplingParameters(sampler.LatestSamplingParameters); + } + [Trait("CategoryName", "RedisIntegrationTests")] [SkipUnlessEnvVarFoundTheory(RedisEndPointEnvVarName)] [InlineData("value1")] @@ -59,7 +113,7 @@ public void SuccessfulCommandTest(string value) using (Sdk.CreateTracerProviderBuilder() .AddProcessor(activityProcessor.Object) .SetSampler(sampler) - .AddRedisInstrumentation(connection) + .AddRedisInstrumentation(connection, c => c.SetVerboseDatabaseStatements = false) .Build()) { var db = connection.GetDatabase(); @@ -78,8 +132,8 @@ public void SuccessfulCommandTest(string value) Assert.Equal(7, activityProcessor.Invocations.Count); - VerifyActivityData((Activity)activityProcessor.Invocations[1].Arguments[0], true, connection.GetEndPoints()[0]); - VerifyActivityData((Activity)activityProcessor.Invocations[3].Arguments[0], false, connection.GetEndPoints()[0]); + VerifyActivityData((Activity)activityProcessor.Invocations[1].Arguments[0], true, connection.GetEndPoints()[0], false); + VerifyActivityData((Activity)activityProcessor.Invocations[3].Arguments[0], false, connection.GetEndPoints()[0], false); VerifySamplingParameters(sampler.LatestSamplingParameters); } @@ -104,6 +158,55 @@ public async void ProfilerSessionUsesTheSameDefault() Assert.Equal(second, third); } + [Trait("CategoryName", "RedisIntegrationTests")] + [SkipUnlessEnvVarFoundTheory(RedisEndPointEnvVarName)] + [InlineData("value1")] + public void CanEnrichActivityFromCommand(string value) + { + var connectionOptions = new ConfigurationOptions + { + AbortOnConnectFail = true, + }; + connectionOptions.EndPoints.Add(RedisEndPoint); + + using var connection = ConnectionMultiplexer.Connect(connectionOptions); + + var activityProcessor = new Mock>(); + var sampler = new TestSampler(); + using (Sdk.CreateTracerProviderBuilder() + .AddProcessor(activityProcessor.Object) + .SetSampler(sampler) + .AddRedisInstrumentation(connection, c => c.Enrich = (activity, command) => + { + if (command.ElapsedTime < TimeSpan.FromMilliseconds(100)) + { + activity.AddTag("is_fast", true); + } + }) + .Build()) + { + var db = connection.GetDatabase(); + + bool set = db.StringSet("key1", value, TimeSpan.FromSeconds(60)); + + Assert.True(set); + + var redisValue = db.StringGet("key1"); + + Assert.True(redisValue.HasValue); + Assert.Equal(value, redisValue.ToString()); + } + + // Disposing SDK should flush the Redis profiling session immediately. + + Assert.Equal(7, activityProcessor.Invocations.Count); + + var setActivity = (Activity)activityProcessor.Invocations[1].Arguments[0]; + Assert.Equal(true, setActivity.GetTagValue("is_fast")); + var getActivity = (Activity)activityProcessor.Invocations[3].Arguments[0]; + Assert.Equal(true, getActivity.GetTagValue("is_fast")); + } + [Fact] public void CheckCacheIsFlushedProperly() { @@ -255,17 +358,31 @@ public void StackExchangeRedis_DependencyInjection_Failure() Assert.Throws(() => serviceProvider.GetRequiredService()); } - private static void VerifyActivityData(Activity activity, bool isSet, EndPoint endPoint) + private static void VerifyActivityData(Activity activity, bool isSet, EndPoint endPoint, bool setCommandKey = false) { if (isSet) { Assert.Equal("SETEX", activity.DisplayName); - Assert.Equal("SETEX", activity.GetTagValue(SemanticConventions.AttributeDbStatement)); + if (setCommandKey) + { + Assert.Equal("SETEX key1", activity.GetTagValue(SemanticConventions.AttributeDbStatement)); + } + else + { + Assert.Equal("SETEX", activity.GetTagValue(SemanticConventions.AttributeDbStatement)); + } } else { Assert.Equal("GET", activity.DisplayName); - Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeDbStatement)); + if (setCommandKey) + { + Assert.Equal("GET key1", activity.GetTagValue(SemanticConventions.AttributeDbStatement)); + } + else + { + Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeDbStatement)); + } } Assert.Equal(Status.Unset, activity.GetStatus()); From 24226a58f8bc407df940aaee9a32956881013d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Tue, 10 Aug 2021 19:36:42 +0200 Subject: [PATCH 26/45] Add support for OTEL_SERVICE_NAME environmental variable (#2209) --- src/OpenTelemetry/CHANGELOG.md | 3 + src/OpenTelemetry/README.md | 20 ++++-- .../Resources/OtelEnvResourceDetector.cs | 9 +-- .../OtelServiceNameEnvVarDetector.cs | 51 ++++++++++++++ .../Resources/ResourceBuilderExtensions.cs | 6 +- .../Resources/OtelEnvResourceDetectorTest.cs | 28 +++++--- .../OtelServiceNameEnvVarDetectorTests.cs | 70 +++++++++++++++++++ .../Resources/ResourceTest.cs | 63 +++++++++++++++-- 8 files changed, 222 insertions(+), 28 deletions(-) create mode 100644 src/OpenTelemetry/Resources/OtelServiceNameEnvVarDetector.cs create mode 100644 test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 89595afcbd8..1749e7c1a0f 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* `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)) diff --git a/src/OpenTelemetry/README.md b/src/OpenTelemetry/README.md index 94744f5cadc..33a840986a0 100644 --- a/src/OpenTelemetry/README.md +++ b/src/OpenTelemetry/README.md @@ -5,10 +5,10 @@ * [Installation](#installation) * [Introduction](#introduction) -* [Getting started with Logs](#getting-started-with-logging) -* [Getting started with Traces](#getting-started-with-tracing) -* [Tracing Configuration](#tracing-configuration) - * [ActivitySource](#activity-source) +* [Getting started with Logging](#getting-started-with-logging) +* [Getting started with Tracing](#getting-started-with-tracing) +* [Tracing configuration](#tracing-configuration) + * [Activity Source](#activity-source) * [Instrumentation](#instrumentation) * [Processor](#processor) * [Resource](#resource) @@ -16,6 +16,8 @@ * [Advanced topics](#advanced-topics) * [Propagators](#propagators) * [Troubleshooting](#troubleshooting) + * [Configuration Parameters](#configuration-parameters) + * [Remarks](#remarks) * [References](#references) ## Installation @@ -243,6 +245,16 @@ using var tracerProvider = Sdk.CreateTracerProviderBuilder() .Build(); ``` +It is also possible to configure the `Resource` by using `AddEnvironmentVariableDetector` +together with following environmental variables: + + +| Environment variable | Description | +| -------------------------- | -------------------------------------------------- | +| `OTEL_RESOURCE_ATTRIBUTES` | Key-value pairs to be used as resource attributes. See the [Resource SDK specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.5.0/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable) for more details. | +| `OTEL_SERVICE_NAME` | Sets the value of the `service.name` resource attribute. If `service.name` is also provided in `OTEL_RESOURCE_ATTRIBUTES`, then `OTEL_SERVICE_NAME` takes precedence. | + + ### Sampler [Samplers](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampler) diff --git a/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs b/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs index 236762ad098..cd424e2d034 100644 --- a/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs +++ b/src/OpenTelemetry/Resources/OtelEnvResourceDetector.cs @@ -16,13 +16,14 @@ using System; using System.Collections.Generic; +using System.Security; using OpenTelemetry.Internal; namespace OpenTelemetry.Resources { internal class OtelEnvResourceDetector : IResourceDetector { - private const string OTelResourceEnvVarKey = "OTEL_RESOURCE_ATTRIBUTES"; + public const string EnvVarKey = "OTEL_RESOURCE_ATTRIBUTES"; private const char AttributeListSplitter = ','; private const char AttributeKeyValueSplitter = '='; @@ -32,16 +33,16 @@ public Resource Detect() try { - string envResourceAttributeValue = Environment.GetEnvironmentVariable(OTelResourceEnvVarKey); + string envResourceAttributeValue = Environment.GetEnvironmentVariable(EnvVarKey); if (!string.IsNullOrEmpty(envResourceAttributeValue)) { var attributes = ParseResourceAttributes(envResourceAttributeValue); resource = new Resource(attributes); } } - catch (Exception ex) + catch (SecurityException ex) { - OpenTelemetrySdkEventSource.Log.ResourceDetectorFailed("OtelEnvResourceDetector", ex.Message); + OpenTelemetrySdkEventSource.Log.ResourceDetectorFailed(nameof(OtelEnvResourceDetector), ex.Message); } return resource; diff --git a/src/OpenTelemetry/Resources/OtelServiceNameEnvVarDetector.cs b/src/OpenTelemetry/Resources/OtelServiceNameEnvVarDetector.cs new file mode 100644 index 00000000000..806ced7c793 --- /dev/null +++ b/src/OpenTelemetry/Resources/OtelServiceNameEnvVarDetector.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. +// + +using System; +using System.Collections.Generic; +using System.Security; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Resources +{ + internal class OtelServiceNameEnvVarDetector : IResourceDetector + { + public const string EnvVarKey = "OTEL_SERVICE_NAME"; + + public Resource Detect() + { + var resource = Resource.Empty; + + try + { + string envResourceAttributeValue = Environment.GetEnvironmentVariable(EnvVarKey); + if (!string.IsNullOrEmpty(envResourceAttributeValue)) + { + resource = new Resource(new Dictionary + { + [ResourceSemanticConventions.AttributeServiceName] = envResourceAttributeValue, + }); + } + } + catch (SecurityException ex) + { + OpenTelemetrySdkEventSource.Log.ResourceDetectorFailed(nameof(OtelServiceNameEnvVarDetector), ex.Message); + } + + return resource; + } + } +} diff --git a/src/OpenTelemetry/Resources/ResourceBuilderExtensions.cs b/src/OpenTelemetry/Resources/ResourceBuilderExtensions.cs index 8eb500597da..9dd30fc1236 100644 --- a/src/OpenTelemetry/Resources/ResourceBuilderExtensions.cs +++ b/src/OpenTelemetry/Resources/ResourceBuilderExtensions.cs @@ -110,8 +110,8 @@ public static ResourceBuilder AddAttributes(this ResourceBuilder resourceBuilder } /// - /// Adds resource attributes parsed from an environment variable to a - /// following the following the Resource /// SDK. /// @@ -119,7 +119,7 @@ public static ResourceBuilder AddAttributes(this ResourceBuilder resourceBuilder /// Returns for chaining. public static ResourceBuilder AddEnvironmentVariableDetector(this ResourceBuilder resourceBuilder) { - return resourceBuilder.AddDetector(new OtelEnvResourceDetector()); + return resourceBuilder.AddDetector(new OtelEnvResourceDetector()).AddDetector(new OtelServiceNameEnvVarDetector()); } private static string GetFileVersion() diff --git a/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs b/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs index 8ca21cdb0b9..dc1a8a376e2 100644 --- a/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs +++ b/test/OpenTelemetry.Tests/Resources/OtelEnvResourceDetectorTest.cs @@ -22,11 +22,24 @@ namespace OpenTelemetry.Resources.Tests { public class OtelEnvResourceDetectorTest : IDisposable { - private const string OtelEnvVarKey = "OTEL_RESOURCE_ATTRIBUTES"; - public OtelEnvResourceDetectorTest() { - Environment.SetEnvironmentVariable(OtelEnvVarKey, null); + Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, null); + } + + public void Dispose() + { + Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, null); + } + + [Fact] + public void OtelEnvResource_EnvVarKey() + { + // Act + var resource = new OtelServiceNameEnvVarDetector().Detect(); + + // Assert + Assert.Equal("OTEL_RESOURCE_ATTRIBUTES", OtelEnvResourceDetector.EnvVarKey); } [Fact] @@ -44,7 +57,7 @@ public void OtelEnvResource_WithEnvVar_1() { // Arrange var envVarValue = "Key1=Val1,Key2=Val2"; - Environment.SetEnvironmentVariable(OtelEnvVarKey, envVarValue); + Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, envVarValue); var resource = new OtelEnvResourceDetector().Detect(); // Assert @@ -57,7 +70,7 @@ public void OtelEnvResource_WithEnvVar_2() { // Arrange var envVarValue = "Key1,Key2=Val2"; - Environment.SetEnvironmentVariable(OtelEnvVarKey, envVarValue); + Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, envVarValue); var resource = new OtelEnvResourceDetector().Detect(); // Assert @@ -65,10 +78,5 @@ public void OtelEnvResource_WithEnvVar_2() Assert.Single(resource.Attributes); Assert.Contains(new KeyValuePair("Key2", "Val2"), resource.Attributes); } - - public void Dispose() - { - Environment.SetEnvironmentVariable(OtelEnvVarKey, null); - } } } diff --git a/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs b/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs new file mode 100644 index 00000000000..27ef814c3af --- /dev/null +++ b/test/OpenTelemetry.Tests/Resources/OtelServiceNameEnvVarDetectorTests.cs @@ -0,0 +1,70 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using Xunit; + +namespace OpenTelemetry.Resources.Tests +{ + public class OtelServiceNameEnvVarDetectorTests : IDisposable + { + public OtelServiceNameEnvVarDetectorTests() + { + Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, null); + } + + public void Dispose() + { + Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, null); + } + + [Fact] + public void OtelServiceNameEnvVar_EnvVarKey() + { + // Act + var resource = new OtelServiceNameEnvVarDetector().Detect(); + + // Assert + Assert.Equal("OTEL_SERVICE_NAME", OtelServiceNameEnvVarDetector.EnvVarKey); + } + + [Fact] + public void OtelServiceNameEnvVar_Null() + { + // Act + var resource = new OtelServiceNameEnvVarDetector().Detect(); + + // Assert + Assert.Equal(Resource.Empty, resource); + } + + [Fact] + public void OtelServiceNameEnvVar_WithValue() + { + // Arrange + var envVarValue = "my-service"; + Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, envVarValue); + + // Act + var resource = new OtelServiceNameEnvVarDetector().Detect(); + + // Assert + Assert.NotEqual(Resource.Empty, resource); + Assert.Contains(new KeyValuePair(ResourceSemanticConventions.AttributeServiceName, envVarValue), resource.Attributes); + } + } +} diff --git a/test/OpenTelemetry.Tests/Resources/ResourceTest.cs b/test/OpenTelemetry.Tests/Resources/ResourceTest.cs index 624d0c3f404..12288630a65 100644 --- a/test/OpenTelemetry.Tests/Resources/ResourceTest.cs +++ b/test/OpenTelemetry.Tests/Resources/ResourceTest.cs @@ -25,15 +25,19 @@ public class ResourceTest : IDisposable { private const string KeyName = "key"; private const string ValueName = "value"; - private const string OtelEnvVarKey = "OTEL_RESOURCE_ATTRIBUTES"; public ResourceTest() { - Environment.SetEnvironmentVariable(OtelEnvVarKey, null); + ClearEnvVars(); + } + + public void Dispose() + { + ClearEnvVars(); } [Fact] - public static void CreateResource_NullAttributeCollection() + public void CreateResource_NullAttributeCollection() { // Act and Assert var resource = new Resource(null); @@ -421,10 +425,10 @@ public void GetResourceWithDefaultAttributes_ResourceWithAttrs() } [Fact] - public void GetResourceWithDefaultAttributes_WithEnvVar() + public void GetResourceWithDefaultAttributes_WithResourceEnvVar() { // Arrange - Environment.SetEnvironmentVariable(OtelEnvVarKey, "EVKey1=EVVal1,EVKey2=EVVal2"); + Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "EVKey1=EVVal1,EVKey2=EVVal2"); var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddAttributes(this.CreateAttributes(2)).Build(); // Assert @@ -436,9 +440,54 @@ public void GetResourceWithDefaultAttributes_WithEnvVar() Assert.Contains(new KeyValuePair("EVKey2", "EVVal2"), attributes); } - public void Dispose() + [Fact] + public void GetResource_WithServiceEnvVar() + { + // Arrange + Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, "some-service"); + var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddAttributes(this.CreateAttributes(2)).Build(); + + // Assert + var attributes = resource.Attributes; + Assert.Equal(3, attributes.Count()); + ValidateAttributes(attributes, 0, 1); + Assert.Contains(new KeyValuePair("service.name", "some-service"), attributes); + } + + [Fact] + public void GetResource_WithServiceNameSetWithTwoEnvVars() + { + // Arrange + Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "service.name=from-resource-attr"); + Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, "from-service-name"); + var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddAttributes(this.CreateAttributes(2)).Build(); + + // Assert + var attributes = resource.Attributes; + Assert.Equal(3, attributes.Count()); + ValidateAttributes(attributes, 0, 1); + Assert.Contains(new KeyValuePair("service.name", "from-service-name"), attributes); + } + + [Fact] + public void GetResource_WithServiceNameSetWithTwoEnvVarsAndCode() + { + // Arrange + Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "service.name=from-resource-attr"); + Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, "from-service-name"); + var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddService("from-code").AddAttributes(this.CreateAttributes(2)).Build(); + + // Assert + var attributes = resource.Attributes; + Assert.Equal(4, attributes.Count()); + ValidateAttributes(attributes, 0, 1); + Assert.Contains(new KeyValuePair("service.name", "from-code"), attributes); + } + + private static void ClearEnvVars() { - Environment.SetEnvironmentVariable(OtelEnvVarKey, null); + Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, null); + Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, null); } private static void AddAttributes(Dictionary attributes, int attributeCount, int startIndex = 0) From 717ad74fbfa0aad75bc6bf37d4c526348041a544 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Tue, 3 Aug 2021 17:14:17 -0700 Subject: [PATCH 27/45] copy the code from ASP.NET repo (#2223) --- .../.gitattributes | 52 ++ .../.gitignore | 15 + .../.nuget/NuGet.Config | 7 + .../CODE-OF-CONDUCT.md | 6 + .../CONTRIBUTING.md | 25 + .../LICENSE.txt | 12 + .../Microsoft.AspNet.TelemetryCorrelation.sln | 46 ++ .../NuGet.Config | 7 + .../README.md | 59 ++ .../ActivityExtensions.cs | 146 +++++ .../ActivityHelper.cs | 149 +++++ .../AspNetTelemetryCorrelationEventSource.cs | 97 +++ .../Internal/BaseHeaderParser.cs | 70 +++ .../Internal/GenericHeaderParser.cs | 31 + .../Internal/HeaderUtilities.cs | 46 ++ .../Internal/HttpHeaderParser.cs | 27 + .../Internal/HttpParseResult.cs | 24 + .../Internal/HttpRuleParser.cs | 270 +++++++++ .../Internal/NameValueHeaderValue.cs | 117 ++++ ...crosoft.AspNet.TelemetryCorrelation.csproj | 87 +++ ...rosoft.AspNet.TelemetryCorrelation.ruleset | 77 +++ .../Properties/AssemblyInfo.cs | 14 + .../TelemetryCorrelationHttpModule.cs | 159 +++++ .../web.config.install.xdt | 48 ++ .../web.config.uninstall.xdt | 17 + .../ActivityExtensionsTest.cs | 312 ++++++++++ .../ActivityHelperTest.cs | 559 ++++++++++++++++++ .../HttpContextHelper.cs | 93 +++ ...t.AspNet.TelemetryCorrelation.Tests.csproj | 82 +++ .../Properties/AssemblyInfo.cs | 18 + .../PropertyExtensions.cs | 18 + .../TestDiagnosticListener.cs | 34 ++ .../WebConfigTransformTest.cs | 401 +++++++++++++ .../WebConfigWithLocationTagTransformTest.cs | 431 ++++++++++++++ .../tools/35MSSharedLib1024.snk | Bin 0 -> 160 bytes .../tools/Common.props | 64 ++ .../tools/Debug.snk | Bin 0 -> 596 bytes .../tools/stylecop.json | 15 + 38 files changed, 3635 insertions(+) create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/.gitignore create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/README.md create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/web.config.install.xdt create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/web.config.uninstall.xdt create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/35MSSharedLib1024.snk create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/Common.props create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/Debug.snk create mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/stylecop.json diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes b/src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes new file mode 100644 index 00000000000..d4ee1cb7f39 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes @@ -0,0 +1,52 @@ +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain + +*.jpg binary +*.png binary +*.gif binary + +*.cs text=auto diff=csharp +*.vb text=auto +*.resx text=auto +*.c text=auto +*.cpp text=auto +*.cxx text=auto +*.h text=auto +*.hxx text=auto +*.py text=auto +*.rb text=auto +*.java text=auto +*.html text=auto +*.htm text=auto +*.css text=auto +*.scss text=auto +*.sass text=auto +*.less text=auto +*.js text=auto +*.lisp text=auto +*.clj text=auto +*.sql text=auto +*.php text=auto +*.lua text=auto +*.m text=auto +*.asm text=auto +*.erl text=auto +*.fs text=auto +*.fsx text=auto +*.hs text=auto + +*.csproj text=auto +*.vbproj text=auto +*.fsproj text=auto +*.dbproj text=auto +*.sln text=auto eol=crlf + +*.sh eol=lf diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/.gitignore b/src/Microsoft.AspNet.TelemetryCorrelation/.gitignore new file mode 100644 index 00000000000..b27c3880583 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/.gitignore @@ -0,0 +1,15 @@ +[Oo]bj/ +[Bb]in/ +TestResults/ +_ReSharper.*/ +/packages/ +artifacts/ +PublishProfiles/ +*.user +*.suo +*.cache +*.sln.ide +.vs +.build/ +.testPublish/ +msbuild.* \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config b/src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config new file mode 100644 index 00000000000..d8f90e2617e --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md b/src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md new file mode 100644 index 00000000000..775f221c98e --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md @@ -0,0 +1,6 @@ +# Code of Conduct + +This project has adopted the code of conduct defined by the Contributor Covenant +to clarify expected behavior in our community. + +For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md b/src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md new file mode 100644 index 00000000000..1e61cc52e3e --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing + +Information on contributing to this repo is in the [Contributing +Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in +the Home repo. + +## Build and test + +1. Open project in Visual Studio 2017+. +2. Build and compile run unit tests right from Visual Studio. + +Command line: + +``` +dotnet build .\Microsoft.AspNet.TelemetryCorrelation.sln +dotnet test .\Microsoft.AspNet.TelemetryCorrelation.sln +dotnet pack .\Microsoft.AspNet.TelemetryCorrelation.sln +``` + +## Manual testing + +Follow readme to install http module to your application. + +Set `set PublicRelease=True` before build to produce delay-signed +assembly with the public key matching released version of assembly. \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt b/src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt new file mode 100644 index 00000000000..ed0ac7bc067 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt @@ -0,0 +1,12 @@ +Copyright (c) .NET Foundation. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +these files 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. \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln new file mode 100644 index 00000000000..4ea7f514bd5 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29306.81 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{258D5057-81B9-40EC-A872-D21E27452749}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.TelemetryCorrelation", "src\Microsoft.AspNet.TelemetryCorrelation\Microsoft.AspNet.TelemetryCorrelation.csproj", "{4C8E592C-C532-4CF2-80EF-3BDD0D788D12}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.TelemetryCorrelation.Tests", "test\Microsoft.AspNet.TelemetryCorrelation.Tests\Microsoft.AspNet.TelemetryCorrelation.Tests.csproj", "{9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{504D7010-38CC-4B07-BC57-D7030209D631}" + ProjectSection(SolutionItems) = preProject + tools\Common.props = tools\Common.props + NuGet.Config = NuGet.Config + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Release|Any CPU.Build.0 = Release|Any CPU + {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4C8E592C-C532-4CF2-80EF-3BDD0D788D12} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC} + {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED} = {258D5057-81B9-40EC-A872-D21E27452749} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6E28F11C-A0D8-461B-B71F-70F348C1BB53} + EndGlobalSection +EndGlobal diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config b/src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config new file mode 100644 index 00000000000..248a5bb51aa --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/README.md b/src/Microsoft.AspNet.TelemetryCorrelation/README.md new file mode 100644 index 00000000000..6d5f844c0cf --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/README.md @@ -0,0 +1,59 @@ +# Telemetry correlation http module + +[![NuGet](https://img.shields.io/nuget/v/Microsoft.AspNet.TelemetryCorrelation.svg)](https://www.nuget.org/packages/Microsoft.AspNet.TelemetryCorrelation/) + +Telemetry correlation http module enables cross tier telemetry tracking. + +## Usage + +1. Install NuGet for your app. +2. Enable diagnostics source listener using code below. Note, some + telemetry vendors like Azure Application Insights will enable it + automatically. + + ``` csharp + public class NoopDiagnosticsListener : IObserver> + { + public void OnCompleted() { } + + public void OnError(Exception error) { } + + public void OnNext(KeyValuePair evnt) + { + } + } + + public class NoopSubscriber : IObserver + { + public void OnCompleted() { } + + public void OnError(Exception error) { } + + public void OnNext(DiagnosticListener listener) + { + if (listener.Name == "Microsoft.AspNet.TelemetryCorrelation" || listener.Name == "System.Net.Http" ) + { + listener.Subscribe(new NoopDiagnosticsListener()); + } + } + } + ``` +3. Double check that http module was registered in `web.config` for your + app. + +Once enabled - this http module will: + +- Reads correlation http headers +- Start/Stops Activity for the http request +- Ensure the Activity ambient state is transferred thru the IIS + callbacks + +See http protocol [specifications][http-protocol-specification] for +details. + +This http module is used by Application Insights. See +[documentation][usage-in-ai-docs] and [code][usage-in-ai-code]. + +[http-protocol-specification]: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md +[usage-in-ai-docs]: https://docs.microsoft.com/azure/application-insights/application-insights-correlation +[usage-in-ai-code]: https://github.com/Microsoft/ApplicationInsights-dotnet-server diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs new file mode 100644 index 00000000000..04687e77baa --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs @@ -0,0 +1,146 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Extensions of Activity class + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ActivityExtensions + { + /// + /// Http header name to carry the Request Id: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md. + /// + internal const string RequestIdHeaderName = "Request-Id"; + + /// + /// Http header name to carry the traceparent: https://www.w3.org/TR/trace-context/. + /// + internal const string TraceparentHeaderName = "traceparent"; + + /// + /// Http header name to carry the tracestate: https://www.w3.org/TR/trace-context/. + /// + internal const string TracestateHeaderName = "tracestate"; + + /// + /// Http header name to carry the correlation context. + /// + internal const string CorrelationContextHeaderName = "Correlation-Context"; + + /// + /// Maximum length of Correlation-Context header value. + /// + internal const int MaxCorrelationContextLength = 1024; + + /// + /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. + /// + /// Instance of activity that has not been started yet. + /// Request headers collection. + /// true if request was parsed successfully, false - otherwise. + public static bool Extract(this Activity activity, NameValueCollection requestHeaders) + { + if (activity == null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("activity is null"); + return false; + } + + if (activity.ParentId != null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); + return false; + } + + if (activity.Id != null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("Activity is already started"); + return false; + } + + var parents = requestHeaders.GetValues(TraceparentHeaderName); + if (parents == null || parents.Length == 0) + { + parents = requestHeaders.GetValues(RequestIdHeaderName); + } + + if (parents != null && parents.Length > 0 && !string.IsNullOrEmpty(parents[0])) + { + // there may be several Request-Id or traceparent headers, but we only read the first one + activity.SetParentId(parents[0]); + + var tracestates = requestHeaders.GetValues(TracestateHeaderName); + if (tracestates != null && tracestates.Length > 0) + { + if (tracestates.Length == 1 && !string.IsNullOrEmpty(tracestates[0])) + { + activity.TraceStateString = tracestates[0]; + } + else + { + activity.TraceStateString = string.Join(",", tracestates); + } + } + + // Header format - Correlation-Context: key1=value1, key2=value2 + var baggages = requestHeaders.GetValues(CorrelationContextHeaderName); + if (baggages != null) + { + int correlationContextLength = -1; + + // there may be several Correlation-Context header + foreach (var item in baggages) + { + if (correlationContextLength >= MaxCorrelationContextLength) + { + break; + } + + foreach (var pair in item.Split(',')) + { + correlationContextLength += pair.Length + 1; // pair and comma + + if (correlationContextLength >= MaxCorrelationContextLength) + { + break; + } + + if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem)) + { + activity.AddBaggage(baggageItem.Name, baggageItem.Value); + } + else + { + AspNetTelemetryCorrelationEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); + } + } + } + } + + return true; + } + + return false; + } + + /// + /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. + /// + /// Instance of activity that has not been started yet. + /// Request headers collection. + /// true if request was parsed successfully, false - otherwise. + [Obsolete("Method is obsolete, use Extract method instead", true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool TryParse(this Activity activity, NameValueCollection requestHeaders) + { + return Extract(activity, requestHeaders); + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs new file mode 100644 index 00000000000..a625df5e1dd --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs @@ -0,0 +1,149 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Diagnostics; +using System.Web; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Activity helper class + /// + internal static class ActivityHelper + { + /// + /// Listener name. + /// + public const string AspNetListenerName = "Microsoft.AspNet.TelemetryCorrelation"; + + /// + /// Activity name for http request. + /// + public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; + + /// + /// Event name for the activity start event. + /// + public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start"; + + /// + /// Key to store the activity in HttpContext. + /// + public const string ActivityKey = "__AspnetActivity__"; + + private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName); + + private static readonly object EmptyPayload = new object(); + + /// + /// Stops the activity and notifies listeners about it. + /// + /// HttpContext.Items. + public static void StopAspNetActivity(IDictionary contextItems) + { + var currentActivity = Activity.Current; + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + + if (currentActivity != aspNetActivity) + { + Activity.Current = aspNetActivity; + currentActivity = aspNetActivity; + } + + if (currentActivity != null) + { + // stop Activity with Stop event + AspNetListener.StopActivity(currentActivity, EmptyPayload); + contextItems[ActivityKey] = null; + } + + AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); + } + + /// + /// Creates root (first level) activity that describes incoming request. + /// + /// Current HttpContext. + /// Determines if headers should be parsed get correlation ids. + /// New root activity. + public static Activity CreateRootActivity(HttpContext context, bool parseHeaders) + { + if (AspNetListener.IsEnabled() && AspNetListener.IsEnabled(AspNetActivityName)) + { + var rootActivity = new Activity(AspNetActivityName); + + if (parseHeaders) + { + rootActivity.Extract(context.Request.Unvalidated.Headers); + } + + AspNetListener.OnActivityImport(rootActivity, null); + + if (StartAspNetActivity(rootActivity)) + { + context.Items[ActivityKey] = rootActivity; + AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id); + return rootActivity; + } + } + + return null; + } + + public static void WriteActivityException(IDictionary contextItems, Exception exception) + { + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + + if (aspNetActivity != null) + { + if (Activity.Current != aspNetActivity) + { + Activity.Current = aspNetActivity; + } + + AspNetListener.Write(aspNetActivity.OperationName + ".Exception", exception); + AspNetTelemetryCorrelationEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, 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. + /// + /// HttpContext.Items dictionary. + internal static void RestoreActivityIfNeeded(IDictionary contextItems) + { + if (Activity.Current == null) + { + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + if (aspNetActivity != null) + { + Activity.Current = aspNetActivity; + } + } + } + + private static bool StartAspNetActivity(Activity activity) + { + if (AspNetListener.IsEnabled(AspNetActivityName, activity, EmptyPayload)) + { + if (AspNetListener.IsEnabled(AspNetActivityStartName)) + { + AspNetListener.StartActivity(activity, EmptyPayload); + } + else + { + activity.Start(); + } + + return true; + } + + return false; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs new file mode 100644 index 00000000000..9748b57a015 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs @@ -0,0 +1,97 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Tracing; +#pragma warning disable SA1600 // Elements must be documented + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// ETW EventSource tracing class. + /// + [EventSource(Name = "Microsoft-AspNet-Telemetry-Correlation", Guid = "ace2021e-e82c-5502-d81d-657f27612673")] + internal sealed class AspNetTelemetryCorrelationEventSource : EventSource + { + /// + /// Instance of the PlatformEventSource class. + /// + public static readonly AspNetTelemetryCorrelationEventSource Log = new AspNetTelemetryCorrelationEventSource(); + + [NonEvent] + public void ActivityException(string id, string eventName, Exception ex) + { + if (IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + { + ActivityException(id, eventName, ex.ToString()); + } + } + + [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] + public void TraceCallback(string callback) + { + WriteEvent(1, callback); + } + + [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] + public void ActivityStarted(string id) + { + WriteEvent(2, id); + } + + [Event(3, Message = "Activity stopped, Id='{0}', Name='{1}'", Level = EventLevel.Verbose)] + public void ActivityStopped(string id, string eventName) + { + WriteEvent(3, id, eventName); + } + + [Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)] + public void HeaderParsingError(string headerName, string headerValue) + { + WriteEvent(4, headerName, headerValue); + } + + [Event(5, Message = "Failed to extract activity, reason '{0}'", Level = EventLevel.Error)] + public void ActvityExtractionError(string reason) + { + WriteEvent(5, reason); + } + + [Event(6, Message = "Finished Activity is detected on the stack, Id: '{0}', Name: '{1}'", Level = EventLevel.Error)] + public void FinishedActivityIsDetected(string id, string name) + { + WriteEvent(6, id, name); + } + + [Event(7, Message = "System.Diagnostics.Activity stack is too deep. This is a code authoring error, Activity will not be stopped.", Level = EventLevel.Error)] + public void ActivityStackIsTooDeepError() + { + WriteEvent(7); + } + + [Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] + public void ActivityRestored(string id) + { + WriteEvent(8, id); + } + + [Event(9, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] + public void OnExecuteRequestStepInvokationError(string error) + { + WriteEvent(9, error); + } + + [Event(10, Message = "System.Diagnostics.Activity stack is too deep. Current Id: '{0}', Name: '{1}'", Level = EventLevel.Warning)] + public void ActivityStackIsTooDeepDetails(string id, string name) + { + WriteEvent(10, id, name); + } + + [Event(11, Message = "Activity exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] + public void ActivityException(string id, string eventName, string ex) + { + WriteEvent(11, id, eventName, ex); + } + } +} +#pragma warning restore SA1600 // Elements must be documented \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs new file mode 100644 index 00000000000..2186f0930b3 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs + internal abstract class BaseHeaderParser : HttpHeaderParser + { + protected BaseHeaderParser(bool supportsMultipleValues) + : base(supportsMultipleValues) + { + } + + public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) + { + parsedValue = default(T); + + // If multiple values are supported (i.e. list of values), then accept an empty string: The header may + // be added multiple times to the request/response message. E.g. + // Accept: text/xml; q=1 + // Accept: + // Accept: text/plain; q=0.2 + if (string.IsNullOrEmpty(value) || (index == value.Length)) + { + return SupportsMultipleValues; + } + + var separatorFound = false; + var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, SupportsMultipleValues, out separatorFound); + + if (separatorFound && !SupportsMultipleValues) + { + return false; // leading separators not allowed if we don't support multiple values. + } + + if (current == value.Length) + { + if (SupportsMultipleValues) + { + index = current; + } + + return SupportsMultipleValues; + } + + T result; + var length = GetParsedValueLength(value, current, out result); + + if (length == 0) + { + return false; + } + + current = current + length; + current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues, out separatorFound); + + // If we support multiple values and we've not reached the end of the string, then we must have a separator. + if ((separatorFound && !SupportsMultipleValues) || (!separatorFound && (current < value.Length))) + { + return false; + } + + index = current; + parsedValue = result; + return true; + } + + protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs new file mode 100644 index 00000000000..daa6d308060 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs + internal sealed class GenericHeaderParser : BaseHeaderParser + { + private GetParsedValueLengthDelegate getParsedValueLength; + + internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) + : base(supportsMultipleValues) + { + if (getParsedValueLength == null) + { + throw new ArgumentNullException(nameof(getParsedValueLength)); + } + + this.getParsedValueLength = getParsedValueLength; + } + + internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); + + protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) + { + return getParsedValueLength(value, startIndex, out parsedValue); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs new file mode 100644 index 00000000000..7403f97b0c7 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs + internal static class HeaderUtilities + { + internal static int GetNextNonEmptyOrWhitespaceIndex( + string input, + int startIndex, + bool skipEmptyValues, + out bool separatorFound) + { + Contract.Requires(input != null); + Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length. + + separatorFound = false; + var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex); + + if ((current == input.Length) || (input[current] != ',')) + { + return current; + } + + // If we have a separator, skip the separator and all following whitespaces. If we support + // empty values, continue until the current character is neither a separator nor a whitespace. + separatorFound = true; + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + if (skipEmptyValues) + { + while ((current < input.Length) && (input[current] == ',')) + { + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + } + } + + return current; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs new file mode 100644 index 00000000000..61d9172080e --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs + internal abstract class HttpHeaderParser + { + private bool supportsMultipleValues; + + protected HttpHeaderParser(bool supportsMultipleValues) + { + this.supportsMultipleValues = supportsMultipleValues; + } + + public bool SupportsMultipleValues + { + get { return supportsMultipleValues; } + } + + // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' + // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 + // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first + // non-whitespace after the separator ','. + public abstract bool TryParseValue(string value, ref int index, out T parsedValue); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs new file mode 100644 index 00000000000..2f2ff6c50ba --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs + internal enum HttpParseResult + { + /// + /// Parsed succesfully. + /// + Parsed, + + /// + /// Was not parsed. + /// + NotParsed, + + /// + /// Invalid format. + /// + InvalidFormat, + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs new file mode 100644 index 00000000000..5ede3faa54e --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs @@ -0,0 +1,270 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs + internal static class HttpRuleParser + { + internal const char CR = '\r'; + internal const char LF = '\n'; + internal const char SP = ' '; + internal const char Tab = '\t'; + internal const int MaxInt64Digits = 19; + internal const int MaxInt32Digits = 10; + + private const int MaxNestedCount = 5; + private static readonly bool[] TokenChars = CreateTokenChars(); + + internal static bool IsTokenChar(char character) + { + // Must be between 'space' (32) and 'DEL' (127) + if (character > 127) + { + return false; + } + + return TokenChars[character]; + } + + internal static int GetTokenLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + while (current < input.Length) + { + if (!IsTokenChar(input[current])) + { + return current - startIndex; + } + + current++; + } + + return input.Length - startIndex; + } + + internal static int GetWhitespaceLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + char c; + while (current < input.Length) + { + c = input[current]; + + if ((c == SP) || (c == Tab)) + { + current++; + continue; + } + + if (c == CR) + { + // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. + if ((current + 2 < input.Length) && (input[current + 1] == LF)) + { + char spaceOrTab = input[current + 2]; + if ((spaceOrTab == SP) || (spaceOrTab == Tab)) + { + current += 3; + continue; + } + } + } + + return current - startIndex; + } + + // All characters between startIndex and the end of the string are LWS characters. + return input.Length - startIndex; + } + + internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) + { + var nestedCount = 0; + return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); + } + + // quoted-pair = "\" CHAR + // CHAR = + internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && + (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); + + length = 0; + + if (input[startIndex] != '\\') + { + return HttpParseResult.NotParsed; + } + + // Quoted-char has 2 characters. Check wheter there are 2 chars left ('\' + char) + // If so, check whether the character is in the range 0-127. If not, it's an invalid value. + if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) + { + return HttpParseResult.InvalidFormat; + } + + // We don't care what the char next to '\' is. + length = 2; + return HttpParseResult.Parsed; + } + + private static bool[] CreateTokenChars() + { + // token = 1* + // CTL = + var tokenChars = new bool[128]; // everything is false + + for (int i = 33; i < 127; i++) + { + // skip Space (32) & DEL (127) + tokenChars[i] = true; + } + + // remove separators: these are not valid token characters + tokenChars[(byte)'('] = false; + tokenChars[(byte)')'] = false; + tokenChars[(byte)'<'] = false; + tokenChars[(byte)'>'] = false; + tokenChars[(byte)'@'] = false; + tokenChars[(byte)','] = false; + tokenChars[(byte)';'] = false; + tokenChars[(byte)':'] = false; + tokenChars[(byte)'\\'] = false; + tokenChars[(byte)'"'] = false; + tokenChars[(byte)'/'] = false; + tokenChars[(byte)'['] = false; + tokenChars[(byte)']'] = false; + tokenChars[(byte)'?'] = false; + tokenChars[(byte)'='] = false; + tokenChars[(byte)'{'] = false; + tokenChars[(byte)'}'] = false; + + return tokenChars; + } + + // TEXT = + // LWS = [CRLF] 1*( SP | HT ) + // CTL = + // + // Since we don't really care about the content of a quoted string or comment, we're more tolerant and + // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). + // + // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like + // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested + // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) + // is unusual. + private static HttpParseResult GetExpressionLength( + string input, + int startIndex, + char openChar, + char closeChar, + bool supportsNesting, + ref int nestedCount, + out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || + (Contract.ValueAtReturn(out length) > 0)); + + length = 0; + + if (input[startIndex] != openChar) + { + return HttpParseResult.NotParsed; + } + + var current = startIndex + 1; // Start parsing with the character next to the first open-char + while (current < input.Length) + { + // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. + // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. + var quotedPairLength = 0; + if ((current + 2 < input.Length) && + (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) + { + // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, + // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only + // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars). + current = current + quotedPairLength; + continue; + } + + // If we support nested expressions and we find an open-char, then parse the nested expressions. + if (supportsNesting && (input[current] == openChar)) + { + nestedCount++; + try + { + // Check if we exceeded the number of nested calls. + if (nestedCount > MaxNestedCount) + { + return HttpParseResult.InvalidFormat; + } + + var nestedLength = 0; + HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); + + switch (nestedResult) + { + case HttpParseResult.Parsed: + current += nestedLength; // add the length of the nested expression and continue. + break; + + case HttpParseResult.NotParsed: + Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression parsing, because we found the open-char. So either it's a valid nested expression or it has invalid format."); + break; + + case HttpParseResult.InvalidFormat: + // If the nested expression is invalid, we can't continue, so we fail with invalid format. + return HttpParseResult.InvalidFormat; + + default: + Contract.Assert(false, "Unknown enum result: " + nestedResult); + break; + } + } + finally + { + nestedCount--; + } + } + + if (input[current] == closeChar) + { + length = current - startIndex + 1; + return HttpParseResult.Parsed; + } + + current++; + } + + // We didn't see the final quote, therefore we have an invalid expression string. + return HttpParseResult.InvalidFormat; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs new file mode 100644 index 00000000000..ecae4e11df8 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs @@ -0,0 +1,117 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs + + // According to the RFC, in places where a "parameter" is required, the value is mandatory + // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports + // name-only values in addition to name/value pairs. + internal class NameValueHeaderValue + { + private static readonly HttpHeaderParser SingleValueParser + = new GenericHeaderParser(false, GetNameValueLength); + + private string name; + private string value; + + private NameValueHeaderValue() + { + // Used by the parser to create a new instance of this type. + } + + public string Name + { + get { return name; } + } + + public string Value + { + get { return value; } + } + + public static bool TryParse(string input, out NameValueHeaderValue parsedValue) + { + var index = 0; + return SingleValueParser.TryParseValue(input, ref index, out parsedValue); + } + + internal static int GetValueLength(string input, int startIndex) + { + Contract.Requires(input != null); + + if (startIndex >= input.Length) + { + return 0; + } + + var valueLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (valueLength == 0) + { + // A value can either be a token or a quoted string. Check if it is a quoted string. + if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed) + { + // We have an invalid value. Reset the name and return. + return 0; + } + } + + return valueLength; + } + + private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue) + { + Contract.Requires(input != null); + Contract.Requires(startIndex >= 0); + + parsedValue = null; + + if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) + { + return 0; + } + + // Parse the name, i.e. in name/value string "=". Caller must remove + // leading whitespaces. + var nameLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (nameLength == 0) + { + return 0; + } + + var name = input.Substring(startIndex, nameLength); + var current = startIndex + nameLength; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the separator between name and value + if ((current == input.Length) || (input[current] != '=')) + { + // We only have a name and that's OK. Return. + parsedValue = new NameValueHeaderValue(); + parsedValue.name = name; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the value, i.e. in name/value string "=" + int valueLength = GetValueLength(input, current); + + // Value after the '=' may be empty + // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. + parsedValue = new NameValueHeaderValue(); + parsedValue.name = name; + parsedValue.value = input.Substring(current, valueLength); + current = current + valueLength; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj new file mode 100644 index 00000000000..3c684dd1564 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj @@ -0,0 +1,87 @@ + + + + {4C8E592C-C532-4CF2-80EF-3BDD0D788D12} + Library + Properties + Microsoft.AspNet.TelemetryCorrelation + Microsoft.AspNet.TelemetryCorrelation + net45 + 512 + true + $(OutputPath)$(AssemblyName).xml + ..\..\ + Microsoft.AspNet.TelemetryCorrelation.ruleset + true + prompt + 4 + true + $(OutputPath)/$(TargetFramework)/$(AssemblyName).xml + + + true + $(RepositoryRoot)tools\35MSSharedLib1024.snk + $(DefineConstants);PUBLIC_RELEASE + + + false + $(RepositoryRoot)tools\Debug.snk + + + full + false + $(DefineConstants);DEBUG;TRACE + + + pdbonly + true + $(DefineConstants);TRACE + + + Microsoft Corporation + + True + True + snupkg + + Microsoft.AspNet.TelemetryCorrelation + + + Microsoft + Microsoft Asp.Net telemetry correlation + A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. + © Microsoft Corporation. All rights reserved. + Apache-2.0 + http://www.asp.net/ + http://go.microsoft.com/fwlink/?LinkID=288859 + Diagnostics DiagnosticSource Correlation Activity ASP.NET + https://github.com/aspnet/Microsoft.AspNet.TelemetryCorrelation/ + Git + Dependency + content + + + + + + All + + + All + + + All + + + All + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset new file mode 100644 index 00000000000..a13b89aa163 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..bc6fa30b168 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 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)] + +#if PUBLIC_RELEASE +[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +#else +[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100319b35b21a993df850846602dae9e86d8fbb0528a0ad488ecd6414db798996534381825f94f90d8b16b72a51c4e7e07cf66ff3293c1046c045fafc354cfcc15fc177c748111e4a8c5a34d3940e7f3789dd58a928add6160d6f9cc219680253dcea88a034e7d472de51d4989c7783e19343839273e0e63a43b99ab338149dd59f")] +#endif \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs new file mode 100644 index 00000000000..55827833858 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs @@ -0,0 +1,159 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Reflection; +using System.Web; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Http Module sets ambient state using Activity API from DiagnosticsSource package. + /// + public class TelemetryCorrelationHttpModule : IHttpModule + { + private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled"; + + // 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 MethodInfo onStepMethodInfo = null; + + static TelemetryCorrelationHttpModule() + { + onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); + } + + /// + /// Gets or sets a value indicating whether TelemetryCorrelationHttpModule should parse headers to get correlation ids. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ParseHeaders { get; set; } = true; + + /// + public void Dispose() + { + } + + /// + public void Init(HttpApplication context) + { + context.BeginRequest += Application_BeginRequest; + context.EndRequest += Application_EndRequest; + context.Error += Application_Error; + + // OnExecuteRequestStep is availabile starting with 4.7.1 + // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), + // we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute + if (onStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) + { + try + { + onStepMethodInfo.Invoke(context, new object[] { (Action)OnExecuteRequestStep }); + } + catch (Exception e) + { + AspNetTelemetryCorrelationEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); + } + } + else + { + context.PreRequestHandlerExecute += Application_PreRequestHandlerExecute; + } + } + + /// + /// Restores Activity before each pipeline step if it was lost. + /// + /// HttpContext instance. + /// Step to be executed. + internal void OnExecuteRequestStep(HttpContextBase context, Action step) + { + // Once we have public Activity.Current setter (https://github.com/dotnet/corefx/issues/29207) this method will be + // simplified to just assign Current if is was lost. + // In the mean time, we are creating child Activity to restore the context. We have to send + // event with this Activity to tracing system. It created a lot of issues for listeners as + // we may potentially have a lot of them for different stages. + // To reduce amount of events, we only care about ExecuteRequestHandler stage - restore activity here and + // stop/report it to tracing system in EndRequest. + if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) + { + ActivityHelper.RestoreActivityIfNeeded(context.Items); + } + + step(); + } + + private void Application_BeginRequest(object sender, EventArgs e) + { + var context = ((HttpApplication)sender).Context; + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); + ActivityHelper.CreateRootActivity(context, ParseHeaders); + context.Items[BeginCalledFlag] = true; + } + + private void Application_PreRequestHandlerExecute(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); + ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items); + } + + private void Application_EndRequest(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_EndRequest"); + bool trackActivity = true; + + var context = ((HttpApplication)sender).Context; + + // EndRequest does it's best effort to notify that request has ended + // BeginRequest has never been called + if (!context.Items.Contains(BeginCalledFlag)) + { + // 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 pipleline 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 + ActivityHelper.CreateRootActivity(context, ParseHeaders); + } + } + + if (trackActivity) + { + ActivityHelper.StopAspNetActivity(context.Items); + } + } + + private void Application_Error(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_Error"); + + var context = ((HttpApplication)sender).Context; + + var exception = context.Error; + if (exception != null) + { + if (!context.Items.Contains(BeginCalledFlag)) + { + ActivityHelper.CreateRootActivity(context, ParseHeaders); + } + + ActivityHelper.WriteActivityException(context.Items, exception); + } + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/web.config.install.xdt b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/web.config.install.xdt new file mode 100644 index 00000000000..b2ab96ba36d --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/web.config.install.xdt @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/web.config.uninstall.xdt b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/web.config.uninstall.xdt new file mode 100644 index 00000000000..7ed679c2954 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/web.config.uninstall.xdt @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs new file mode 100644 index 00000000000..b0ca6135d61 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs @@ -0,0 +1,312 @@ +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Linq; + using Xunit; + + public class ActivityExtensionsTest + { + private const string TestActivityName = "Activity.Test"; + + [Fact] + public void Restore_Nothing_If_Header_Does_Not_Contain_RequestId() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection(); + + Assert.False(activity.Extract(requestHeaders)); + + Assert.True(string.IsNullOrEmpty(activity.ParentId)); + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Restore_First_RequestId_When_Multiple_RequestId_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Extract_RequestId_Is_Ignored_When_Traceparent_Is_Present() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" } + }; + Assert.True(activity.Extract(requestHeaders)); + + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.False(activity.Recorded); + + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Extract_First_Traceparent_When_Multiple_Traceparents_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" } + }; + Assert.True(activity.Extract(requestHeaders)); + + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.False(activity.Recorded); + + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Extract_RootActivity_From_W3C_Headers_And_CC() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + }; + + Assert.True(activity.Extract(requestHeaders)); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(activity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Extract_Empty_Traceparent() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, string.Empty }, + }; + + Assert.False(activity.Extract(requestHeaders)); + + Assert.Equal(default, activity.ParentSpanId); + Assert.Null(activity.ParentId); + } + + [Fact] + public void Can_Extract_Multi_Line_Tracestate() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1" }, + { ActivityExtensions.TracestateHeaderName, "ts2=v2" }, + }; + + Assert.True(activity.Extract(requestHeaders)); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(activity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); + } + + [Fact] + public void Restore_Empty_RequestId_Should_Not_Throw_Exception() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, string.Empty } + }; + Assert.False(activity.Extract(requestHeaders)); + + Assert.Null(activity.ParentId); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Restore_Empty_Traceparent_Should_Not_Throw_Exception() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, string.Empty } + }; + Assert.False(activity.Extract(requestHeaders)); + + Assert.Null(activity.ParentId); + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + { ActivityExtensions.CorrelationContextHeaderName, "key4=abc,key5=def" }, + { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + new KeyValuePair("key4", "abc"), + new KeyValuePair("key5", "def"), + new KeyValuePair("key6", "xyz") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + { ActivityExtensions.CorrelationContextHeaderName, "key4=abc;key5=def" }, + { ActivityExtensions.CorrelationContextHeaderName, "key6????xyz" }, + { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Theory] + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx", 1023)] // 1023 chars + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx1", 1024)] // 1024 chars + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,key74=value74", 1029)] // more than 1024 chars + public void Validates_Correlation_Context_Length(string correlationContext, int expectedLength) + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|abc.1" }, + { ActivityExtensions.CorrelationContextHeaderName, correlationContext } + }; + Assert.True(activity.Extract(requestHeaders)); + + var baggageItems = Enumerable.Range(0, 74).Select(i => new KeyValuePair("key" + i, "value" + i)).ToList(); + if (expectedLength < 1024) + { + baggageItems.Add(new KeyValuePair("k100", "vx")); + } + + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs new file mode 100644 index 00000000000..d4f28b8895e --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs @@ -0,0 +1,559 @@ +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + using Xunit; + + public class ActivityHelperTest : IDisposable + { + private const string TestActivityName = "Activity.Test"; + private readonly List> baggageItems; + private readonly string baggageInHeader; + private IDisposable subscriptionAllListeners; + private IDisposable subscriptionAspNetListener; + + public ActivityHelperTest() + { + this.baggageItems = new List> + { + new KeyValuePair("TestKey1", "123"), + new KeyValuePair("TestKey2", "456"), + new KeyValuePair("TestKey1", "789") + }; + + this.baggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; + + // reset static fields + var allListenerField = typeof(DiagnosticListener). + GetField("s_allListenerObservable", BindingFlags.Static | BindingFlags.NonPublic); + allListenerField.SetValue(null, null); + var aspnetListenerField = typeof(ActivityHelper). + GetField("AspNetListener", BindingFlags.Static | BindingFlags.NonPublic); + aspnetListenerField.SetValue(null, new DiagnosticListener(ActivityHelper.AspNetListenerName)); + } + + public void Dispose() + { + this.subscriptionAspNetListener?.Dispose(); + this.subscriptionAllListeners?.Dispose(); + } + + [Fact] + public void Can_Restore_Activity() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.RestoreActivityIfNeeded(context.Items); + + Assert.Same(Activity.Current, rootActivity); + } + + [Fact] + public void Can_Stop_Lost_Activity() + { + this.EnableAll(pair => + { + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Not_Stop_Lost_Activity_If_Not_In_Context() + { + this.EnableAll(pair => + { + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + context.Items.Remove(ActivityHelper.ActivityKey); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(rootActivity.Duration == TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() + { + this.EnableAll(); + ActivityHelper.RestoreActivityIfNeeded(HttpContextHelper.GetFakeHttpContext().Items); + + Assert.Null(Activity.Current); + } + + [Fact] + public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() + { + this.EnableAll(); + var root = new Activity("root").Start(); + + var context = HttpContextHelper.GetFakeHttpContext(); + context.Items[ActivityHelper.ActivityKey] = root; + + var module = new TelemetryCorrelationHttpModule(); + + ActivityHelper.RestoreActivityIfNeeded(context.Items); + + Assert.Equal(root, Activity.Current); + } + + [Fact] + public void Can_Stop_Activity_Without_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = this.CreateActivity(); + rootActivity.Start(); + context.Items[ActivityHelper.ActivityKey] = rootActivity; + Thread.Sleep(100); + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Activity_With_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = this.CreateActivity(); + rootActivity.Start(); + context.Items[ActivityHelper.ActivityKey] = rootActivity; + Thread.Sleep(100); + this.EnableAspNetListenerOnly(); + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Root_Activity_With_All_Children() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + + var child = new Activity("child").Start(); + new Activity("grandchild").Start(); + + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Root_While_Child_Is_Current() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + var child = new Activity("child").Start(); + + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void OnImportActivity_Is_Called() + { + bool onImportIsCalled = false; + Activity importedActivity = null; + this.EnableAll(onImport: (activity, _) => + { + onImportIsCalled = true; + importedActivity = activity; + Assert.Null(Activity.Current); + }); + + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + Assert.True(onImportIsCalled); + Assert.NotNull(importedActivity); + Assert.Equal(importedActivity, Activity.Current); + Assert.Equal(importedActivity, rootActivity); + } + + [Fact] + public void OnImportActivity_Can_Set_Parent() + { + this.EnableAll(onImport: (activity, _) => + { + Assert.Null(activity.ParentId); + activity.SetParentId("|guid.123."); + }); + + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + + Assert.Equal("|guid.123.", Activity.Current.ParentId); + } + + [Fact] + public async Task Can_Stop_Root_Activity_If_It_Is_Broken() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var root = ActivityHelper.CreateRootActivity(context, false); + 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(context.Items); + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + Assert.Null(Activity.Current); + } + + [Fact] + public void Stop_Root_Activity_With_129_Nesting_Depth() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var root = ActivityHelper.CreateRootActivity(context, false); + + for (int i = 0; i < 129; i++) + { + new Activity("child" + i).Start(); + } + + // can stop any activity regardless of the stack depth + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + Assert.Null(Activity.Current); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerOnly(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled_With_Arguments() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndDisableActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Can_Create_RootActivity_And_Restore_Info_From_Request_Header() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.True(rootActivity.ParentId == "|aba2f1e978b2cab6.1."); + var expectedBaggage = this.baggageItems.OrderBy(item => item.Value); + var actualBaggage = rootActivity.Baggage.OrderBy(item => item.Value); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Create_RootActivity_From_W3C_Traceparent() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + 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.False(rootActivity.Recorded); + + Assert.Null(rootActivity.TraceStateString); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + 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); + } + + [Fact] + public void Can_Create_RootActivity_And_Ignore_Info_From_Request_Header_If_ParseHeaders_Is_False() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, parseHeaders: false); + + Assert.NotNull(rootActivity); + Assert.Null(rootActivity.ParentId); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivity_And_Start_Activity() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); + } + + [Fact] + public void Can_Create_RootActivity_And_Saved_In_HttContext() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.Same(rootActivity, context.Items[ActivityHelper.ActivityKey]); + } + + private Activity CreateActivity() + { + var activity = new Activity(TestActivityName); + this.baggageItems.ForEach(kv => activity.AddBaggage(kv.Key, kv.Value)); + + return activity; + } + + private void EnableAll(Action> onNext = null, Action onImport = null) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, a1, a2) => true, + (a, o) => onImport?.Invoke(a, o), + (a, o) => { }); + } + }); + } + + private void EnableAspNetListenerAndDisableActivity( + Action> onNext = null, + string activityName = ActivityHelper.AspNetActivityName) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, arg1, arg2) => name == activityName && arg1 == null); + } + }); + } + + private void EnableAspNetListenerAndActivity( + Action> onNext = null, + string activityName = ActivityHelper.AspNetActivityName) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, arg1, arg2) => name == activityName); + } + }); + } + + private void EnableAspNetListenerOnly(Action> onNext = null) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + activityName => false); + } + }); + } + + private class TestHttpRequest : HttpRequestBase + { + private readonly NameValueCollection headers = new NameValueCollection(); + + 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; } + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs new file mode 100644 index 00000000000..a33ae70f06d --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.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 = HttpWorkerRequest.GetKnownRequestHeaderName(index); + + if (this.headers.ContainsKey(name)) + { + return this.headers[name]; + } + + return base.GetKnownRequestHeader(index); + } + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj new file mode 100644 index 00000000000..93c85be63f8 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj @@ -0,0 +1,82 @@ + + + + Debug + AnyCPU + {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED} + Library + net452 + 512 + prompt + 4 + + + true + full + false + $(DefineConstants);DEBUG;TRACE + + + pdbonly + true + $(DefineConstants);TRACE + + + true + + + true + $(RepositoryRoot)tools\35MSSharedLib1024.snk + $(DefineConstants);PUBLIC_RELEASE + + + false + $(RepositoryRoot)tools\Debug.snk + + + + + + + + + + + + + + + + + + + + + + + + + + + All + + + All + + + {4c8e592c-c532-4cf2-80ef-3bdd0d788d12} + Microsoft.AspNet.TelemetryCorrelation + + + + + Resources\web.config.install.xdt + + + Resources\web.config.uninstall.xdt + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..dd7ff9a4b19 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +using System.Runtime.InteropServices; +using Xunit; + +// 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("9fae5c43-f56c-4d87-a23c-6d2d57b4abed")] + +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs new file mode 100644 index 00000000000..696b08f8993 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Reflection; + + internal static class PropertyExtensions + { + public static object GetProperty(this object obj, string propertyName) + { + return obj.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(obj); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs new file mode 100644 index 00000000000..6bb5445cd48 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System; + using System.Collections.Generic; + + internal class TestDiagnosticListener : IObserver> + { + private readonly Action> onNextCallBack; + + public TestDiagnosticListener(Action> onNext) + { + this.onNextCallBack = onNext; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(KeyValuePair value) + { + this.onNextCallBack?.Invoke(value); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs new file mode 100644 index 00000000000..1db0bd43ef5 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs @@ -0,0 +1,401 @@ +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.IO; + using System.Xml.Linq; + using Microsoft.Web.XmlTransform; + using Xunit; + + public class WebConfigTransformTest + { + private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; + private const string UninstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.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; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs new file mode 100644 index 00000000000..5fcdbdd0ad8 --- /dev/null +++ b/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs @@ -0,0 +1,431 @@ +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.IO; + using System.Xml.Linq; + using Microsoft.Web.XmlTransform; + using Xunit; + + public class WebConfigWithLocationTagTransformTest + { + private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.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/src/Microsoft.AspNet.TelemetryCorrelation/tools/35MSSharedLib1024.snk b/src/Microsoft.AspNet.TelemetryCorrelation/tools/35MSSharedLib1024.snk new file mode 100644 index 0000000000000000000000000000000000000000..695f1b38774e839e5b90059bfb7f32df1dff4223 GIT binary patch literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa50098C{E+7Ye`kjtcRG*W zi8#m|)B?I?xgZ^2Sw5D;l4TxtPwG;3)3^j?qDHjEteSTF{rM+4WI`v zCD?tsZ^;k+S&r1&HRMb=j738S=;J$tCKNrc$@P|lZ + + + 7.3 + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Microsoft.AspNet.TelemetryCorrelation.sln))\ + + + + Release + $(RepositoryRoot)bin\ + $(RepositoryRoot)obj\ + $(BinPath)$(Configuration)\ + $(ObjPath)$(Configuration)\$(MSBuildProjectName)\ + + + + + + + + + rtm + 2018 + 1 + 0 + 9 + + $([MSBuild]::Add(1, $([MSBuild]::Subtract($([System.DateTime]::Now.Year), $(VersionStartYear)))))$([System.DateTime]::Now.ToString("MMdd")) + 0 + 0 + + + + $(VersionMajor).$(VersionMinor).$(VersionRelease) + $(BuildQuality) + $(VersionMajor).$(VersionMinor).$(VersionBuild).$(VersionRevision) + $(VersionMajor).$(VersionMinor).$(VersionRelease)-$(VersionBuild) + $(VersionMajor).$(VersionMinor).$(VersionRelease).0 + + + + Release + $(RepositoryRoot)bin\$(Configuration)\ + $(RepositoryRoot)obj\$(Configuration)\$(MSBuildProjectName)\ + + + + + + + Microsoft400 + + MsSharedLib72 + + + + + + + + diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/tools/Debug.snk b/src/Microsoft.AspNet.TelemetryCorrelation/tools/Debug.snk new file mode 100644 index 0000000000000000000000000000000000000000..00c211eeee2ef3ece4dbc6359939cafd9b5000e5 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096on>Df;nLYSWgk}QT>F8~by9Fqqtw@f| zWE9(ZiI!7Cfr4L@`3;K}w<=M@=iq$yZ}TZU5Jtd7`usIa{J~$rcgIK(9!iW_G}Dw0 ze>aKUSg9zj))ozKoWdDs0#n@Th@dp*)N%igW;1ygOYRL<~l>UnzJ|*ozup=81B;$ZY}?ryl-GiXWrD4_^-?Z%d7TB3qeUcEYu*GCmnsuCrN|NRYF!S+593-qL7JIUHTFCb^jQ zukq^@ii(t?jy1(tiQuG6R9r$L9!hn5Em6|Kb>emq&S6=}!xSH-VvvqvknTd~6*bNc zNrSiH(8pQt?i(_j;G03~YDlccx*hb8W=T%2M7!IMQLz>B%sxqGvY7N;Fo`dNQ~zd? zXMPsixjK?DTh1}p>{o#iFI!T=K_suf`&3c8;AR@Sb*(dZ&NQshA}Zjn%4nWeAGRYf zG`%MWPgqUw?Mc7=q(Jc>fF^9kTO&@rRS~WH`5|NX0$sK+RPI+W^6zC;S_Ov8Y}VVt zC%4}+naOD(v&a~2^dSl2kK-_mr0}Ke7%?nlo^^=<7kx-OF8^AX$CeZ;90JnZjsep) i;s@9~VrT?^n Date: Tue, 3 Aug 2021 17:52:28 -0700 Subject: [PATCH 28/45] initial cleanup (#2224) --- .../.gitattributes | 52 - .../.gitignore | 15 - .../.nuget/NuGet.Config | 7 - .../ActivityExtensions.cs | 292 ++--- .../ActivityHelper.cs | 298 ++--- .../AspNetTelemetryCorrelationEventSource.cs | 192 +-- .../Properties => }/AssemblyInfo.cs | 26 +- .../CODE-OF-CONDUCT.md | 6 - .../CONTRIBUTING.md | 25 - .../Internal/BaseHeaderParser.cs | 138 +- .../Internal/GenericHeaderParser.cs | 60 +- .../Internal/HeaderUtilities.cs | 90 +- .../Internal/HttpHeaderParser.cs | 52 +- .../Internal/HttpParseResult.cs | 46 +- .../Internal/HttpRuleParser.cs | 538 ++++---- .../Internal/NameValueHeaderValue.cs | 232 ++-- .../LICENSE.txt | 12 - ...crosoft.AspNet.TelemetryCorrelation.csproj | 172 +-- .../Microsoft.AspNet.TelemetryCorrelation.sln | 46 - .../NuGet.Config | 7 - .../TelemetryCorrelationHttpModule.cs | 318 ++--- ...rosoft.AspNet.TelemetryCorrelation.ruleset | 77 -- .../tools/35MSSharedLib1024.snk | Bin 160 -> 0 bytes .../tools/Common.props | 64 - .../tools/Debug.snk | Bin 596 -> 0 bytes .../tools/stylecop.json | 15 - .../web.config.install.xdt | 0 .../web.config.uninstall.xdt | 0 .../ActivityExtensionsTest.cs | 624 ++++----- .../ActivityHelperTest.cs | 1118 ++++++++--------- .../HttpContextHelper.cs | 186 +-- ...t.AspNet.TelemetryCorrelation.Tests.csproj | 162 +-- .../Properties/AssemblyInfo.cs | 34 +- .../PropertyExtensions.cs | 34 +- .../TestDiagnosticListener.cs | 66 +- .../WebConfigTransformTest.cs | 800 ++++++------ .../WebConfigWithLocationTagTransformTest.cs | 862 ++++++------- 37 files changed, 3170 insertions(+), 3496 deletions(-) delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/.gitignore delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/ActivityExtensions.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/ActivityHelper.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/AspNetTelemetryCorrelationEventSource.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation/Properties => }/AssemblyInfo.cs (98%) delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/BaseHeaderParser.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/GenericHeaderParser.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/HeaderUtilities.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/HttpHeaderParser.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/HttpParseResult.cs (96%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/HttpRuleParser.cs (97%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Internal/NameValueHeaderValue.cs (97%) delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/Microsoft.AspNet.TelemetryCorrelation.csproj (98%) delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/TelemetryCorrelationHttpModule.cs (97%) delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/35MSSharedLib1024.snk delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/Common.props delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/Debug.snk delete mode 100644 src/Microsoft.AspNet.TelemetryCorrelation/tools/stylecop.json rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/web.config.install.xdt (100%) rename src/Microsoft.AspNet.TelemetryCorrelation/{src/Microsoft.AspNet.TelemetryCorrelation => }/web.config.uninstall.xdt (100%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs (98%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs (97%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs (97%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj (98%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs (97%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs (97%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs (96%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs (98%) rename {src/Microsoft.AspNet.TelemetryCorrelation/test => test}/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs (98%) diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes b/src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes deleted file mode 100644 index d4ee1cb7f39..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/.gitattributes +++ /dev/null @@ -1,52 +0,0 @@ -*.doc diff=astextplain -*.DOC diff=astextplain -*.docx diff=astextplain -*.DOCX diff=astextplain -*.dot diff=astextplain -*.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain -*.rtf diff=astextplain -*.RTF diff=astextplain - -*.jpg binary -*.png binary -*.gif binary - -*.cs text=auto diff=csharp -*.vb text=auto -*.resx text=auto -*.c text=auto -*.cpp text=auto -*.cxx text=auto -*.h text=auto -*.hxx text=auto -*.py text=auto -*.rb text=auto -*.java text=auto -*.html text=auto -*.htm text=auto -*.css text=auto -*.scss text=auto -*.sass text=auto -*.less text=auto -*.js text=auto -*.lisp text=auto -*.clj text=auto -*.sql text=auto -*.php text=auto -*.lua text=auto -*.m text=auto -*.asm text=auto -*.erl text=auto -*.fs text=auto -*.fsx text=auto -*.hs text=auto - -*.csproj text=auto -*.vbproj text=auto -*.fsproj text=auto -*.dbproj text=auto -*.sln text=auto eol=crlf - -*.sh eol=lf diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/.gitignore b/src/Microsoft.AspNet.TelemetryCorrelation/.gitignore deleted file mode 100644 index b27c3880583..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -[Oo]bj/ -[Bb]in/ -TestResults/ -_ReSharper.*/ -/packages/ -artifacts/ -PublishProfiles/ -*.user -*.suo -*.cache -*.sln.ide -.vs -.build/ -.testPublish/ -msbuild.* \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config b/src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config deleted file mode 100644 index d8f90e2617e..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/.nuget/NuGet.Config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs index 04687e77baa..20760f3524a 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs @@ -1,146 +1,146 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Extensions of Activity class - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static class ActivityExtensions - { - /// - /// Http header name to carry the Request Id: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md. - /// - internal const string RequestIdHeaderName = "Request-Id"; - - /// - /// Http header name to carry the traceparent: https://www.w3.org/TR/trace-context/. - /// - internal const string TraceparentHeaderName = "traceparent"; - - /// - /// Http header name to carry the tracestate: https://www.w3.org/TR/trace-context/. - /// - internal const string TracestateHeaderName = "tracestate"; - - /// - /// Http header name to carry the correlation context. - /// - internal const string CorrelationContextHeaderName = "Correlation-Context"; - - /// - /// Maximum length of Correlation-Context header value. - /// - internal const int MaxCorrelationContextLength = 1024; - - /// - /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. - /// - /// Instance of activity that has not been started yet. - /// Request headers collection. - /// true if request was parsed successfully, false - otherwise. - public static bool Extract(this Activity activity, NameValueCollection requestHeaders) - { - if (activity == null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("activity is null"); - return false; - } - - if (activity.ParentId != null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); - return false; - } - - if (activity.Id != null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("Activity is already started"); - return false; - } - - var parents = requestHeaders.GetValues(TraceparentHeaderName); - if (parents == null || parents.Length == 0) - { - parents = requestHeaders.GetValues(RequestIdHeaderName); - } - - if (parents != null && parents.Length > 0 && !string.IsNullOrEmpty(parents[0])) - { - // there may be several Request-Id or traceparent headers, but we only read the first one - activity.SetParentId(parents[0]); - - var tracestates = requestHeaders.GetValues(TracestateHeaderName); - if (tracestates != null && tracestates.Length > 0) - { - if (tracestates.Length == 1 && !string.IsNullOrEmpty(tracestates[0])) - { - activity.TraceStateString = tracestates[0]; - } - else - { - activity.TraceStateString = string.Join(",", tracestates); - } - } - - // Header format - Correlation-Context: key1=value1, key2=value2 - var baggages = requestHeaders.GetValues(CorrelationContextHeaderName); - if (baggages != null) - { - int correlationContextLength = -1; - - // there may be several Correlation-Context header - foreach (var item in baggages) - { - if (correlationContextLength >= MaxCorrelationContextLength) - { - break; - } - - foreach (var pair in item.Split(',')) - { - correlationContextLength += pair.Length + 1; // pair and comma - - if (correlationContextLength >= MaxCorrelationContextLength) - { - break; - } - - if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem)) - { - activity.AddBaggage(baggageItem.Name, baggageItem.Value); - } - else - { - AspNetTelemetryCorrelationEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); - } - } - } - } - - return true; - } - - return false; - } - - /// - /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. - /// - /// Instance of activity that has not been started yet. - /// Request headers collection. - /// true if request was parsed successfully, false - otherwise. - [Obsolete("Method is obsolete, use Extract method instead", true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public static bool TryParse(this Activity activity, NameValueCollection requestHeaders) - { - return Extract(activity, requestHeaders); - } - } -} +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Extensions of Activity class + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ActivityExtensions + { + /// + /// Http header name to carry the Request Id: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md. + /// + internal const string RequestIdHeaderName = "Request-Id"; + + /// + /// Http header name to carry the traceparent: https://www.w3.org/TR/trace-context/. + /// + internal const string TraceparentHeaderName = "traceparent"; + + /// + /// Http header name to carry the tracestate: https://www.w3.org/TR/trace-context/. + /// + internal const string TracestateHeaderName = "tracestate"; + + /// + /// Http header name to carry the correlation context. + /// + internal const string CorrelationContextHeaderName = "Correlation-Context"; + + /// + /// Maximum length of Correlation-Context header value. + /// + internal const int MaxCorrelationContextLength = 1024; + + /// + /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. + /// + /// Instance of activity that has not been started yet. + /// Request headers collection. + /// true if request was parsed successfully, false - otherwise. + public static bool Extract(this Activity activity, NameValueCollection requestHeaders) + { + if (activity == null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("activity is null"); + return false; + } + + if (activity.ParentId != null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); + return false; + } + + if (activity.Id != null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("Activity is already started"); + return false; + } + + var parents = requestHeaders.GetValues(TraceparentHeaderName); + if (parents == null || parents.Length == 0) + { + parents = requestHeaders.GetValues(RequestIdHeaderName); + } + + if (parents != null && parents.Length > 0 && !string.IsNullOrEmpty(parents[0])) + { + // there may be several Request-Id or traceparent headers, but we only read the first one + activity.SetParentId(parents[0]); + + var tracestates = requestHeaders.GetValues(TracestateHeaderName); + if (tracestates != null && tracestates.Length > 0) + { + if (tracestates.Length == 1 && !string.IsNullOrEmpty(tracestates[0])) + { + activity.TraceStateString = tracestates[0]; + } + else + { + activity.TraceStateString = string.Join(",", tracestates); + } + } + + // Header format - Correlation-Context: key1=value1, key2=value2 + var baggages = requestHeaders.GetValues(CorrelationContextHeaderName); + if (baggages != null) + { + int correlationContextLength = -1; + + // there may be several Correlation-Context header + foreach (var item in baggages) + { + if (correlationContextLength >= MaxCorrelationContextLength) + { + break; + } + + foreach (var pair in item.Split(',')) + { + correlationContextLength += pair.Length + 1; // pair and comma + + if (correlationContextLength >= MaxCorrelationContextLength) + { + break; + } + + if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem)) + { + activity.AddBaggage(baggageItem.Name, baggageItem.Value); + } + else + { + AspNetTelemetryCorrelationEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); + } + } + } + } + + return true; + } + + return false; + } + + /// + /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. + /// + /// Instance of activity that has not been started yet. + /// Request headers collection. + /// true if request was parsed successfully, false - otherwise. + [Obsolete("Method is obsolete, use Extract method instead", true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool TryParse(this Activity activity, NameValueCollection requestHeaders) + { + return Extract(activity, requestHeaders); + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs index a625df5e1dd..edf1ad7827a 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs @@ -1,149 +1,149 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections; -using System.Diagnostics; -using System.Web; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Activity helper class - /// - internal static class ActivityHelper - { - /// - /// Listener name. - /// - public const string AspNetListenerName = "Microsoft.AspNet.TelemetryCorrelation"; - - /// - /// Activity name for http request. - /// - public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; - - /// - /// Event name for the activity start event. - /// - public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start"; - - /// - /// Key to store the activity in HttpContext. - /// - public const string ActivityKey = "__AspnetActivity__"; - - private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName); - - private static readonly object EmptyPayload = new object(); - - /// - /// Stops the activity and notifies listeners about it. - /// - /// HttpContext.Items. - public static void StopAspNetActivity(IDictionary contextItems) - { - var currentActivity = Activity.Current; - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - - if (currentActivity != aspNetActivity) - { - Activity.Current = aspNetActivity; - currentActivity = aspNetActivity; - } - - if (currentActivity != null) - { - // stop Activity with Stop event - AspNetListener.StopActivity(currentActivity, EmptyPayload); - contextItems[ActivityKey] = null; - } - - AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); - } - - /// - /// Creates root (first level) activity that describes incoming request. - /// - /// Current HttpContext. - /// Determines if headers should be parsed get correlation ids. - /// New root activity. - public static Activity CreateRootActivity(HttpContext context, bool parseHeaders) - { - if (AspNetListener.IsEnabled() && AspNetListener.IsEnabled(AspNetActivityName)) - { - var rootActivity = new Activity(AspNetActivityName); - - if (parseHeaders) - { - rootActivity.Extract(context.Request.Unvalidated.Headers); - } - - AspNetListener.OnActivityImport(rootActivity, null); - - if (StartAspNetActivity(rootActivity)) - { - context.Items[ActivityKey] = rootActivity; - AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id); - return rootActivity; - } - } - - return null; - } - - public static void WriteActivityException(IDictionary contextItems, Exception exception) - { - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - - if (aspNetActivity != null) - { - if (Activity.Current != aspNetActivity) - { - Activity.Current = aspNetActivity; - } - - AspNetListener.Write(aspNetActivity.OperationName + ".Exception", exception); - AspNetTelemetryCorrelationEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, 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. - /// - /// HttpContext.Items dictionary. - internal static void RestoreActivityIfNeeded(IDictionary contextItems) - { - if (Activity.Current == null) - { - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - if (aspNetActivity != null) - { - Activity.Current = aspNetActivity; - } - } - } - - private static bool StartAspNetActivity(Activity activity) - { - if (AspNetListener.IsEnabled(AspNetActivityName, activity, EmptyPayload)) - { - if (AspNetListener.IsEnabled(AspNetActivityStartName)) - { - AspNetListener.StartActivity(activity, EmptyPayload); - } - else - { - activity.Start(); - } - - return true; - } - - return false; - } - } -} +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Diagnostics; +using System.Web; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Activity helper class + /// + internal static class ActivityHelper + { + /// + /// Listener name. + /// + public const string AspNetListenerName = "Microsoft.AspNet.TelemetryCorrelation"; + + /// + /// Activity name for http request. + /// + public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; + + /// + /// Event name for the activity start event. + /// + public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start"; + + /// + /// Key to store the activity in HttpContext. + /// + public const string ActivityKey = "__AspnetActivity__"; + + private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName); + + private static readonly object EmptyPayload = new object(); + + /// + /// Stops the activity and notifies listeners about it. + /// + /// HttpContext.Items. + public static void StopAspNetActivity(IDictionary contextItems) + { + var currentActivity = Activity.Current; + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + + if (currentActivity != aspNetActivity) + { + Activity.Current = aspNetActivity; + currentActivity = aspNetActivity; + } + + if (currentActivity != null) + { + // stop Activity with Stop event + AspNetListener.StopActivity(currentActivity, EmptyPayload); + contextItems[ActivityKey] = null; + } + + AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); + } + + /// + /// Creates root (first level) activity that describes incoming request. + /// + /// Current HttpContext. + /// Determines if headers should be parsed get correlation ids. + /// New root activity. + public static Activity CreateRootActivity(HttpContext context, bool parseHeaders) + { + if (AspNetListener.IsEnabled() && AspNetListener.IsEnabled(AspNetActivityName)) + { + var rootActivity = new Activity(AspNetActivityName); + + if (parseHeaders) + { + rootActivity.Extract(context.Request.Unvalidated.Headers); + } + + AspNetListener.OnActivityImport(rootActivity, null); + + if (StartAspNetActivity(rootActivity)) + { + context.Items[ActivityKey] = rootActivity; + AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id); + return rootActivity; + } + } + + return null; + } + + public static void WriteActivityException(IDictionary contextItems, Exception exception) + { + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + + if (aspNetActivity != null) + { + if (Activity.Current != aspNetActivity) + { + Activity.Current = aspNetActivity; + } + + AspNetListener.Write(aspNetActivity.OperationName + ".Exception", exception); + AspNetTelemetryCorrelationEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, 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. + /// + /// HttpContext.Items dictionary. + internal static void RestoreActivityIfNeeded(IDictionary contextItems) + { + if (Activity.Current == null) + { + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + if (aspNetActivity != null) + { + Activity.Current = aspNetActivity; + } + } + } + + private static bool StartAspNetActivity(Activity activity) + { + if (AspNetListener.IsEnabled(AspNetActivityName, activity, EmptyPayload)) + { + if (AspNetListener.IsEnabled(AspNetActivityStartName)) + { + AspNetListener.StartActivity(activity, EmptyPayload); + } + else + { + activity.Start(); + } + + return true; + } + + return false; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs index 9748b57a015..c4dcde2dbc0 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs @@ -1,97 +1,97 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics.Tracing; -#pragma warning disable SA1600 // Elements must be documented - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// ETW EventSource tracing class. - /// - [EventSource(Name = "Microsoft-AspNet-Telemetry-Correlation", Guid = "ace2021e-e82c-5502-d81d-657f27612673")] - internal sealed class AspNetTelemetryCorrelationEventSource : EventSource - { - /// - /// Instance of the PlatformEventSource class. - /// - public static readonly AspNetTelemetryCorrelationEventSource Log = new AspNetTelemetryCorrelationEventSource(); - - [NonEvent] - public void ActivityException(string id, string eventName, Exception ex) - { - if (IsEnabled(EventLevel.Error, (EventKeywords)(-1))) - { - ActivityException(id, eventName, ex.ToString()); - } - } - - [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] - public void TraceCallback(string callback) - { - WriteEvent(1, callback); - } - - [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] - public void ActivityStarted(string id) - { - WriteEvent(2, id); - } - - [Event(3, Message = "Activity stopped, Id='{0}', Name='{1}'", Level = EventLevel.Verbose)] - public void ActivityStopped(string id, string eventName) - { - WriteEvent(3, id, eventName); - } - - [Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)] - public void HeaderParsingError(string headerName, string headerValue) - { - WriteEvent(4, headerName, headerValue); - } - - [Event(5, Message = "Failed to extract activity, reason '{0}'", Level = EventLevel.Error)] - public void ActvityExtractionError(string reason) - { - WriteEvent(5, reason); - } - - [Event(6, Message = "Finished Activity is detected on the stack, Id: '{0}', Name: '{1}'", Level = EventLevel.Error)] - public void FinishedActivityIsDetected(string id, string name) - { - WriteEvent(6, id, name); - } - - [Event(7, Message = "System.Diagnostics.Activity stack is too deep. This is a code authoring error, Activity will not be stopped.", Level = EventLevel.Error)] - public void ActivityStackIsTooDeepError() - { - WriteEvent(7); - } - - [Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] - public void ActivityRestored(string id) - { - WriteEvent(8, id); - } - - [Event(9, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] - public void OnExecuteRequestStepInvokationError(string error) - { - WriteEvent(9, error); - } - - [Event(10, Message = "System.Diagnostics.Activity stack is too deep. Current Id: '{0}', Name: '{1}'", Level = EventLevel.Warning)] - public void ActivityStackIsTooDeepDetails(string id, string name) - { - WriteEvent(10, id, name); - } - - [Event(11, Message = "Activity exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] - public void ActivityException(string id, string eventName, string ex) - { - WriteEvent(11, id, eventName, ex); - } - } -} +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Tracing; +#pragma warning disable SA1600 // Elements must be documented + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// ETW EventSource tracing class. + /// + [EventSource(Name = "Microsoft-AspNet-Telemetry-Correlation", Guid = "ace2021e-e82c-5502-d81d-657f27612673")] + internal sealed class AspNetTelemetryCorrelationEventSource : EventSource + { + /// + /// Instance of the PlatformEventSource class. + /// + public static readonly AspNetTelemetryCorrelationEventSource Log = new AspNetTelemetryCorrelationEventSource(); + + [NonEvent] + public void ActivityException(string id, string eventName, Exception ex) + { + if (IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + { + ActivityException(id, eventName, ex.ToString()); + } + } + + [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] + public void TraceCallback(string callback) + { + WriteEvent(1, callback); + } + + [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] + public void ActivityStarted(string id) + { + WriteEvent(2, id); + } + + [Event(3, Message = "Activity stopped, Id='{0}', Name='{1}'", Level = EventLevel.Verbose)] + public void ActivityStopped(string id, string eventName) + { + WriteEvent(3, id, eventName); + } + + [Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)] + public void HeaderParsingError(string headerName, string headerValue) + { + WriteEvent(4, headerName, headerValue); + } + + [Event(5, Message = "Failed to extract activity, reason '{0}'", Level = EventLevel.Error)] + public void ActvityExtractionError(string reason) + { + WriteEvent(5, reason); + } + + [Event(6, Message = "Finished Activity is detected on the stack, Id: '{0}', Name: '{1}'", Level = EventLevel.Error)] + public void FinishedActivityIsDetected(string id, string name) + { + WriteEvent(6, id, name); + } + + [Event(7, Message = "System.Diagnostics.Activity stack is too deep. This is a code authoring error, Activity will not be stopped.", Level = EventLevel.Error)] + public void ActivityStackIsTooDeepError() + { + WriteEvent(7); + } + + [Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] + public void ActivityRestored(string id) + { + WriteEvent(8, id); + } + + [Event(9, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] + public void OnExecuteRequestStepInvokationError(string error) + { + WriteEvent(9, error); + } + + [Event(10, Message = "System.Diagnostics.Activity stack is too deep. Current Id: '{0}', Name: '{1}'", Level = EventLevel.Warning)] + public void ActivityStackIsTooDeepDetails(string id, string name) + { + WriteEvent(10, id, name); + } + + [Event(11, Message = "Activity exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] + public void ActivityException(string id, string eventName, string ex) + { + WriteEvent(11, id, eventName, ex); + } + } +} #pragma warning restore SA1600 // Elements must be documented \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Properties/AssemblyInfo.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs index bc6fa30b168..e615d7f9847 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs @@ -1,14 +1,14 @@ -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// 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)] - -#if PUBLIC_RELEASE -[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -#else -[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100319b35b21a993df850846602dae9e86d8fbb0528a0ad488ecd6414db798996534381825f94f90d8b16b72a51c4e7e07cf66ff3293c1046c045fafc354cfcc15fc177c748111e4a8c5a34d3940e7f3789dd58a928add6160d6f9cc219680253dcea88a034e7d472de51d4989c7783e19343839273e0e63a43b99ab338149dd59f")] +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 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)] + +#if PUBLIC_RELEASE +[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +#else +[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100319b35b21a993df850846602dae9e86d8fbb0528a0ad488ecd6414db798996534381825f94f90d8b16b72a51c4e7e07cf66ff3293c1046c045fafc354cfcc15fc177c748111e4a8c5a34d3940e7f3789dd58a928add6160d6f9cc219680253dcea88a034e7d472de51d4989c7783e19343839273e0e63a43b99ab338149dd59f")] #endif \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md b/src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md deleted file mode 100644 index 775f221c98e..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/CODE-OF-CONDUCT.md +++ /dev/null @@ -1,6 +0,0 @@ -# Code of Conduct - -This project has adopted the code of conduct defined by the Contributor Covenant -to clarify expected behavior in our community. - -For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md b/src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md deleted file mode 100644 index 1e61cc52e3e..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/CONTRIBUTING.md +++ /dev/null @@ -1,25 +0,0 @@ -# Contributing - -Information on contributing to this repo is in the [Contributing -Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in -the Home repo. - -## Build and test - -1. Open project in Visual Studio 2017+. -2. Build and compile run unit tests right from Visual Studio. - -Command line: - -``` -dotnet build .\Microsoft.AspNet.TelemetryCorrelation.sln -dotnet test .\Microsoft.AspNet.TelemetryCorrelation.sln -dotnet pack .\Microsoft.AspNet.TelemetryCorrelation.sln -``` - -## Manual testing - -Follow readme to install http module to your application. - -Set `set PublicRelease=True` before build to produce delay-signed -assembly with the public key matching released version of assembly. \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs index 2186f0930b3..d08bc10c6b5 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs @@ -1,70 +1,70 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs - internal abstract class BaseHeaderParser : HttpHeaderParser - { - protected BaseHeaderParser(bool supportsMultipleValues) - : base(supportsMultipleValues) - { - } - - public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) - { - parsedValue = default(T); - - // If multiple values are supported (i.e. list of values), then accept an empty string: The header may - // be added multiple times to the request/response message. E.g. - // Accept: text/xml; q=1 - // Accept: - // Accept: text/plain; q=0.2 - if (string.IsNullOrEmpty(value) || (index == value.Length)) - { - return SupportsMultipleValues; - } - - var separatorFound = false; - var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, SupportsMultipleValues, out separatorFound); - - if (separatorFound && !SupportsMultipleValues) - { - return false; // leading separators not allowed if we don't support multiple values. - } - - if (current == value.Length) - { - if (SupportsMultipleValues) - { - index = current; - } - - return SupportsMultipleValues; - } - - T result; - var length = GetParsedValueLength(value, current, out result); - - if (length == 0) - { - return false; - } - - current = current + length; - current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues, out separatorFound); - - // If we support multiple values and we've not reached the end of the string, then we must have a separator. - if ((separatorFound && !SupportsMultipleValues) || (!separatorFound && (current < value.Length))) - { - return false; - } - - index = current; - parsedValue = result; - return true; - } - - protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue); - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs + internal abstract class BaseHeaderParser : HttpHeaderParser + { + protected BaseHeaderParser(bool supportsMultipleValues) + : base(supportsMultipleValues) + { + } + + public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) + { + parsedValue = default(T); + + // If multiple values are supported (i.e. list of values), then accept an empty string: The header may + // be added multiple times to the request/response message. E.g. + // Accept: text/xml; q=1 + // Accept: + // Accept: text/plain; q=0.2 + if (string.IsNullOrEmpty(value) || (index == value.Length)) + { + return SupportsMultipleValues; + } + + var separatorFound = false; + var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, SupportsMultipleValues, out separatorFound); + + if (separatorFound && !SupportsMultipleValues) + { + return false; // leading separators not allowed if we don't support multiple values. + } + + if (current == value.Length) + { + if (SupportsMultipleValues) + { + index = current; + } + + return SupportsMultipleValues; + } + + T result; + var length = GetParsedValueLength(value, current, out result); + + if (length == 0) + { + return false; + } + + current = current + length; + current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues, out separatorFound); + + // If we support multiple values and we've not reached the end of the string, then we must have a separator. + if ((separatorFound && !SupportsMultipleValues) || (!separatorFound && (current < value.Length))) + { + return false; + } + + index = current; + parsedValue = result; + return true; + } + + protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue); + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs index daa6d308060..53529af2afd 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs @@ -1,31 +1,31 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs - internal sealed class GenericHeaderParser : BaseHeaderParser - { - private GetParsedValueLengthDelegate getParsedValueLength; - - internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) - : base(supportsMultipleValues) - { - if (getParsedValueLength == null) - { - throw new ArgumentNullException(nameof(getParsedValueLength)); - } - - this.getParsedValueLength = getParsedValueLength; - } - - internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); - - protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) - { - return getParsedValueLength(value, startIndex, out parsedValue); - } - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs + internal sealed class GenericHeaderParser : BaseHeaderParser + { + private GetParsedValueLengthDelegate getParsedValueLength; + + internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) + : base(supportsMultipleValues) + { + if (getParsedValueLength == null) + { + throw new ArgumentNullException(nameof(getParsedValueLength)); + } + + this.getParsedValueLength = getParsedValueLength; + } + + internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); + + protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) + { + return getParsedValueLength(value, startIndex, out parsedValue); + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs index 7403f97b0c7..486b1aa7f23 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs @@ -1,46 +1,46 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Diagnostics.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs - internal static class HeaderUtilities - { - internal static int GetNextNonEmptyOrWhitespaceIndex( - string input, - int startIndex, - bool skipEmptyValues, - out bool separatorFound) - { - Contract.Requires(input != null); - Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length. - - separatorFound = false; - var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex); - - if ((current == input.Length) || (input[current] != ',')) - { - return current; - } - - // If we have a separator, skip the separator and all following whitespaces. If we support - // empty values, continue until the current character is neither a separator nor a whitespace. - separatorFound = true; - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - if (skipEmptyValues) - { - while ((current < input.Length) && (input[current] == ',')) - { - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - } - } - - return current; - } - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs + internal static class HeaderUtilities + { + internal static int GetNextNonEmptyOrWhitespaceIndex( + string input, + int startIndex, + bool skipEmptyValues, + out bool separatorFound) + { + Contract.Requires(input != null); + Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length. + + separatorFound = false; + var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex); + + if ((current == input.Length) || (input[current] != ',')) + { + return current; + } + + // If we have a separator, skip the separator and all following whitespaces. If we support + // empty values, continue until the current character is neither a separator nor a whitespace. + separatorFound = true; + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + if (skipEmptyValues) + { + while ((current < input.Length) && (input[current] == ',')) + { + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + } + } + + return current; + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs index 61d9172080e..07817e7b383 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs @@ -1,27 +1,27 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs - internal abstract class HttpHeaderParser - { - private bool supportsMultipleValues; - - protected HttpHeaderParser(bool supportsMultipleValues) - { - this.supportsMultipleValues = supportsMultipleValues; - } - - public bool SupportsMultipleValues - { - get { return supportsMultipleValues; } - } - - // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' - // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 - // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first - // non-whitespace after the separator ','. - public abstract bool TryParseValue(string value, ref int index, out T parsedValue); - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs + internal abstract class HttpHeaderParser + { + private bool supportsMultipleValues; + + protected HttpHeaderParser(bool supportsMultipleValues) + { + this.supportsMultipleValues = supportsMultipleValues; + } + + public bool SupportsMultipleValues + { + get { return supportsMultipleValues; } + } + + // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' + // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 + // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first + // non-whitespace after the separator ','. + public abstract bool TryParseValue(string value, ref int index, out T parsedValue); + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs similarity index 96% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs index 2f2ff6c50ba..bb890382619 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs @@ -1,24 +1,24 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs - internal enum HttpParseResult - { - /// - /// Parsed succesfully. - /// - Parsed, - - /// - /// Was not parsed. - /// - NotParsed, - - /// - /// Invalid format. - /// - InvalidFormat, - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs + internal enum HttpParseResult + { + /// + /// Parsed succesfully. + /// + Parsed, + + /// + /// Was not parsed. + /// + NotParsed, + + /// + /// Invalid format. + /// + InvalidFormat, + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs index 5ede3faa54e..c2299a952c1 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs @@ -1,270 +1,270 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Diagnostics.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs - internal static class HttpRuleParser - { - internal const char CR = '\r'; - internal const char LF = '\n'; - internal const char SP = ' '; - internal const char Tab = '\t'; - internal const int MaxInt64Digits = 19; - internal const int MaxInt32Digits = 10; - - private const int MaxNestedCount = 5; - private static readonly bool[] TokenChars = CreateTokenChars(); - - internal static bool IsTokenChar(char character) - { - // Must be between 'space' (32) and 'DEL' (127) - if (character > 127) - { - return false; - } - - return TokenChars[character]; - } - - internal static int GetTokenLength(string input, int startIndex) - { - Contract.Requires(input != null); - Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); - - if (startIndex >= input.Length) - { - return 0; - } - - var current = startIndex; - - while (current < input.Length) - { - if (!IsTokenChar(input[current])) - { - return current - startIndex; - } - - current++; - } - - return input.Length - startIndex; - } - - internal static int GetWhitespaceLength(string input, int startIndex) - { - Contract.Requires(input != null); - Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); - - if (startIndex >= input.Length) - { - return 0; - } - - var current = startIndex; - - char c; - while (current < input.Length) - { - c = input[current]; - - if ((c == SP) || (c == Tab)) - { - current++; - continue; - } - - if (c == CR) - { - // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. - if ((current + 2 < input.Length) && (input[current + 1] == LF)) - { - char spaceOrTab = input[current + 2]; - if ((spaceOrTab == SP) || (spaceOrTab == Tab)) - { - current += 3; - continue; - } - } - } - - return current - startIndex; - } - - // All characters between startIndex and the end of the string are LWS characters. - return input.Length - startIndex; - } - - internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) - { - var nestedCount = 0; - return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); - } - - // quoted-pair = "\" CHAR - // CHAR = - internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) - { - Contract.Requires(input != null); - Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); - Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && - (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); - - length = 0; - - if (input[startIndex] != '\\') - { - return HttpParseResult.NotParsed; - } - - // Quoted-char has 2 characters. Check wheter there are 2 chars left ('\' + char) - // If so, check whether the character is in the range 0-127. If not, it's an invalid value. - if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) - { - return HttpParseResult.InvalidFormat; - } - - // We don't care what the char next to '\' is. - length = 2; - return HttpParseResult.Parsed; - } - - private static bool[] CreateTokenChars() - { - // token = 1* - // CTL = - var tokenChars = new bool[128]; // everything is false - - for (int i = 33; i < 127; i++) - { - // skip Space (32) & DEL (127) - tokenChars[i] = true; - } - - // remove separators: these are not valid token characters - tokenChars[(byte)'('] = false; - tokenChars[(byte)')'] = false; - tokenChars[(byte)'<'] = false; - tokenChars[(byte)'>'] = false; - tokenChars[(byte)'@'] = false; - tokenChars[(byte)','] = false; - tokenChars[(byte)';'] = false; - tokenChars[(byte)':'] = false; - tokenChars[(byte)'\\'] = false; - tokenChars[(byte)'"'] = false; - tokenChars[(byte)'/'] = false; - tokenChars[(byte)'['] = false; - tokenChars[(byte)']'] = false; - tokenChars[(byte)'?'] = false; - tokenChars[(byte)'='] = false; - tokenChars[(byte)'{'] = false; - tokenChars[(byte)'}'] = false; - - return tokenChars; - } - - // TEXT = - // LWS = [CRLF] 1*( SP | HT ) - // CTL = - // - // Since we don't really care about the content of a quoted string or comment, we're more tolerant and - // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). - // - // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like - // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested - // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) - // is unusual. - private static HttpParseResult GetExpressionLength( - string input, - int startIndex, - char openChar, - char closeChar, - bool supportsNesting, - ref int nestedCount, - out int length) - { - Contract.Requires(input != null); - Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); - Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || - (Contract.ValueAtReturn(out length) > 0)); - - length = 0; - - if (input[startIndex] != openChar) - { - return HttpParseResult.NotParsed; - } - - var current = startIndex + 1; // Start parsing with the character next to the first open-char - while (current < input.Length) - { - // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. - // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. - var quotedPairLength = 0; - if ((current + 2 < input.Length) && - (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) - { - // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, - // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only - // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars). - current = current + quotedPairLength; - continue; - } - - // If we support nested expressions and we find an open-char, then parse the nested expressions. - if (supportsNesting && (input[current] == openChar)) - { - nestedCount++; - try - { - // Check if we exceeded the number of nested calls. - if (nestedCount > MaxNestedCount) - { - return HttpParseResult.InvalidFormat; - } - - var nestedLength = 0; - HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); - - switch (nestedResult) - { - case HttpParseResult.Parsed: - current += nestedLength; // add the length of the nested expression and continue. - break; - - case HttpParseResult.NotParsed: - Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression parsing, because we found the open-char. So either it's a valid nested expression or it has invalid format."); - break; - - case HttpParseResult.InvalidFormat: - // If the nested expression is invalid, we can't continue, so we fail with invalid format. - return HttpParseResult.InvalidFormat; - - default: - Contract.Assert(false, "Unknown enum result: " + nestedResult); - break; - } - } - finally - { - nestedCount--; - } - } - - if (input[current] == closeChar) - { - length = current - startIndex + 1; - return HttpParseResult.Parsed; - } - - current++; - } - - // We didn't see the final quote, therefore we have an invalid expression string. - return HttpParseResult.InvalidFormat; - } - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs + internal static class HttpRuleParser + { + internal const char CR = '\r'; + internal const char LF = '\n'; + internal const char SP = ' '; + internal const char Tab = '\t'; + internal const int MaxInt64Digits = 19; + internal const int MaxInt32Digits = 10; + + private const int MaxNestedCount = 5; + private static readonly bool[] TokenChars = CreateTokenChars(); + + internal static bool IsTokenChar(char character) + { + // Must be between 'space' (32) and 'DEL' (127) + if (character > 127) + { + return false; + } + + return TokenChars[character]; + } + + internal static int GetTokenLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + while (current < input.Length) + { + if (!IsTokenChar(input[current])) + { + return current - startIndex; + } + + current++; + } + + return input.Length - startIndex; + } + + internal static int GetWhitespaceLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + char c; + while (current < input.Length) + { + c = input[current]; + + if ((c == SP) || (c == Tab)) + { + current++; + continue; + } + + if (c == CR) + { + // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. + if ((current + 2 < input.Length) && (input[current + 1] == LF)) + { + char spaceOrTab = input[current + 2]; + if ((spaceOrTab == SP) || (spaceOrTab == Tab)) + { + current += 3; + continue; + } + } + } + + return current - startIndex; + } + + // All characters between startIndex and the end of the string are LWS characters. + return input.Length - startIndex; + } + + internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) + { + var nestedCount = 0; + return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); + } + + // quoted-pair = "\" CHAR + // CHAR = + internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && + (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); + + length = 0; + + if (input[startIndex] != '\\') + { + return HttpParseResult.NotParsed; + } + + // Quoted-char has 2 characters. Check wheter there are 2 chars left ('\' + char) + // If so, check whether the character is in the range 0-127. If not, it's an invalid value. + if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) + { + return HttpParseResult.InvalidFormat; + } + + // We don't care what the char next to '\' is. + length = 2; + return HttpParseResult.Parsed; + } + + private static bool[] CreateTokenChars() + { + // token = 1* + // CTL = + var tokenChars = new bool[128]; // everything is false + + for (int i = 33; i < 127; i++) + { + // skip Space (32) & DEL (127) + tokenChars[i] = true; + } + + // remove separators: these are not valid token characters + tokenChars[(byte)'('] = false; + tokenChars[(byte)')'] = false; + tokenChars[(byte)'<'] = false; + tokenChars[(byte)'>'] = false; + tokenChars[(byte)'@'] = false; + tokenChars[(byte)','] = false; + tokenChars[(byte)';'] = false; + tokenChars[(byte)':'] = false; + tokenChars[(byte)'\\'] = false; + tokenChars[(byte)'"'] = false; + tokenChars[(byte)'/'] = false; + tokenChars[(byte)'['] = false; + tokenChars[(byte)']'] = false; + tokenChars[(byte)'?'] = false; + tokenChars[(byte)'='] = false; + tokenChars[(byte)'{'] = false; + tokenChars[(byte)'}'] = false; + + return tokenChars; + } + + // TEXT = + // LWS = [CRLF] 1*( SP | HT ) + // CTL = + // + // Since we don't really care about the content of a quoted string or comment, we're more tolerant and + // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). + // + // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like + // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested + // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) + // is unusual. + private static HttpParseResult GetExpressionLength( + string input, + int startIndex, + char openChar, + char closeChar, + bool supportsNesting, + ref int nestedCount, + out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || + (Contract.ValueAtReturn(out length) > 0)); + + length = 0; + + if (input[startIndex] != openChar) + { + return HttpParseResult.NotParsed; + } + + var current = startIndex + 1; // Start parsing with the character next to the first open-char + while (current < input.Length) + { + // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. + // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. + var quotedPairLength = 0; + if ((current + 2 < input.Length) && + (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) + { + // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, + // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only + // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars). + current = current + quotedPairLength; + continue; + } + + // If we support nested expressions and we find an open-char, then parse the nested expressions. + if (supportsNesting && (input[current] == openChar)) + { + nestedCount++; + try + { + // Check if we exceeded the number of nested calls. + if (nestedCount > MaxNestedCount) + { + return HttpParseResult.InvalidFormat; + } + + var nestedLength = 0; + HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); + + switch (nestedResult) + { + case HttpParseResult.Parsed: + current += nestedLength; // add the length of the nested expression and continue. + break; + + case HttpParseResult.NotParsed: + Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression parsing, because we found the open-char. So either it's a valid nested expression or it has invalid format."); + break; + + case HttpParseResult.InvalidFormat: + // If the nested expression is invalid, we can't continue, so we fail with invalid format. + return HttpParseResult.InvalidFormat; + + default: + Contract.Assert(false, "Unknown enum result: " + nestedResult); + break; + } + } + finally + { + nestedCount--; + } + } + + if (input[current] == closeChar) + { + length = current - startIndex + 1; + return HttpParseResult.Parsed; + } + + current++; + } + + // We didn't see the final quote, therefore we have an invalid expression string. + return HttpParseResult.InvalidFormat; + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs index ecae4e11df8..be894c34ab0 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs @@ -1,117 +1,117 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Diagnostics.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs - - // According to the RFC, in places where a "parameter" is required, the value is mandatory - // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports - // name-only values in addition to name/value pairs. - internal class NameValueHeaderValue - { - private static readonly HttpHeaderParser SingleValueParser - = new GenericHeaderParser(false, GetNameValueLength); - - private string name; - private string value; - - private NameValueHeaderValue() - { - // Used by the parser to create a new instance of this type. - } - - public string Name - { - get { return name; } - } - - public string Value - { - get { return value; } - } - - public static bool TryParse(string input, out NameValueHeaderValue parsedValue) - { - var index = 0; - return SingleValueParser.TryParseValue(input, ref index, out parsedValue); - } - - internal static int GetValueLength(string input, int startIndex) - { - Contract.Requires(input != null); - - if (startIndex >= input.Length) - { - return 0; - } - - var valueLength = HttpRuleParser.GetTokenLength(input, startIndex); - - if (valueLength == 0) - { - // A value can either be a token or a quoted string. Check if it is a quoted string. - if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed) - { - // We have an invalid value. Reset the name and return. - return 0; - } - } - - return valueLength; - } - - private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue) - { - Contract.Requires(input != null); - Contract.Requires(startIndex >= 0); - - parsedValue = null; - - if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) - { - return 0; - } - - // Parse the name, i.e. in name/value string "=". Caller must remove - // leading whitespaces. - var nameLength = HttpRuleParser.GetTokenLength(input, startIndex); - - if (nameLength == 0) - { - return 0; - } - - var name = input.Substring(startIndex, nameLength); - var current = startIndex + nameLength; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - // Parse the separator between name and value - if ((current == input.Length) || (input[current] != '=')) - { - // We only have a name and that's OK. Return. - parsedValue = new NameValueHeaderValue(); - parsedValue.name = name; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces - return current - startIndex; - } - - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - // Parse the value, i.e. in name/value string "=" - int valueLength = GetValueLength(input, current); - - // Value after the '=' may be empty - // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. - parsedValue = new NameValueHeaderValue(); - parsedValue.name = name; - parsedValue.value = input.Substring(current, valueLength); - current = current + valueLength; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces - return current - startIndex; - } - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs + + // According to the RFC, in places where a "parameter" is required, the value is mandatory + // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports + // name-only values in addition to name/value pairs. + internal class NameValueHeaderValue + { + private static readonly HttpHeaderParser SingleValueParser + = new GenericHeaderParser(false, GetNameValueLength); + + private string name; + private string value; + + private NameValueHeaderValue() + { + // Used by the parser to create a new instance of this type. + } + + public string Name + { + get { return name; } + } + + public string Value + { + get { return value; } + } + + public static bool TryParse(string input, out NameValueHeaderValue parsedValue) + { + var index = 0; + return SingleValueParser.TryParseValue(input, ref index, out parsedValue); + } + + internal static int GetValueLength(string input, int startIndex) + { + Contract.Requires(input != null); + + if (startIndex >= input.Length) + { + return 0; + } + + var valueLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (valueLength == 0) + { + // A value can either be a token or a quoted string. Check if it is a quoted string. + if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed) + { + // We have an invalid value. Reset the name and return. + return 0; + } + } + + return valueLength; + } + + private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue) + { + Contract.Requires(input != null); + Contract.Requires(startIndex >= 0); + + parsedValue = null; + + if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) + { + return 0; + } + + // Parse the name, i.e. in name/value string "=". Caller must remove + // leading whitespaces. + var nameLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (nameLength == 0) + { + return 0; + } + + var name = input.Substring(startIndex, nameLength); + var current = startIndex + nameLength; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the separator between name and value + if ((current == input.Length) || (input[current] != '=')) + { + // We only have a name and that's OK. Return. + parsedValue = new NameValueHeaderValue(); + parsedValue.name = name; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the value, i.e. in name/value string "=" + int valueLength = GetValueLength(input, current); + + // Value after the '=' may be empty + // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. + parsedValue = new NameValueHeaderValue(); + parsedValue.name = name; + parsedValue.value = input.Substring(current, valueLength); + current = current + valueLength; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt b/src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt deleted file mode 100644 index ed0ac7bc067..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/LICENSE.txt +++ /dev/null @@ -1,12 +0,0 @@ -Copyright (c) .NET Foundation. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -these files 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. \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj rename to src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj index 3c684dd1564..1d119876928 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj @@ -1,87 +1,87 @@ - - - - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12} - Library - Properties - Microsoft.AspNet.TelemetryCorrelation - Microsoft.AspNet.TelemetryCorrelation - net45 - 512 - true - $(OutputPath)$(AssemblyName).xml - ..\..\ - Microsoft.AspNet.TelemetryCorrelation.ruleset - true - prompt - 4 - true - $(OutputPath)/$(TargetFramework)/$(AssemblyName).xml - - - true - $(RepositoryRoot)tools\35MSSharedLib1024.snk - $(DefineConstants);PUBLIC_RELEASE - - - false - $(RepositoryRoot)tools\Debug.snk - - - full - false - $(DefineConstants);DEBUG;TRACE - - - pdbonly - true - $(DefineConstants);TRACE - - - Microsoft Corporation - - True - True - snupkg - - Microsoft.AspNet.TelemetryCorrelation - - - Microsoft - Microsoft Asp.Net telemetry correlation - A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. - © Microsoft Corporation. All rights reserved. - Apache-2.0 - http://www.asp.net/ - http://go.microsoft.com/fwlink/?LinkID=288859 - Diagnostics DiagnosticSource Correlation Activity ASP.NET - https://github.com/aspnet/Microsoft.AspNet.TelemetryCorrelation/ - Git - Dependency - content - - - - - - All - - - All - - - All - - - All - - - - - - - - - - + + + + {4C8E592C-C532-4CF2-80EF-3BDD0D788D12} + Library + Properties + Microsoft.AspNet.TelemetryCorrelation + Microsoft.AspNet.TelemetryCorrelation + net45 + 512 + true + $(OutputPath)$(AssemblyName).xml + ..\..\ + Microsoft.AspNet.TelemetryCorrelation.ruleset + true + prompt + 4 + true + $(OutputPath)/$(TargetFramework)/$(AssemblyName).xml + + + true + $(RepositoryRoot)tools\35MSSharedLib1024.snk + $(DefineConstants);PUBLIC_RELEASE + + + false + $(RepositoryRoot)tools\Debug.snk + + + full + false + $(DefineConstants);DEBUG;TRACE + + + pdbonly + true + $(DefineConstants);TRACE + + + Microsoft Corporation + + True + True + snupkg + + Microsoft.AspNet.TelemetryCorrelation + + + Microsoft + Microsoft Asp.Net telemetry correlation + A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. + © Microsoft Corporation. All rights reserved. + Apache-2.0 + http://www.asp.net/ + http://go.microsoft.com/fwlink/?LinkID=288859 + Diagnostics DiagnosticSource Correlation Activity ASP.NET + https://github.com/aspnet/Microsoft.AspNet.TelemetryCorrelation/ + Git + Dependency + content + + + + + + All + + + All + + + All + + + All + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln deleted file mode 100644 index 4ea7f514bd5..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.sln +++ /dev/null @@ -1,46 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29306.81 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{258D5057-81B9-40EC-A872-D21E27452749}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.TelemetryCorrelation", "src\Microsoft.AspNet.TelemetryCorrelation\Microsoft.AspNet.TelemetryCorrelation.csproj", "{4C8E592C-C532-4CF2-80EF-3BDD0D788D12}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.TelemetryCorrelation.Tests", "test\Microsoft.AspNet.TelemetryCorrelation.Tests\Microsoft.AspNet.TelemetryCorrelation.Tests.csproj", "{9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{504D7010-38CC-4B07-BC57-D7030209D631}" - ProjectSection(SolutionItems) = preProject - tools\Common.props = tools\Common.props - NuGet.Config = NuGet.Config - README.md = README.md - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12}.Release|Any CPU.Build.0 = Release|Any CPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC} - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED} = {258D5057-81B9-40EC-A872-D21E27452749} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {6E28F11C-A0D8-461B-B71F-70F348C1BB53} - EndGlobalSection -EndGlobal diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config b/src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config deleted file mode 100644 index 248a5bb51aa..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/NuGet.Config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs rename to src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs index 55827833858..418db7cfa91 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs @@ -1,159 +1,159 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Reflection; -using System.Web; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Http Module sets ambient state using Activity API from DiagnosticsSource package. - /// - public class TelemetryCorrelationHttpModule : IHttpModule - { - private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled"; - - // 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 MethodInfo onStepMethodInfo = null; - - static TelemetryCorrelationHttpModule() - { - onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); - } - - /// - /// Gets or sets a value indicating whether TelemetryCorrelationHttpModule should parse headers to get correlation ids. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool ParseHeaders { get; set; } = true; - - /// - public void Dispose() - { - } - - /// - public void Init(HttpApplication context) - { - context.BeginRequest += Application_BeginRequest; - context.EndRequest += Application_EndRequest; - context.Error += Application_Error; - - // OnExecuteRequestStep is availabile starting with 4.7.1 - // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), - // we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute - if (onStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) - { - try - { - onStepMethodInfo.Invoke(context, new object[] { (Action)OnExecuteRequestStep }); - } - catch (Exception e) - { - AspNetTelemetryCorrelationEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); - } - } - else - { - context.PreRequestHandlerExecute += Application_PreRequestHandlerExecute; - } - } - - /// - /// Restores Activity before each pipeline step if it was lost. - /// - /// HttpContext instance. - /// Step to be executed. - internal void OnExecuteRequestStep(HttpContextBase context, Action step) - { - // Once we have public Activity.Current setter (https://github.com/dotnet/corefx/issues/29207) this method will be - // simplified to just assign Current if is was lost. - // In the mean time, we are creating child Activity to restore the context. We have to send - // event with this Activity to tracing system. It created a lot of issues for listeners as - // we may potentially have a lot of them for different stages. - // To reduce amount of events, we only care about ExecuteRequestHandler stage - restore activity here and - // stop/report it to tracing system in EndRequest. - if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) - { - ActivityHelper.RestoreActivityIfNeeded(context.Items); - } - - step(); - } - - private void Application_BeginRequest(object sender, EventArgs e) - { - var context = ((HttpApplication)sender).Context; - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); - ActivityHelper.CreateRootActivity(context, ParseHeaders); - context.Items[BeginCalledFlag] = true; - } - - private void Application_PreRequestHandlerExecute(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); - ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items); - } - - private void Application_EndRequest(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_EndRequest"); - bool trackActivity = true; - - var context = ((HttpApplication)sender).Context; - - // EndRequest does it's best effort to notify that request has ended - // BeginRequest has never been called - if (!context.Items.Contains(BeginCalledFlag)) - { - // 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 pipleline 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 - ActivityHelper.CreateRootActivity(context, ParseHeaders); - } - } - - if (trackActivity) - { - ActivityHelper.StopAspNetActivity(context.Items); - } - } - - private void Application_Error(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_Error"); - - var context = ((HttpApplication)sender).Context; - - var exception = context.Error; - if (exception != null) - { - if (!context.Items.Contains(BeginCalledFlag)) - { - ActivityHelper.CreateRootActivity(context, ParseHeaders); - } - - ActivityHelper.WriteActivityException(context.Items, exception); - } - } - } -} +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Reflection; +using System.Web; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Http Module sets ambient state using Activity API from DiagnosticsSource package. + /// + public class TelemetryCorrelationHttpModule : IHttpModule + { + private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled"; + + // 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 MethodInfo onStepMethodInfo = null; + + static TelemetryCorrelationHttpModule() + { + onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); + } + + /// + /// Gets or sets a value indicating whether TelemetryCorrelationHttpModule should parse headers to get correlation ids. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ParseHeaders { get; set; } = true; + + /// + public void Dispose() + { + } + + /// + public void Init(HttpApplication context) + { + context.BeginRequest += Application_BeginRequest; + context.EndRequest += Application_EndRequest; + context.Error += Application_Error; + + // OnExecuteRequestStep is availabile starting with 4.7.1 + // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), + // we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute + if (onStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) + { + try + { + onStepMethodInfo.Invoke(context, new object[] { (Action)OnExecuteRequestStep }); + } + catch (Exception e) + { + AspNetTelemetryCorrelationEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); + } + } + else + { + context.PreRequestHandlerExecute += Application_PreRequestHandlerExecute; + } + } + + /// + /// Restores Activity before each pipeline step if it was lost. + /// + /// HttpContext instance. + /// Step to be executed. + internal void OnExecuteRequestStep(HttpContextBase context, Action step) + { + // Once we have public Activity.Current setter (https://github.com/dotnet/corefx/issues/29207) this method will be + // simplified to just assign Current if is was lost. + // In the mean time, we are creating child Activity to restore the context. We have to send + // event with this Activity to tracing system. It created a lot of issues for listeners as + // we may potentially have a lot of them for different stages. + // To reduce amount of events, we only care about ExecuteRequestHandler stage - restore activity here and + // stop/report it to tracing system in EndRequest. + if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) + { + ActivityHelper.RestoreActivityIfNeeded(context.Items); + } + + step(); + } + + private void Application_BeginRequest(object sender, EventArgs e) + { + var context = ((HttpApplication)sender).Context; + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); + ActivityHelper.CreateRootActivity(context, ParseHeaders); + context.Items[BeginCalledFlag] = true; + } + + private void Application_PreRequestHandlerExecute(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); + ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items); + } + + private void Application_EndRequest(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_EndRequest"); + bool trackActivity = true; + + var context = ((HttpApplication)sender).Context; + + // EndRequest does it's best effort to notify that request has ended + // BeginRequest has never been called + if (!context.Items.Contains(BeginCalledFlag)) + { + // 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 pipleline 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 + ActivityHelper.CreateRootActivity(context, ParseHeaders); + } + } + + if (trackActivity) + { + ActivityHelper.StopAspNetActivity(context.Items); + } + } + + private void Application_Error(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_Error"); + + var context = ((HttpApplication)sender).Context; + + var exception = context.Error; + if (exception != null) + { + if (!context.Items.Contains(BeginCalledFlag)) + { + ActivityHelper.CreateRootActivity(context, ParseHeaders); + } + + ActivityHelper.WriteActivityException(context.Items, exception); + } + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset b/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset deleted file mode 100644 index a13b89aa163..00000000000 --- a/src/Microsoft.AspNet.TelemetryCorrelation/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.ruleset +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/tools/35MSSharedLib1024.snk b/src/Microsoft.AspNet.TelemetryCorrelation/tools/35MSSharedLib1024.snk deleted file mode 100644 index 695f1b38774e839e5b90059bfb7f32df1dff4223..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa50098C{E+7Ye`kjtcRG*W zi8#m|)B?I?xgZ^2Sw5D;l4TxtPwG;3)3^j?qDHjEteSTF{rM+4WI`v zCD?tsZ^;k+S&r1&HRMb=j738S=;J$tCKNrc$@P|lZ - - - 7.3 - - - - $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Microsoft.AspNet.TelemetryCorrelation.sln))\ - - - - Release - $(RepositoryRoot)bin\ - $(RepositoryRoot)obj\ - $(BinPath)$(Configuration)\ - $(ObjPath)$(Configuration)\$(MSBuildProjectName)\ - - - - - - - - - rtm - 2018 - 1 - 0 - 9 - - $([MSBuild]::Add(1, $([MSBuild]::Subtract($([System.DateTime]::Now.Year), $(VersionStartYear)))))$([System.DateTime]::Now.ToString("MMdd")) - 0 - 0 - - - - $(VersionMajor).$(VersionMinor).$(VersionRelease) - $(BuildQuality) - $(VersionMajor).$(VersionMinor).$(VersionBuild).$(VersionRevision) - $(VersionMajor).$(VersionMinor).$(VersionRelease)-$(VersionBuild) - $(VersionMajor).$(VersionMinor).$(VersionRelease).0 - - - - Release - $(RepositoryRoot)bin\$(Configuration)\ - $(RepositoryRoot)obj\$(Configuration)\$(MSBuildProjectName)\ - - - - - - - Microsoft400 - - MsSharedLib72 - - - - - - - - diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/tools/Debug.snk b/src/Microsoft.AspNet.TelemetryCorrelation/tools/Debug.snk deleted file mode 100644 index 00c211eeee2ef3ece4dbc6359939cafd9b5000e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096on>Df;nLYSWgk}QT>F8~by9Fqqtw@f| zWE9(ZiI!7Cfr4L@`3;K}w<=M@=iq$yZ}TZU5Jtd7`usIa{J~$rcgIK(9!iW_G}Dw0 ze>aKUSg9zj))ozKoWdDs0#n@Th@dp*)N%igW;1ygOYRL<~l>UnzJ|*ozup=81B;$ZY}?ryl-GiXWrD4_^-?Z%d7TB3qeUcEYu*GCmnsuCrN|NRYF!S+593-qL7JIUHTFCb^jQ zukq^@ii(t?jy1(tiQuG6R9r$L9!hn5Em6|Kb>emq&S6=}!xSH-VvvqvknTd~6*bNc zNrSiH(8pQt?i(_j;G03~YDlccx*hb8W=T%2M7!IMQLz>B%sxqGvY7N;Fo`dNQ~zd? zXMPsixjK?DTh1}p>{o#iFI!T=K_suf`&3c8;AR@Sb*(dZ&NQshA}Zjn%4nWeAGRYf zG`%MWPgqUw?Mc7=q(Jc>fF^9kTO&@rRS~WH`5|NX0$sK+RPI+W^6zC;S_Ov8Y}VVt zC%4}+naOD(v&a~2^dSl2kK-_mr0}Ke7%?nlo^^=<7kx-OF8^AX$CeZ;90JnZjsep) i;s@9~VrT?^n -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Linq; - using Xunit; - - public class ActivityExtensionsTest - { - private const string TestActivityName = "Activity.Test"; - - [Fact] - public void Restore_Nothing_If_Header_Does_Not_Contain_RequestId() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection(); - - Assert.False(activity.Extract(requestHeaders)); - - Assert.True(string.IsNullOrEmpty(activity.ParentId)); - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Restore_First_RequestId_When_Multiple_RequestId_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" } - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Extract_RequestId_Is_Ignored_When_Traceparent_Is_Present() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" } - }; - Assert.True(activity.Extract(requestHeaders)); - - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.False(activity.Recorded); - - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Extract_First_Traceparent_When_Multiple_Traceparents_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" } - }; - Assert.True(activity.Extract(requestHeaders)); - - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.False(activity.Recorded); - - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Extract_RootActivity_From_W3C_Headers_And_CC() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - }; - - Assert.True(activity.Extract(requestHeaders)); - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.True(activity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789") - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Extract_Empty_Traceparent() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, string.Empty }, - }; - - Assert.False(activity.Extract(requestHeaders)); - - Assert.Equal(default, activity.ParentSpanId); - Assert.Null(activity.ParentId); - } - - [Fact] - public void Can_Extract_Multi_Line_Tracestate() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1" }, - { ActivityExtensions.TracestateHeaderName, "ts2=v2" }, - }; - - Assert.True(activity.Extract(requestHeaders)); - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.True(activity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); - } - - [Fact] - public void Restore_Empty_RequestId_Should_Not_Throw_Exception() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, string.Empty } - }; - Assert.False(activity.Extract(requestHeaders)); - - Assert.Null(activity.ParentId); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Restore_Empty_Traceparent_Should_Not_Throw_Exception() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, string.Empty } - }; - Assert.False(activity.Extract(requestHeaders)); - - Assert.Null(activity.ParentId); - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" } - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789") - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - { ActivityExtensions.CorrelationContextHeaderName, "key4=abc,key5=def" }, - { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" } - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789"), - new KeyValuePair("key4", "abc"), - new KeyValuePair("key5", "def"), - new KeyValuePair("key6", "xyz") - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - { ActivityExtensions.CorrelationContextHeaderName, "key4=abc;key5=def" }, - { ActivityExtensions.CorrelationContextHeaderName, "key6????xyz" }, - { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" } - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789") - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Theory] - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx", 1023)] // 1023 chars - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx1", 1024)] // 1024 chars - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,key74=value74", 1029)] // more than 1024 chars - public void Validates_Correlation_Context_Length(string correlationContext, int expectedLength) - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|abc.1" }, - { ActivityExtensions.CorrelationContextHeaderName, correlationContext } - }; - Assert.True(activity.Extract(requestHeaders)); - - var baggageItems = Enumerable.Range(0, 74).Select(i => new KeyValuePair("key" + i, "value" + i)).ToList(); - if (expectedLength < 1024) - { - baggageItems.Add(new KeyValuePair("k100", "vx")); - } - - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - } -} +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Linq; + using Xunit; + + public class ActivityExtensionsTest + { + private const string TestActivityName = "Activity.Test"; + + [Fact] + public void Restore_Nothing_If_Header_Does_Not_Contain_RequestId() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection(); + + Assert.False(activity.Extract(requestHeaders)); + + Assert.True(string.IsNullOrEmpty(activity.ParentId)); + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Restore_First_RequestId_When_Multiple_RequestId_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Extract_RequestId_Is_Ignored_When_Traceparent_Is_Present() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" } + }; + Assert.True(activity.Extract(requestHeaders)); + + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.False(activity.Recorded); + + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Extract_First_Traceparent_When_Multiple_Traceparents_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" } + }; + Assert.True(activity.Extract(requestHeaders)); + + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.False(activity.Recorded); + + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Extract_RootActivity_From_W3C_Headers_And_CC() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + }; + + Assert.True(activity.Extract(requestHeaders)); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(activity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Extract_Empty_Traceparent() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, string.Empty }, + }; + + Assert.False(activity.Extract(requestHeaders)); + + Assert.Equal(default, activity.ParentSpanId); + Assert.Null(activity.ParentId); + } + + [Fact] + public void Can_Extract_Multi_Line_Tracestate() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1" }, + { ActivityExtensions.TracestateHeaderName, "ts2=v2" }, + }; + + Assert.True(activity.Extract(requestHeaders)); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(activity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); + } + + [Fact] + public void Restore_Empty_RequestId_Should_Not_Throw_Exception() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, string.Empty } + }; + Assert.False(activity.Extract(requestHeaders)); + + Assert.Null(activity.ParentId); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Restore_Empty_Traceparent_Should_Not_Throw_Exception() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, string.Empty } + }; + Assert.False(activity.Extract(requestHeaders)); + + Assert.Null(activity.ParentId); + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + { ActivityExtensions.CorrelationContextHeaderName, "key4=abc,key5=def" }, + { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + new KeyValuePair("key4", "abc"), + new KeyValuePair("key5", "def"), + new KeyValuePair("key6", "xyz") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + { ActivityExtensions.CorrelationContextHeaderName, "key4=abc;key5=def" }, + { ActivityExtensions.CorrelationContextHeaderName, "key6????xyz" }, + { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" } + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789") + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Theory] + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx", 1023)] // 1023 chars + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx1", 1024)] // 1024 chars + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,key74=value74", 1029)] // more than 1024 chars + public void Validates_Correlation_Context_Length(string correlationContext, int expectedLength) + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|abc.1" }, + { ActivityExtensions.CorrelationContextHeaderName, correlationContext } + }; + Assert.True(activity.Extract(requestHeaders)); + + var baggageItems = Enumerable.Range(0, 74).Select(i => new KeyValuePair("key" + i, "value" + i)).ToList(); + if (expectedLength < 1024) + { + baggageItems.Add(new KeyValuePair("k100", "vx")); + } + + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs index d4f28b8895e..81e852393ac 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs @@ -1,559 +1,559 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using System.Threading; - using System.Threading.Tasks; - using System.Web; - using Xunit; - - public class ActivityHelperTest : IDisposable - { - private const string TestActivityName = "Activity.Test"; - private readonly List> baggageItems; - private readonly string baggageInHeader; - private IDisposable subscriptionAllListeners; - private IDisposable subscriptionAspNetListener; - - public ActivityHelperTest() - { - this.baggageItems = new List> - { - new KeyValuePair("TestKey1", "123"), - new KeyValuePair("TestKey2", "456"), - new KeyValuePair("TestKey1", "789") - }; - - this.baggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; - - // reset static fields - var allListenerField = typeof(DiagnosticListener). - GetField("s_allListenerObservable", BindingFlags.Static | BindingFlags.NonPublic); - allListenerField.SetValue(null, null); - var aspnetListenerField = typeof(ActivityHelper). - GetField("AspNetListener", BindingFlags.Static | BindingFlags.NonPublic); - aspnetListenerField.SetValue(null, new DiagnosticListener(ActivityHelper.AspNetListenerName)); - } - - public void Dispose() - { - this.subscriptionAspNetListener?.Dispose(); - this.subscriptionAllListeners?.Dispose(); - } - - [Fact] - public void Can_Restore_Activity() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.RestoreActivityIfNeeded(context.Items); - - Assert.Same(Activity.Current, rootActivity); - } - - [Fact] - public void Can_Stop_Lost_Activity() - { - this.EnableAll(pair => - { - Assert.NotNull(Activity.Current); - Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); - }); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.StopAspNetActivity(context.Items); - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Not_Stop_Lost_Activity_If_Not_In_Context() - { - this.EnableAll(pair => - { - Assert.NotNull(Activity.Current); - Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); - }); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - context.Items.Remove(ActivityHelper.ActivityKey); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.StopAspNetActivity(context.Items); - Assert.True(rootActivity.Duration == TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() - { - this.EnableAll(); - ActivityHelper.RestoreActivityIfNeeded(HttpContextHelper.GetFakeHttpContext().Items); - - Assert.Null(Activity.Current); - } - - [Fact] - public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() - { - this.EnableAll(); - var root = new Activity("root").Start(); - - var context = HttpContextHelper.GetFakeHttpContext(); - context.Items[ActivityHelper.ActivityKey] = root; - - var module = new TelemetryCorrelationHttpModule(); - - ActivityHelper.RestoreActivityIfNeeded(context.Items); - - Assert.Equal(root, Activity.Current); - } - - [Fact] - public void Can_Stop_Activity_Without_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = this.CreateActivity(); - rootActivity.Start(); - context.Items[ActivityHelper.ActivityKey] = rootActivity; - Thread.Sleep(100); - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Activity_With_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = this.CreateActivity(); - rootActivity.Start(); - context.Items[ActivityHelper.ActivityKey] = rootActivity; - Thread.Sleep(100); - this.EnableAspNetListenerOnly(); - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Root_Activity_With_All_Children() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - - var child = new Activity("child").Start(); - new Activity("grandchild").Start(); - - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.True(child.Duration == TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Root_While_Child_Is_Current() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - var child = new Activity("child").Start(); - - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(child.Duration == TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void OnImportActivity_Is_Called() - { - bool onImportIsCalled = false; - Activity importedActivity = null; - this.EnableAll(onImport: (activity, _) => - { - onImportIsCalled = true; - importedActivity = activity; - Assert.Null(Activity.Current); - }); - - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - Assert.True(onImportIsCalled); - Assert.NotNull(importedActivity); - Assert.Equal(importedActivity, Activity.Current); - Assert.Equal(importedActivity, rootActivity); - } - - [Fact] - public void OnImportActivity_Can_Set_Parent() - { - this.EnableAll(onImport: (activity, _) => - { - Assert.Null(activity.ParentId); - activity.SetParentId("|guid.123."); - }); - - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - - Assert.Equal("|guid.123.", Activity.Current.ParentId); - } - - [Fact] - public async Task Can_Stop_Root_Activity_If_It_Is_Broken() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var root = ActivityHelper.CreateRootActivity(context, false); - 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(context.Items); - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - Assert.Null(Activity.Current); - } - - [Fact] - public void Stop_Root_Activity_With_129_Nesting_Depth() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var root = ActivityHelper.CreateRootActivity(context, false); - - for (int i = 0; i < 129; i++) - { - new Activity("child" + i).Start(); - } - - // can stop any activity regardless of the stack depth - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - Assert.Null(Activity.Current); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerOnly(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled_With_Arguments() - { - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndDisableActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Can_Create_RootActivity_And_Restore_Info_From_Request_Header() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, - { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.True(rootActivity.ParentId == "|aba2f1e978b2cab6.1."); - var expectedBaggage = this.baggageItems.OrderBy(item => item.Value); - var actualBaggage = rootActivity.Baggage.OrderBy(item => item.Value); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Create_RootActivity_From_W3C_Traceparent() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - 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.False(rootActivity.Recorded); - - Assert.Null(rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); - } - - [Fact] - public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - 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); - } - - [Fact] - public void Can_Create_RootActivity_And_Ignore_Info_From_Request_Header_If_ParseHeaders_Is_False() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, - { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, parseHeaders: false); - - Assert.NotNull(rootActivity); - Assert.Null(rootActivity.ParentId); - Assert.Empty(rootActivity.Baggage); - } - - [Fact] - public void Can_Create_RootActivity_And_Start_Activity() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); - } - - [Fact] - public void Can_Create_RootActivity_And_Saved_In_HttContext() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.Same(rootActivity, context.Items[ActivityHelper.ActivityKey]); - } - - private Activity CreateActivity() - { - var activity = new Activity(TestActivityName); - this.baggageItems.ForEach(kv => activity.AddBaggage(kv.Key, kv.Value)); - - return activity; - } - - private void EnableAll(Action> onNext = null, Action onImport = null) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, a1, a2) => true, - (a, o) => onImport?.Invoke(a, o), - (a, o) => { }); - } - }); - } - - private void EnableAspNetListenerAndDisableActivity( - Action> onNext = null, - string activityName = ActivityHelper.AspNetActivityName) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, arg1, arg2) => name == activityName && arg1 == null); - } - }); - } - - private void EnableAspNetListenerAndActivity( - Action> onNext = null, - string activityName = ActivityHelper.AspNetActivityName) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, arg1, arg2) => name == activityName); - } - }); - } - - private void EnableAspNetListenerOnly(Action> onNext = null) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - activityName => false); - } - }); - } - - private class TestHttpRequest : HttpRequestBase - { - private readonly NameValueCollection headers = new NameValueCollection(); - - 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; } - } - } -} +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + using Xunit; + + public class ActivityHelperTest : IDisposable + { + private const string TestActivityName = "Activity.Test"; + private readonly List> baggageItems; + private readonly string baggageInHeader; + private IDisposable subscriptionAllListeners; + private IDisposable subscriptionAspNetListener; + + public ActivityHelperTest() + { + this.baggageItems = new List> + { + new KeyValuePair("TestKey1", "123"), + new KeyValuePair("TestKey2", "456"), + new KeyValuePair("TestKey1", "789") + }; + + this.baggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; + + // reset static fields + var allListenerField = typeof(DiagnosticListener). + GetField("s_allListenerObservable", BindingFlags.Static | BindingFlags.NonPublic); + allListenerField.SetValue(null, null); + var aspnetListenerField = typeof(ActivityHelper). + GetField("AspNetListener", BindingFlags.Static | BindingFlags.NonPublic); + aspnetListenerField.SetValue(null, new DiagnosticListener(ActivityHelper.AspNetListenerName)); + } + + public void Dispose() + { + this.subscriptionAspNetListener?.Dispose(); + this.subscriptionAllListeners?.Dispose(); + } + + [Fact] + public void Can_Restore_Activity() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.RestoreActivityIfNeeded(context.Items); + + Assert.Same(Activity.Current, rootActivity); + } + + [Fact] + public void Can_Stop_Lost_Activity() + { + this.EnableAll(pair => + { + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Not_Stop_Lost_Activity_If_Not_In_Context() + { + this.EnableAll(pair => + { + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + context.Items.Remove(ActivityHelper.ActivityKey); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(rootActivity.Duration == TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() + { + this.EnableAll(); + ActivityHelper.RestoreActivityIfNeeded(HttpContextHelper.GetFakeHttpContext().Items); + + Assert.Null(Activity.Current); + } + + [Fact] + public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() + { + this.EnableAll(); + var root = new Activity("root").Start(); + + var context = HttpContextHelper.GetFakeHttpContext(); + context.Items[ActivityHelper.ActivityKey] = root; + + var module = new TelemetryCorrelationHttpModule(); + + ActivityHelper.RestoreActivityIfNeeded(context.Items); + + Assert.Equal(root, Activity.Current); + } + + [Fact] + public void Can_Stop_Activity_Without_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = this.CreateActivity(); + rootActivity.Start(); + context.Items[ActivityHelper.ActivityKey] = rootActivity; + Thread.Sleep(100); + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Activity_With_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = this.CreateActivity(); + rootActivity.Start(); + context.Items[ActivityHelper.ActivityKey] = rootActivity; + Thread.Sleep(100); + this.EnableAspNetListenerOnly(); + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Root_Activity_With_All_Children() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + + var child = new Activity("child").Start(); + new Activity("grandchild").Start(); + + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Root_While_Child_Is_Current() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + var child = new Activity("child").Start(); + + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void OnImportActivity_Is_Called() + { + bool onImportIsCalled = false; + Activity importedActivity = null; + this.EnableAll(onImport: (activity, _) => + { + onImportIsCalled = true; + importedActivity = activity; + Assert.Null(Activity.Current); + }); + + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + Assert.True(onImportIsCalled); + Assert.NotNull(importedActivity); + Assert.Equal(importedActivity, Activity.Current); + Assert.Equal(importedActivity, rootActivity); + } + + [Fact] + public void OnImportActivity_Can_Set_Parent() + { + this.EnableAll(onImport: (activity, _) => + { + Assert.Null(activity.ParentId); + activity.SetParentId("|guid.123."); + }); + + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + + Assert.Equal("|guid.123.", Activity.Current.ParentId); + } + + [Fact] + public async Task Can_Stop_Root_Activity_If_It_Is_Broken() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var root = ActivityHelper.CreateRootActivity(context, false); + 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(context.Items); + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + Assert.Null(Activity.Current); + } + + [Fact] + public void Stop_Root_Activity_With_129_Nesting_Depth() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var root = ActivityHelper.CreateRootActivity(context, false); + + for (int i = 0; i < 129; i++) + { + new Activity("child" + i).Start(); + } + + // can stop any activity regardless of the stack depth + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + Assert.Null(Activity.Current); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerOnly(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled_With_Arguments() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndDisableActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Can_Create_RootActivity_And_Restore_Info_From_Request_Header() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.True(rootActivity.ParentId == "|aba2f1e978b2cab6.1."); + var expectedBaggage = this.baggageItems.OrderBy(item => item.Value); + var actualBaggage = rootActivity.Baggage.OrderBy(item => item.Value); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Create_RootActivity_From_W3C_Traceparent() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + 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.False(rootActivity.Recorded); + + Assert.Null(rootActivity.TraceStateString); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + 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); + } + + [Fact] + public void Can_Create_RootActivity_And_Ignore_Info_From_Request_Header_If_ParseHeaders_Is_False() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, parseHeaders: false); + + Assert.NotNull(rootActivity); + Assert.Null(rootActivity.ParentId); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivity_And_Start_Activity() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); + } + + [Fact] + public void Can_Create_RootActivity_And_Saved_In_HttContext() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.Same(rootActivity, context.Items[ActivityHelper.ActivityKey]); + } + + private Activity CreateActivity() + { + var activity = new Activity(TestActivityName); + this.baggageItems.ForEach(kv => activity.AddBaggage(kv.Key, kv.Value)); + + return activity; + } + + private void EnableAll(Action> onNext = null, Action onImport = null) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, a1, a2) => true, + (a, o) => onImport?.Invoke(a, o), + (a, o) => { }); + } + }); + } + + private void EnableAspNetListenerAndDisableActivity( + Action> onNext = null, + string activityName = ActivityHelper.AspNetActivityName) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, arg1, arg2) => name == activityName && arg1 == null); + } + }); + } + + private void EnableAspNetListenerAndActivity( + Action> onNext = null, + string activityName = ActivityHelper.AspNetActivityName) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, arg1, arg2) => name == activityName); + } + }); + } + + private void EnableAspNetListenerOnly(Action> onNext = null) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + activityName => false); + } + }); + } + + private class TestHttpRequest : HttpRequestBase + { + private readonly NameValueCollection headers = new NameValueCollection(); + + 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; } + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs index a33ae70f06d..fa6dd71f05b 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs @@ -1,93 +1,93 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.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 = HttpWorkerRequest.GetKnownRequestHeaderName(index); - - if (this.headers.ContainsKey(name)) - { - return this.headers[name]; - } - - return base.GetKnownRequestHeader(index); - } - } - } -} +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.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 = HttpWorkerRequest.GetKnownRequestHeaderName(index); + + if (this.headers.ContainsKey(name)) + { + return this.headers[name]; + } + + return base.GetKnownRequestHeader(index); + } + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj index 93c85be63f8..76337c01c09 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj @@ -1,82 +1,82 @@ - - - - Debug - AnyCPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED} - Library - net452 - 512 - prompt - 4 - - - true - full - false - $(DefineConstants);DEBUG;TRACE - - - pdbonly - true - $(DefineConstants);TRACE - - - true - - - true - $(RepositoryRoot)tools\35MSSharedLib1024.snk - $(DefineConstants);PUBLIC_RELEASE - - - false - $(RepositoryRoot)tools\Debug.snk - - - - - - - - - - - - - - - - - - - - - - - - - - - All - - - All - - - {4c8e592c-c532-4cf2-80ef-3bdd0d788d12} - Microsoft.AspNet.TelemetryCorrelation - - - - - Resources\web.config.install.xdt - - - Resources\web.config.uninstall.xdt - - - - - + + + + Debug + AnyCPU + {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED} + Library + net452 + 512 + prompt + 4 + + + true + full + false + $(DefineConstants);DEBUG;TRACE + + + pdbonly + true + $(DefineConstants);TRACE + + + true + + + true + $(RepositoryRoot)tools\35MSSharedLib1024.snk + $(DefineConstants);PUBLIC_RELEASE + + + false + $(RepositoryRoot)tools\Debug.snk + + + + + + + + + + + + + + + + + + + + + + + + + + + All + + + All + + + {4c8e592c-c532-4cf2-80ef-3bdd0d788d12} + Microsoft.AspNet.TelemetryCorrelation + + + + + Resources\web.config.install.xdt + + + Resources\web.config.uninstall.xdt + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs index dd7ff9a4b19..be739a5d996 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs @@ -1,18 +1,18 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -using System.Runtime.InteropServices; -using Xunit; - -// 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("9fae5c43-f56c-4d87-a23c-6d2d57b4abed")] - +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +using System.Runtime.InteropServices; +using Xunit; + +// 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("9fae5c43-f56c-4d87-a23c-6d2d57b4abed")] + [assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs index 696b08f8993..314b7d47b03 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs @@ -1,18 +1,18 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.Reflection; - - internal static class PropertyExtensions - { - public static object GetProperty(this object obj, string propertyName) - { - return obj.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(obj); - } - } +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Reflection; + + internal static class PropertyExtensions + { + public static object GetProperty(this object obj, string propertyName) + { + return obj.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(obj); + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs similarity index 96% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs index 6bb5445cd48..48d64c7bf1e 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs @@ -1,34 +1,34 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System; - using System.Collections.Generic; - - internal class TestDiagnosticListener : IObserver> - { - private readonly Action> onNextCallBack; - - public TestDiagnosticListener(Action> onNext) - { - this.onNextCallBack = onNext; - } - - public void OnCompleted() - { - } - - public void OnError(Exception error) - { - } - - public void OnNext(KeyValuePair value) - { - this.onNextCallBack?.Invoke(value); - } - } +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System; + using System.Collections.Generic; + + internal class TestDiagnosticListener : IObserver> + { + private readonly Action> onNextCallBack; + + public TestDiagnosticListener(Action> onNext) + { + this.onNextCallBack = onNext; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(KeyValuePair value) + { + this.onNextCallBack?.Invoke(value); + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs index 1db0bd43ef5..70bbb95e352 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs @@ -1,401 +1,401 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.IO; - using System.Xml.Linq; - using Microsoft.Web.XmlTransform; - using Xunit; - - public class WebConfigTransformTest - { - private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; - private const string UninstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.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; - } - } +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.IO; + using System.Xml.Linq; + using Microsoft.Web.XmlTransform; + using Xunit; + + public class WebConfigTransformTest + { + private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; + private const string UninstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.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; + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs rename to test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs index 5fcdbdd0ad8..6bc095b11ef 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs @@ -1,431 +1,431 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.IO; - using System.Xml.Linq; - using Microsoft.Web.XmlTransform; - using Xunit; - - public class WebConfigWithLocationTagTransformTest - { - private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.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; - } - } -} +// +// Copyright (c) .NET Foundation. All rights reserved. +// +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// + +namespace Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.IO; + using System.Xml.Linq; + using Microsoft.Web.XmlTransform; + using Xunit; + + public class WebConfigWithLocationTagTransformTest + { + private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.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; + } + } +} From 6e00a6c3208216567569737aa19ab4ed6acaf070 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Tue, 3 Aug 2021 23:22:20 -0700 Subject: [PATCH 29/45] update copyright info (#2225) --- .../ActivityExtensions.cs | 17 +++++++++++++-- .../ActivityHelper.cs | 17 +++++++++++++-- .../AspNetTelemetryCorrelationEventSource.cs | 17 +++++++++++++-- .../AssemblyInfo.cs | 21 +++++++++++++++---- .../Internal/BaseHeaderParser.cs | 17 +++++++++++++-- .../Internal/GenericHeaderParser.cs | 18 +++++++++++++--- .../Internal/HeaderUtilities.cs | 17 +++++++++++++-- .../Internal/HttpHeaderParser.cs | 18 +++++++++++++--- .../Internal/HttpParseResult.cs | 17 +++++++++++++-- .../Internal/HttpRuleParser.cs | 17 +++++++++++++-- .../Internal/NameValueHeaderValue.cs | 17 +++++++++++++-- .../TelemetryCorrelationHttpModule.cs | 17 +++++++++++++-- .../ActivityExtensionsTest.cs | 16 +++++++++++--- .../ActivityHelperTest.cs | 16 +++++++++++--- .../HttpContextHelper.cs | 16 +++++++++++--- .../Properties/AssemblyInfo.cs | 18 ---------------- .../PropertyExtensions.cs | 16 +++++++++++--- .../TestDiagnosticListener.cs | 16 +++++++++++--- .../WebConfigTransformTest.cs | 16 +++++++++++--- .../WebConfigWithLocationTagTransformTest.cs | 16 +++++++++++--- 20 files changed, 273 insertions(+), 67 deletions(-) delete mode 100644 test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs index 20760f3524a..ea282615675 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs @@ -1,5 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// 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.Specialized; diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs index edf1ad7827a..1b8d2ccc12a 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs @@ -1,5 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// 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; diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs index c4dcde2dbc0..0c9b50e319e 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs @@ -1,5 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// 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; diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs index e615d7f9847..f498bfb7804 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs @@ -1,10 +1,23 @@ -using System; +// +// 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; using System.Runtime.InteropServices; -// 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)] #if PUBLIC_RELEASE diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs index d08bc10c6b5..a6256c6278f 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs @@ -1,5 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// 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 Microsoft.AspNet.TelemetryCorrelation { diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs index 53529af2afd..46b0f86848c 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs @@ -1,6 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - +// +// 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; namespace Microsoft.AspNet.TelemetryCorrelation diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs index 486b1aa7f23..02bd2a54762 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs @@ -1,5 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// 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.Contracts; diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs index 07817e7b383..2685734f40a 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs @@ -1,6 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - +// +// 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 Microsoft.AspNet.TelemetryCorrelation { // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs index bb890382619..05f20f0de1e 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs @@ -1,5 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// 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 Microsoft.AspNet.TelemetryCorrelation { diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs index c2299a952c1..fc24d5adb63 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs @@ -1,5 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// 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.Contracts; diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs index be894c34ab0..3ab7812bb92 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs @@ -1,5 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// 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.Contracts; diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs index 418db7cfa91..7cc233efaa8 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs @@ -1,5 +1,18 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// 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.ComponentModel; diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs index 2e507e97a93..8b3e1ef7a05 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs @@ -1,7 +1,17 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. +// +// Copyright The OpenTelemetry Authors // -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs index 81e852393ac..03fc4975014 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs @@ -1,7 +1,17 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. +// +// Copyright The OpenTelemetry Authors // -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs index fa6dd71f05b..eb6feab52a5 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs @@ -1,7 +1,17 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. +// +// Copyright The OpenTelemetry Authors // -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index be739a5d996..00000000000 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// - -using System.Runtime.InteropServices; -using Xunit; - -// 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("9fae5c43-f56c-4d87-a23c-6d2d57b4abed")] - -[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs index 314b7d47b03..059b1c456ee 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs @@ -1,7 +1,17 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. +// +// Copyright The OpenTelemetry Authors // -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs index 48d64c7bf1e..bd380d3e5e3 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs @@ -1,7 +1,17 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. +// +// Copyright The OpenTelemetry Authors // -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs index 70bbb95e352..208a5e20988 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs @@ -1,7 +1,17 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. +// +// Copyright The OpenTelemetry Authors // -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs index 6bc095b11ef..4d8b3545468 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs @@ -1,7 +1,17 @@ -// -// Copyright (c) .NET Foundation. All rights reserved. +// +// Copyright The OpenTelemetry Authors // -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests From 7ec2802be89757564c5194c050e7a9ba1d6fb876 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 4 Aug 2021 09:35:32 -0700 Subject: [PATCH 30/45] Fix build (#2226) --- .../ActivityExtensions.cs | 2 +- .../ActivityHelper.cs | 2 +- .../AspNetTelemetryCorrelationEventSource.cs | 26 +++--- .../Internal/BaseHeaderParser.cs | 16 ++-- .../Internal/GenericHeaderParser.cs | 2 +- .../Internal/HttpHeaderParser.cs | 2 +- .../Internal/HttpParseResult.cs | 2 +- .../Internal/HttpRuleParser.cs | 2 +- .../Internal/NameValueHeaderValue.cs | 6 +- ...crosoft.AspNet.TelemetryCorrelation.csproj | 86 +++---------------- .../TelemetryCorrelationHttpModule.cs | 16 ++-- 11 files changed, 48 insertions(+), 114 deletions(-) diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs index ea282615675..b7a18a0461b 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.TelemetryCorrelation { /// - /// Extensions of Activity class + /// Extensions of Activity class. /// [EditorBrowsable(EditorBrowsableState.Never)] public static class ActivityExtensions diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs index 1b8d2ccc12a..9bfd9b1a8dd 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.TelemetryCorrelation { /// - /// Activity helper class + /// Activity helper class. /// internal static class ActivityHelper { diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs index 0c9b50e319e..d2b576de95f 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs @@ -34,76 +34,76 @@ internal sealed class AspNetTelemetryCorrelationEventSource : EventSource [NonEvent] public void ActivityException(string id, string eventName, Exception ex) { - if (IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) { - ActivityException(id, eventName, ex.ToString()); + this.ActivityException(id, eventName, ex.ToString()); } } [Event(1, Message = "Callback='{0}'", Level = EventLevel.Verbose)] public void TraceCallback(string callback) { - WriteEvent(1, callback); + this.WriteEvent(1, callback); } [Event(2, Message = "Activity started, Id='{0}'", Level = EventLevel.Verbose)] public void ActivityStarted(string id) { - WriteEvent(2, id); + this.WriteEvent(2, id); } [Event(3, Message = "Activity stopped, Id='{0}', Name='{1}'", Level = EventLevel.Verbose)] public void ActivityStopped(string id, string eventName) { - WriteEvent(3, id, eventName); + this.WriteEvent(3, id, eventName); } [Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)] public void HeaderParsingError(string headerName, string headerValue) { - WriteEvent(4, headerName, headerValue); + this.WriteEvent(4, headerName, headerValue); } [Event(5, Message = "Failed to extract activity, reason '{0}'", Level = EventLevel.Error)] public void ActvityExtractionError(string reason) { - WriteEvent(5, reason); + this.WriteEvent(5, reason); } [Event(6, Message = "Finished Activity is detected on the stack, Id: '{0}', Name: '{1}'", Level = EventLevel.Error)] public void FinishedActivityIsDetected(string id, string name) { - WriteEvent(6, id, name); + this.WriteEvent(6, id, name); } [Event(7, Message = "System.Diagnostics.Activity stack is too deep. This is a code authoring error, Activity will not be stopped.", Level = EventLevel.Error)] public void ActivityStackIsTooDeepError() { - WriteEvent(7); + this.WriteEvent(7); } [Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] public void ActivityRestored(string id) { - WriteEvent(8, id); + this.WriteEvent(8, id); } [Event(9, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] public void OnExecuteRequestStepInvokationError(string error) { - WriteEvent(9, error); + this.WriteEvent(9, error); } [Event(10, Message = "System.Diagnostics.Activity stack is too deep. Current Id: '{0}', Name: '{1}'", Level = EventLevel.Warning)] public void ActivityStackIsTooDeepDetails(string id, string name) { - WriteEvent(10, id, name); + this.WriteEvent(10, id, name); } [Event(11, Message = "Activity exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] public void ActivityException(string id, string eventName, string ex) { - WriteEvent(11, id, eventName, ex); + this.WriteEvent(11, id, eventName, ex); } } } diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs index a6256c6278f..5f152f67fba 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs @@ -35,29 +35,29 @@ public sealed override bool TryParseValue(string value, ref int index, out T par // Accept: text/plain; q=0.2 if (string.IsNullOrEmpty(value) || (index == value.Length)) { - return SupportsMultipleValues; + return this.SupportsMultipleValues; } var separatorFound = false; - var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, SupportsMultipleValues, out separatorFound); + var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, this.SupportsMultipleValues, out separatorFound); - if (separatorFound && !SupportsMultipleValues) + if (separatorFound && !this.SupportsMultipleValues) { return false; // leading separators not allowed if we don't support multiple values. } if (current == value.Length) { - if (SupportsMultipleValues) + if (this.SupportsMultipleValues) { index = current; } - return SupportsMultipleValues; + return this.SupportsMultipleValues; } T result; - var length = GetParsedValueLength(value, current, out result); + var length = this.GetParsedValueLength(value, current, out result); if (length == 0) { @@ -65,10 +65,10 @@ public sealed override bool TryParseValue(string value, ref int index, out T par } current = current + length; - current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues, out separatorFound); + current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, this.SupportsMultipleValues, out separatorFound); // If we support multiple values and we've not reached the end of the string, then we must have a separator. - if ((separatorFound && !SupportsMultipleValues) || (!separatorFound && (current < value.Length))) + if ((separatorFound && !this.SupportsMultipleValues) || (!separatorFound && (current < value.Length))) { return false; } diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs index 46b0f86848c..78b90508f75 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs @@ -37,7 +37,7 @@ internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDe protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) { - return getParsedValueLength(value, startIndex, out parsedValue); + return this.getParsedValueLength(value, startIndex, out parsedValue); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs index 2685734f40a..435eee12c07 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs @@ -27,7 +27,7 @@ protected HttpHeaderParser(bool supportsMultipleValues) public bool SupportsMultipleValues { - get { return supportsMultipleValues; } + get { return this.supportsMultipleValues; } } // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs index 05f20f0de1e..a0105e9b4cc 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.TelemetryCorrelation internal enum HttpParseResult { /// - /// Parsed succesfully. + /// Parsed successfully. /// Parsed, diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs index fc24d5adb63..de5e3339e27 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs @@ -133,7 +133,7 @@ internal static HttpParseResult GetQuotedPairLength(string input, int startIndex return HttpParseResult.NotParsed; } - // Quoted-char has 2 characters. Check wheter there are 2 chars left ('\' + char) + // Quoted-char has 2 characters. Check whether there are 2 chars left ('\' + char) // If so, check whether the character is in the range 0-127. If not, it's an invalid value. if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) { diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs index 3ab7812bb92..a0dd899445b 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,12 +38,12 @@ private NameValueHeaderValue() public string Name { - get { return name; } + get { return this.name; } } public string Value { - get { return value; } + get { return this.value; } } public static bool TryParse(string input, out NameValueHeaderValue parsedValue) diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj index 1d119876928..ed41c1df670 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj @@ -1,85 +1,19 @@ - - + - {4C8E592C-C532-4CF2-80EF-3BDD0D788D12} - Library - Properties - Microsoft.AspNet.TelemetryCorrelation - Microsoft.AspNet.TelemetryCorrelation - net45 - 512 - true - $(OutputPath)$(AssemblyName).xml - ..\..\ - Microsoft.AspNet.TelemetryCorrelation.ruleset - true - prompt - 4 - true - $(OutputPath)/$(TargetFramework)/$(AssemblyName).xml - - - true - $(RepositoryRoot)tools\35MSSharedLib1024.snk - $(DefineConstants);PUBLIC_RELEASE - - - false - $(RepositoryRoot)tools\Debug.snk - - - full - false - $(DefineConstants);DEBUG;TRACE - - - pdbonly - true - $(DefineConstants);TRACE - - - Microsoft Corporation - - True - True - snupkg - - Microsoft.AspNet.TelemetryCorrelation - - - Microsoft - Microsoft Asp.Net telemetry correlation + net461 A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. - © Microsoft Corporation. All rights reserved. - Apache-2.0 - http://www.asp.net/ - http://go.microsoft.com/fwlink/?LinkID=288859 - Diagnostics DiagnosticSource Correlation Activity ASP.NET - https://github.com/aspnet/Microsoft.AspNet.TelemetryCorrelation/ - Git - Dependency - content + + $(NoWarn),1591,CS0618 + core- + - - - All - - - All - - - All - - - All - - - - - + + diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs index 7cc233efaa8..86f985184d0 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs @@ -56,9 +56,9 @@ public void Dispose() /// public void Init(HttpApplication context) { - context.BeginRequest += Application_BeginRequest; - context.EndRequest += Application_EndRequest; - context.Error += Application_Error; + context.BeginRequest += this.Application_BeginRequest; + context.EndRequest += this.Application_EndRequest; + context.Error += this.Application_Error; // OnExecuteRequestStep is availabile starting with 4.7.1 // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), @@ -67,7 +67,7 @@ public void Init(HttpApplication context) { try { - onStepMethodInfo.Invoke(context, new object[] { (Action)OnExecuteRequestStep }); + onStepMethodInfo.Invoke(context, new object[] { (Action)this.OnExecuteRequestStep }); } catch (Exception e) { @@ -76,7 +76,7 @@ public void Init(HttpApplication context) } else { - context.PreRequestHandlerExecute += Application_PreRequestHandlerExecute; + context.PreRequestHandlerExecute += this.Application_PreRequestHandlerExecute; } } @@ -106,7 +106,7 @@ private void Application_BeginRequest(object sender, EventArgs e) { var context = ((HttpApplication)sender).Context; AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); - ActivityHelper.CreateRootActivity(context, ParseHeaders); + ActivityHelper.CreateRootActivity(context, this.ParseHeaders); context.Items[BeginCalledFlag] = true; } @@ -141,7 +141,7 @@ private void Application_EndRequest(object sender, EventArgs e) else { // Activity has never been started - ActivityHelper.CreateRootActivity(context, ParseHeaders); + ActivityHelper.CreateRootActivity(context, this.ParseHeaders); } } @@ -162,7 +162,7 @@ private void Application_Error(object sender, EventArgs e) { if (!context.Items.Contains(BeginCalledFlag)) { - ActivityHelper.CreateRootActivity(context, ParseHeaders); + ActivityHelper.CreateRootActivity(context, this.ParseHeaders); } ActivityHelper.WriteActivityException(context.Items, exception); From bc1e9538953dc3dfa80bcd8d574efe3438d0b4fd Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 4 Aug 2021 09:59:05 -0700 Subject: [PATCH 31/45] fix build (#2229) --- .../AssemblyInfo.cs | 17 ++-- .../ActivityExtensionsTest.cs | 26 +++--- .../ActivityHelperTest.cs | 6 +- ...t.AspNet.TelemetryCorrelation.Tests.csproj | 85 ++++--------------- 4 files changed, 46 insertions(+), 88 deletions(-) diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs index f498bfb7804..890303af426 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs @@ -14,14 +14,21 @@ // limitations under the License. // -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: ComVisible(false)] -#if PUBLIC_RELEASE -[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests" + AssemblyInfo.PublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; +} #else -[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100319b35b21a993df850846602dae9e86d8fbb0528a0ad488ecd6414db798996534381825f94f90d8b16b72a51c4e7e07cf66ff3293c1046c045fafc354cfcc15fc177c748111e4a8c5a34d3940e7f3789dd58a928add6160d6f9cc219680253dcea88a034e7d472de51d4989c7783e19343839273e0e63a43b99ab338149dd59f")] -#endif \ No newline at end of file +internal static class AssemblyInfo +{ + public const string PublicKey = ""; +} +#endif diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs index 8b3e1ef7a05..9f51e8dc590 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs @@ -46,7 +46,7 @@ public void Can_Restore_First_RequestId_When_Multiple_RequestId_In_Headers() var requestHeaders = new NameValueCollection { { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" } + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" }, }; Assert.True(activity.Extract(requestHeaders)); @@ -61,7 +61,7 @@ public void Extract_RequestId_Is_Ignored_When_Traceparent_Is_Present() var requestHeaders = new NameValueCollection { { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" } + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, }; Assert.True(activity.Extract(requestHeaders)); @@ -83,7 +83,7 @@ public void Can_Extract_First_Traceparent_When_Multiple_Traceparents_In_Headers( var requestHeaders = new NameValueCollection { { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" } + { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" }, }; Assert.True(activity.Extract(requestHeaders)); @@ -121,7 +121,7 @@ public void Can_Extract_RootActivity_From_W3C_Headers_And_CC() { new KeyValuePair("key1", "123"), new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789") + new KeyValuePair("key3", "789"), }; var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); @@ -170,7 +170,7 @@ public void Restore_Empty_RequestId_Should_Not_Throw_Exception() var activity = new Activity(TestActivityName); var requestHeaders = new NameValueCollection { - { ActivityExtensions.RequestIdHeaderName, string.Empty } + { ActivityExtensions.RequestIdHeaderName, string.Empty }, }; Assert.False(activity.Extract(requestHeaders)); @@ -184,7 +184,7 @@ public void Restore_Empty_Traceparent_Should_Not_Throw_Exception() var activity = new Activity(TestActivityName); var requestHeaders = new NameValueCollection { - { ActivityExtensions.TraceparentHeaderName, string.Empty } + { ActivityExtensions.TraceparentHeaderName, string.Empty }, }; Assert.False(activity.Extract(requestHeaders)); @@ -200,7 +200,7 @@ public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() var requestHeaders = new NameValueCollection { { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" } + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, }; Assert.True(activity.Extract(requestHeaders)); @@ -209,7 +209,7 @@ public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() { new KeyValuePair("key1", "123"), new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789") + new KeyValuePair("key3", "789"), }; var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); @@ -225,7 +225,7 @@ public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, { ActivityExtensions.CorrelationContextHeaderName, "key4=abc,key5=def" }, - { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" } + { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" }, }; Assert.True(activity.Extract(requestHeaders)); @@ -237,7 +237,7 @@ public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() new KeyValuePair("key3", "789"), new KeyValuePair("key4", "abc"), new KeyValuePair("key5", "def"), - new KeyValuePair("key6", "xyz") + new KeyValuePair("key6", "xyz"), }; var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); @@ -254,7 +254,7 @@ public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Heade { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, { ActivityExtensions.CorrelationContextHeaderName, "key4=abc;key5=def" }, { ActivityExtensions.CorrelationContextHeaderName, "key6????xyz" }, - { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" } + { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" }, }; Assert.True(activity.Extract(requestHeaders)); @@ -263,7 +263,7 @@ public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Heade { new KeyValuePair("key1", "123"), new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789") + new KeyValuePair("key3", "789"), }; var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); @@ -304,7 +304,7 @@ public void Validates_Correlation_Context_Length(string correlationContext, int var requestHeaders = new NameValueCollection { { ActivityExtensions.RequestIdHeaderName, "|abc.1" }, - { ActivityExtensions.CorrelationContextHeaderName, correlationContext } + { ActivityExtensions.CorrelationContextHeaderName, correlationContext }, }; Assert.True(activity.Extract(requestHeaders)); diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs index 03fc4975014..40e86a9e8d7 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs @@ -42,7 +42,7 @@ public ActivityHelperTest() { new KeyValuePair("TestKey1", "123"), new KeyValuePair("TestKey2", "456"), - new KeyValuePair("TestKey1", "789") + new KeyValuePair("TestKey1", "789"), }; this.baggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; @@ -331,7 +331,7 @@ public void Can_Create_RootActivity_And_Restore_Info_From_Request_Header() var requestHeaders = new Dictionary { { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, - { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader }, }; var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); @@ -401,7 +401,7 @@ public void Can_Create_RootActivity_And_Ignore_Info_From_Request_Header_If_Parse var requestHeaders = new Dictionary { { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, - { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader } + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader }, }; var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj index 76337c01c09..93819ccf2b3 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj @@ -1,82 +1,33 @@ - - + - Debug - AnyCPU - {9FAE5C43-F56C-4D87-A23C-6D2D57B4ABED} - Library - net452 - 512 - prompt - 4 - - - true - full - false - $(DefineConstants);DEBUG;TRACE - - - pdbonly - true - $(DefineConstants);TRACE - - - true - - - true - $(RepositoryRoot)tools\35MSSharedLib1024.snk - $(DefineConstants);PUBLIC_RELEASE - - - false - $(RepositoryRoot)tools\Debug.snk + Unit test project for ASP.NET HttpModule + net461 + false + - - - - - - - + - - - - - - - - - - - - - - - All - - - All + + + all + runtime; build; native; contentfiles; analyzers - - {4c8e592c-c532-4cf2-80ef-3bdd0d788d12} - Microsoft.AspNet.TelemetryCorrelation - + + - + + + + + Resources\web.config.install.xdt - + Resources\web.config.uninstall.xdt - - - \ No newline at end of file From 0ccbf737bcd5a53436bf2b1524e5f30c515e1b1c Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 4 Aug 2021 12:24:25 -0700 Subject: [PATCH 32/45] Fix format (#2231) --- .../ActivityExtensions.cs | 318 ++--- .../ActivityHelper.cs | 324 ++--- .../AspNetTelemetryCorrelationEventSource.cs | 220 ++-- .../AssemblyInfo.cs | 68 +- .../Internal/BaseHeaderParser.cs | 166 +-- .../Internal/GenericHeaderParser.cs | 87 +- .../Internal/HeaderUtilities.cs | 118 +- .../Internal/HttpHeaderParser.cs | 79 +- .../Internal/HttpParseResult.cs | 74 +- .../Internal/HttpRuleParser.cs | 566 ++++---- .../Internal/NameValueHeaderValue.cs | 260 ++-- ...crosoft.AspNet.TelemetryCorrelation.csproj | 42 +- .../README.md | 27 +- .../TelemetryCorrelationHttpModule.cs | 344 ++--- .../ActivityExtensionsTest.cs | 644 +++++----- .../ActivityHelperTest.cs | 1138 ++++++++--------- .../HttpContextHelper.cs | 206 +-- ...t.AspNet.TelemetryCorrelation.Tests.csproj | 66 +- .../PropertyExtensions.cs | 56 +- .../TestDiagnosticListener.cs | 88 +- .../WebConfigTransformTest.cs | 822 ++++++------ .../WebConfigWithLocationTagTransformTest.cs | 882 ++++++------- 22 files changed, 3299 insertions(+), 3296 deletions(-) diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs index b7a18a0461b..8492e7327df 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs @@ -1,159 +1,159 @@ -// -// 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.Specialized; -using System.ComponentModel; -using System.Diagnostics; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Extensions of Activity class. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static class ActivityExtensions - { - /// - /// Http header name to carry the Request Id: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md. - /// - internal const string RequestIdHeaderName = "Request-Id"; - - /// - /// Http header name to carry the traceparent: https://www.w3.org/TR/trace-context/. - /// - internal const string TraceparentHeaderName = "traceparent"; - - /// - /// Http header name to carry the tracestate: https://www.w3.org/TR/trace-context/. - /// - internal const string TracestateHeaderName = "tracestate"; - - /// - /// Http header name to carry the correlation context. - /// - internal const string CorrelationContextHeaderName = "Correlation-Context"; - - /// - /// Maximum length of Correlation-Context header value. - /// - internal const int MaxCorrelationContextLength = 1024; - - /// - /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. - /// - /// Instance of activity that has not been started yet. - /// Request headers collection. - /// true if request was parsed successfully, false - otherwise. - public static bool Extract(this Activity activity, NameValueCollection requestHeaders) - { - if (activity == null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("activity is null"); - return false; - } - - if (activity.ParentId != null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); - return false; - } - - if (activity.Id != null) - { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("Activity is already started"); - return false; - } - - var parents = requestHeaders.GetValues(TraceparentHeaderName); - if (parents == null || parents.Length == 0) - { - parents = requestHeaders.GetValues(RequestIdHeaderName); - } - - if (parents != null && parents.Length > 0 && !string.IsNullOrEmpty(parents[0])) - { - // there may be several Request-Id or traceparent headers, but we only read the first one - activity.SetParentId(parents[0]); - - var tracestates = requestHeaders.GetValues(TracestateHeaderName); - if (tracestates != null && tracestates.Length > 0) - { - if (tracestates.Length == 1 && !string.IsNullOrEmpty(tracestates[0])) - { - activity.TraceStateString = tracestates[0]; - } - else - { - activity.TraceStateString = string.Join(",", tracestates); - } - } - - // Header format - Correlation-Context: key1=value1, key2=value2 - var baggages = requestHeaders.GetValues(CorrelationContextHeaderName); - if (baggages != null) - { - int correlationContextLength = -1; - - // there may be several Correlation-Context header - foreach (var item in baggages) - { - if (correlationContextLength >= MaxCorrelationContextLength) - { - break; - } - - foreach (var pair in item.Split(',')) - { - correlationContextLength += pair.Length + 1; // pair and comma - - if (correlationContextLength >= MaxCorrelationContextLength) - { - break; - } - - if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem)) - { - activity.AddBaggage(baggageItem.Name, baggageItem.Value); - } - else - { - AspNetTelemetryCorrelationEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); - } - } - } - } - - return true; - } - - return false; - } - - /// - /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. - /// - /// Instance of activity that has not been started yet. - /// Request headers collection. - /// true if request was parsed successfully, false - otherwise. - [Obsolete("Method is obsolete, use Extract method instead", true)] - [EditorBrowsable(EditorBrowsableState.Never)] - public static bool TryParse(this Activity activity, NameValueCollection requestHeaders) - { - return Extract(activity, requestHeaders); - } - } -} +// +// 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.Specialized; +using System.ComponentModel; +using System.Diagnostics; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Extensions of Activity class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ActivityExtensions + { + /// + /// Http header name to carry the Request Id: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md. + /// + internal const string RequestIdHeaderName = "Request-Id"; + + /// + /// Http header name to carry the traceparent: https://www.w3.org/TR/trace-context/. + /// + internal const string TraceparentHeaderName = "traceparent"; + + /// + /// Http header name to carry the tracestate: https://www.w3.org/TR/trace-context/. + /// + internal const string TracestateHeaderName = "tracestate"; + + /// + /// Http header name to carry the correlation context. + /// + internal const string CorrelationContextHeaderName = "Correlation-Context"; + + /// + /// Maximum length of Correlation-Context header value. + /// + internal const int MaxCorrelationContextLength = 1024; + + /// + /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. + /// + /// Instance of activity that has not been started yet. + /// Request headers collection. + /// true if request was parsed successfully, false - otherwise. + public static bool Extract(this Activity activity, NameValueCollection requestHeaders) + { + if (activity == null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("activity is null"); + return false; + } + + if (activity.ParentId != null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); + return false; + } + + if (activity.Id != null) + { + AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("Activity is already started"); + return false; + } + + var parents = requestHeaders.GetValues(TraceparentHeaderName); + if (parents == null || parents.Length == 0) + { + parents = requestHeaders.GetValues(RequestIdHeaderName); + } + + if (parents != null && parents.Length > 0 && !string.IsNullOrEmpty(parents[0])) + { + // there may be several Request-Id or traceparent headers, but we only read the first one + activity.SetParentId(parents[0]); + + var tracestates = requestHeaders.GetValues(TracestateHeaderName); + if (tracestates != null && tracestates.Length > 0) + { + if (tracestates.Length == 1 && !string.IsNullOrEmpty(tracestates[0])) + { + activity.TraceStateString = tracestates[0]; + } + else + { + activity.TraceStateString = string.Join(",", tracestates); + } + } + + // Header format - Correlation-Context: key1=value1, key2=value2 + var baggages = requestHeaders.GetValues(CorrelationContextHeaderName); + if (baggages != null) + { + int correlationContextLength = -1; + + // there may be several Correlation-Context header + foreach (var item in baggages) + { + if (correlationContextLength >= MaxCorrelationContextLength) + { + break; + } + + foreach (var pair in item.Split(',')) + { + correlationContextLength += pair.Length + 1; // pair and comma + + if (correlationContextLength >= MaxCorrelationContextLength) + { + break; + } + + if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem)) + { + activity.AddBaggage(baggageItem.Name, baggageItem.Value); + } + else + { + AspNetTelemetryCorrelationEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); + } + } + } + } + + return true; + } + + return false; + } + + /// + /// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity. + /// + /// Instance of activity that has not been started yet. + /// Request headers collection. + /// true if request was parsed successfully, false - otherwise. + [Obsolete("Method is obsolete, use Extract method instead", true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool TryParse(this Activity activity, NameValueCollection requestHeaders) + { + return Extract(activity, requestHeaders); + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs index 9bfd9b1a8dd..057891fe8ce 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs @@ -1,162 +1,162 @@ -// -// 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; -using System.Diagnostics; -using System.Web; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Activity helper class. - /// - internal static class ActivityHelper - { - /// - /// Listener name. - /// - public const string AspNetListenerName = "Microsoft.AspNet.TelemetryCorrelation"; - - /// - /// Activity name for http request. - /// - public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; - - /// - /// Event name for the activity start event. - /// - public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start"; - - /// - /// Key to store the activity in HttpContext. - /// - public const string ActivityKey = "__AspnetActivity__"; - - private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName); - - private static readonly object EmptyPayload = new object(); - - /// - /// Stops the activity and notifies listeners about it. - /// - /// HttpContext.Items. - public static void StopAspNetActivity(IDictionary contextItems) - { - var currentActivity = Activity.Current; - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - - if (currentActivity != aspNetActivity) - { - Activity.Current = aspNetActivity; - currentActivity = aspNetActivity; - } - - if (currentActivity != null) - { - // stop Activity with Stop event - AspNetListener.StopActivity(currentActivity, EmptyPayload); - contextItems[ActivityKey] = null; - } - - AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); - } - - /// - /// Creates root (first level) activity that describes incoming request. - /// - /// Current HttpContext. - /// Determines if headers should be parsed get correlation ids. - /// New root activity. - public static Activity CreateRootActivity(HttpContext context, bool parseHeaders) - { - if (AspNetListener.IsEnabled() && AspNetListener.IsEnabled(AspNetActivityName)) - { - var rootActivity = new Activity(AspNetActivityName); - - if (parseHeaders) - { - rootActivity.Extract(context.Request.Unvalidated.Headers); - } - - AspNetListener.OnActivityImport(rootActivity, null); - - if (StartAspNetActivity(rootActivity)) - { - context.Items[ActivityKey] = rootActivity; - AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id); - return rootActivity; - } - } - - return null; - } - - public static void WriteActivityException(IDictionary contextItems, Exception exception) - { - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - - if (aspNetActivity != null) - { - if (Activity.Current != aspNetActivity) - { - Activity.Current = aspNetActivity; - } - - AspNetListener.Write(aspNetActivity.OperationName + ".Exception", exception); - AspNetTelemetryCorrelationEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, 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. - /// - /// HttpContext.Items dictionary. - internal static void RestoreActivityIfNeeded(IDictionary contextItems) - { - if (Activity.Current == null) - { - Activity aspNetActivity = (Activity)contextItems[ActivityKey]; - if (aspNetActivity != null) - { - Activity.Current = aspNetActivity; - } - } - } - - private static bool StartAspNetActivity(Activity activity) - { - if (AspNetListener.IsEnabled(AspNetActivityName, activity, EmptyPayload)) - { - if (AspNetListener.IsEnabled(AspNetActivityStartName)) - { - AspNetListener.StartActivity(activity, EmptyPayload); - } - else - { - activity.Start(); - } - - return true; - } - - return false; - } - } -} +// +// 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; +using System.Diagnostics; +using System.Web; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Activity helper class. + /// + internal static class ActivityHelper + { + /// + /// Listener name. + /// + public const string AspNetListenerName = "Microsoft.AspNet.TelemetryCorrelation"; + + /// + /// Activity name for http request. + /// + public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn"; + + /// + /// Event name for the activity start event. + /// + public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start"; + + /// + /// Key to store the activity in HttpContext. + /// + public const string ActivityKey = "__AspnetActivity__"; + + private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName); + + private static readonly object EmptyPayload = new object(); + + /// + /// Stops the activity and notifies listeners about it. + /// + /// HttpContext.Items. + public static void StopAspNetActivity(IDictionary contextItems) + { + var currentActivity = Activity.Current; + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + + if (currentActivity != aspNetActivity) + { + Activity.Current = aspNetActivity; + currentActivity = aspNetActivity; + } + + if (currentActivity != null) + { + // stop Activity with Stop event + AspNetListener.StopActivity(currentActivity, EmptyPayload); + contextItems[ActivityKey] = null; + } + + AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); + } + + /// + /// Creates root (first level) activity that describes incoming request. + /// + /// Current HttpContext. + /// Determines if headers should be parsed get correlation ids. + /// New root activity. + public static Activity CreateRootActivity(HttpContext context, bool parseHeaders) + { + if (AspNetListener.IsEnabled() && AspNetListener.IsEnabled(AspNetActivityName)) + { + var rootActivity = new Activity(AspNetActivityName); + + if (parseHeaders) + { + rootActivity.Extract(context.Request.Unvalidated.Headers); + } + + AspNetListener.OnActivityImport(rootActivity, null); + + if (StartAspNetActivity(rootActivity)) + { + context.Items[ActivityKey] = rootActivity; + AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id); + return rootActivity; + } + } + + return null; + } + + public static void WriteActivityException(IDictionary contextItems, Exception exception) + { + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + + if (aspNetActivity != null) + { + if (Activity.Current != aspNetActivity) + { + Activity.Current = aspNetActivity; + } + + AspNetListener.Write(aspNetActivity.OperationName + ".Exception", exception); + AspNetTelemetryCorrelationEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, 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. + /// + /// HttpContext.Items dictionary. + internal static void RestoreActivityIfNeeded(IDictionary contextItems) + { + if (Activity.Current == null) + { + Activity aspNetActivity = (Activity)contextItems[ActivityKey]; + if (aspNetActivity != null) + { + Activity.Current = aspNetActivity; + } + } + } + + private static bool StartAspNetActivity(Activity activity) + { + if (AspNetListener.IsEnabled(AspNetActivityName, activity, EmptyPayload)) + { + if (AspNetListener.IsEnabled(AspNetActivityStartName)) + { + AspNetListener.StartActivity(activity, EmptyPayload); + } + else + { + activity.Start(); + } + + return true; + } + + return false; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs index d2b576de95f..f439add5081 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs @@ -1,110 +1,110 @@ -// -// 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; -#pragma warning disable SA1600 // Elements must be documented - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// ETW EventSource tracing class. - /// - [EventSource(Name = "Microsoft-AspNet-Telemetry-Correlation", Guid = "ace2021e-e82c-5502-d81d-657f27612673")] - internal sealed class AspNetTelemetryCorrelationEventSource : EventSource - { - /// - /// Instance of the PlatformEventSource class. - /// - public static readonly AspNetTelemetryCorrelationEventSource Log = new AspNetTelemetryCorrelationEventSource(); - - [NonEvent] - public void ActivityException(string id, string eventName, Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.ActivityException(id, eventName, ex.ToString()); - } - } - - [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}', Name='{1}'", Level = EventLevel.Verbose)] - public void ActivityStopped(string id, string eventName) - { - this.WriteEvent(3, id, eventName); - } - - [Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)] - public void HeaderParsingError(string headerName, string headerValue) - { - this.WriteEvent(4, headerName, headerValue); - } - - [Event(5, Message = "Failed to extract activity, reason '{0}'", Level = EventLevel.Error)] - public void ActvityExtractionError(string reason) - { - this.WriteEvent(5, reason); - } - - [Event(6, Message = "Finished Activity is detected on the stack, Id: '{0}', Name: '{1}'", Level = EventLevel.Error)] - public void FinishedActivityIsDetected(string id, string name) - { - this.WriteEvent(6, id, name); - } - - [Event(7, Message = "System.Diagnostics.Activity stack is too deep. This is a code authoring error, Activity will not be stopped.", Level = EventLevel.Error)] - public void ActivityStackIsTooDeepError() - { - this.WriteEvent(7); - } - - [Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] - public void ActivityRestored(string id) - { - this.WriteEvent(8, id); - } - - [Event(9, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] - public void OnExecuteRequestStepInvokationError(string error) - { - this.WriteEvent(9, error); - } - - [Event(10, Message = "System.Diagnostics.Activity stack is too deep. Current Id: '{0}', Name: '{1}'", Level = EventLevel.Warning)] - public void ActivityStackIsTooDeepDetails(string id, string name) - { - this.WriteEvent(10, id, name); - } - - [Event(11, Message = "Activity exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] - public void ActivityException(string id, string eventName, string ex) - { - this.WriteEvent(11, id, eventName, ex); - } - } -} -#pragma warning restore SA1600 // Elements must be documented \ No newline at end of file +// +// 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; +#pragma warning disable SA1600 // Elements must be documented + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// ETW EventSource tracing class. + /// + [EventSource(Name = "Microsoft-AspNet-Telemetry-Correlation", Guid = "ace2021e-e82c-5502-d81d-657f27612673")] + internal sealed class AspNetTelemetryCorrelationEventSource : EventSource + { + /// + /// Instance of the PlatformEventSource class. + /// + public static readonly AspNetTelemetryCorrelationEventSource Log = new AspNetTelemetryCorrelationEventSource(); + + [NonEvent] + public void ActivityException(string id, string eventName, Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.ActivityException(id, eventName, ex.ToString()); + } + } + + [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}', Name='{1}'", Level = EventLevel.Verbose)] + public void ActivityStopped(string id, string eventName) + { + this.WriteEvent(3, id, eventName); + } + + [Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)] + public void HeaderParsingError(string headerName, string headerValue) + { + this.WriteEvent(4, headerName, headerValue); + } + + [Event(5, Message = "Failed to extract activity, reason '{0}'", Level = EventLevel.Error)] + public void ActvityExtractionError(string reason) + { + this.WriteEvent(5, reason); + } + + [Event(6, Message = "Finished Activity is detected on the stack, Id: '{0}', Name: '{1}'", Level = EventLevel.Error)] + public void FinishedActivityIsDetected(string id, string name) + { + this.WriteEvent(6, id, name); + } + + [Event(7, Message = "System.Diagnostics.Activity stack is too deep. This is a code authoring error, Activity will not be stopped.", Level = EventLevel.Error)] + public void ActivityStackIsTooDeepError() + { + this.WriteEvent(7); + } + + [Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)] + public void ActivityRestored(string id) + { + this.WriteEvent(8, id); + } + + [Event(9, Message = "Failed to invoke OnExecuteRequestStep, Error='{0}'", Level = EventLevel.Error)] + public void OnExecuteRequestStepInvokationError(string error) + { + this.WriteEvent(9, error); + } + + [Event(10, Message = "System.Diagnostics.Activity stack is too deep. Current Id: '{0}', Name: '{1}'", Level = EventLevel.Warning)] + public void ActivityStackIsTooDeepDetails(string id, string name) + { + this.WriteEvent(10, id, name); + } + + [Event(11, Message = "Activity exception, Id='{0}', Name='{1}': {2}", Level = EventLevel.Error)] + public void ActivityException(string id, string eventName, string ex) + { + this.WriteEvent(11, id, eventName, ex); + } + } +} +#pragma warning restore SA1600 // Elements must be documented diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs index 890303af426..d5967973522 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs @@ -1,34 +1,34 @@ -// -// 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)] - -[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests" + AssemblyInfo.PublicKey)] - -#if SIGNED -internal static class AssemblyInfo -{ - public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; -} -#else -internal static class AssemblyInfo -{ - public const string PublicKey = ""; -} -#endif +// +// 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)] + +[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests" + AssemblyInfo.PublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; +} +#endif diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs index 5f152f67fba..3e884dd095f 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs @@ -1,83 +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. -// - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs - internal abstract class BaseHeaderParser : HttpHeaderParser - { - protected BaseHeaderParser(bool supportsMultipleValues) - : base(supportsMultipleValues) - { - } - - public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) - { - parsedValue = default(T); - - // If multiple values are supported (i.e. list of values), then accept an empty string: The header may - // be added multiple times to the request/response message. E.g. - // Accept: text/xml; q=1 - // Accept: - // Accept: text/plain; q=0.2 - if (string.IsNullOrEmpty(value) || (index == value.Length)) - { - return this.SupportsMultipleValues; - } - - var separatorFound = false; - var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, this.SupportsMultipleValues, out separatorFound); - - if (separatorFound && !this.SupportsMultipleValues) - { - return false; // leading separators not allowed if we don't support multiple values. - } - - if (current == value.Length) - { - if (this.SupportsMultipleValues) - { - index = current; - } - - return this.SupportsMultipleValues; - } - - T result; - var length = this.GetParsedValueLength(value, current, out result); - - if (length == 0) - { - return false; - } - - current = current + length; - current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, this.SupportsMultipleValues, out separatorFound); - - // If we support multiple values and we've not reached the end of the string, then we must have a separator. - if ((separatorFound && !this.SupportsMultipleValues) || (!separatorFound && (current < value.Length))) - { - return false; - } - - index = current; - parsedValue = result; - return true; - } - - protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue); - } -} \ No newline at end of file +// +// 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 Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs + internal abstract class BaseHeaderParser : HttpHeaderParser + { + protected BaseHeaderParser(bool supportsMultipleValues) + : base(supportsMultipleValues) + { + } + + public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) + { + parsedValue = default(T); + + // If multiple values are supported (i.e. list of values), then accept an empty string: The header may + // be added multiple times to the request/response message. E.g. + // Accept: text/xml; q=1 + // Accept: + // Accept: text/plain; q=0.2 + if (string.IsNullOrEmpty(value) || (index == value.Length)) + { + return this.SupportsMultipleValues; + } + + var separatorFound = false; + var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, this.SupportsMultipleValues, out separatorFound); + + if (separatorFound && !this.SupportsMultipleValues) + { + return false; // leading separators not allowed if we don't support multiple values. + } + + if (current == value.Length) + { + if (this.SupportsMultipleValues) + { + index = current; + } + + return this.SupportsMultipleValues; + } + + T result; + var length = this.GetParsedValueLength(value, current, out result); + + if (length == 0) + { + return false; + } + + current = current + length; + current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, this.SupportsMultipleValues, out separatorFound); + + // If we support multiple values and we've not reached the end of the string, then we must have a separator. + if ((separatorFound && !this.SupportsMultipleValues) || (!separatorFound && (current < value.Length))) + { + return false; + } + + index = current; + parsedValue = result; + return true; + } + + protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue); + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs index 78b90508f75..136dc224c81 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs @@ -1,43 +1,44 @@ -// -// 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; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs - internal sealed class GenericHeaderParser : BaseHeaderParser - { - private GetParsedValueLengthDelegate getParsedValueLength; - - internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) - : base(supportsMultipleValues) - { - if (getParsedValueLength == null) - { - throw new ArgumentNullException(nameof(getParsedValueLength)); - } - - this.getParsedValueLength = getParsedValueLength; - } - - internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); - - protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) - { - return this.getParsedValueLength(value, startIndex, out parsedValue); - } - } -} \ No newline at end of file +// +// 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; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs + internal sealed class GenericHeaderParser : BaseHeaderParser + { + private GetParsedValueLengthDelegate getParsedValueLength; + + internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) + : base(supportsMultipleValues) + { + if (getParsedValueLength == null) + { + throw new ArgumentNullException(nameof(getParsedValueLength)); + } + + this.getParsedValueLength = getParsedValueLength; + } + + internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); + + protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) + { + return this.getParsedValueLength(value, startIndex, out parsedValue); + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs index 02bd2a54762..84734368871 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs @@ -1,59 +1,59 @@ -// -// 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.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs - internal static class HeaderUtilities - { - internal static int GetNextNonEmptyOrWhitespaceIndex( - string input, - int startIndex, - bool skipEmptyValues, - out bool separatorFound) - { - Contract.Requires(input != null); - Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length. - - separatorFound = false; - var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex); - - if ((current == input.Length) || (input[current] != ',')) - { - return current; - } - - // If we have a separator, skip the separator and all following whitespaces. If we support - // empty values, continue until the current character is neither a separator nor a whitespace. - separatorFound = true; - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - if (skipEmptyValues) - { - while ((current < input.Length) && (input[current] == ',')) - { - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - } - } - - return current; - } - } -} \ No newline at end of file +// +// 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.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs + internal static class HeaderUtilities + { + internal static int GetNextNonEmptyOrWhitespaceIndex( + string input, + int startIndex, + bool skipEmptyValues, + out bool separatorFound) + { + Contract.Requires(input != null); + Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length. + + separatorFound = false; + var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex); + + if ((current == input.Length) || (input[current] != ',')) + { + return current; + } + + // If we have a separator, skip the separator and all following whitespaces. If we support + // empty values, continue until the current character is neither a separator nor a whitespace. + separatorFound = true; + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + if (skipEmptyValues) + { + while ((current < input.Length) && (input[current] == ',')) + { + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + } + } + + return current; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs index 435eee12c07..6c2568be6ee 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs @@ -1,39 +1,40 @@ -// -// 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 Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs - internal abstract class HttpHeaderParser - { - private bool supportsMultipleValues; - - protected HttpHeaderParser(bool supportsMultipleValues) - { - this.supportsMultipleValues = supportsMultipleValues; - } - - public bool SupportsMultipleValues - { - get { return this.supportsMultipleValues; } - } - - // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' - // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 - // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first - // non-whitespace after the separator ','. - public abstract bool TryParseValue(string value, ref int index, out T parsedValue); - } -} \ No newline at end of file +// +// 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 Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs + internal abstract class HttpHeaderParser + { + private bool supportsMultipleValues; + + protected HttpHeaderParser(bool supportsMultipleValues) + { + this.supportsMultipleValues = supportsMultipleValues; + } + + public bool SupportsMultipleValues + { + get { return this.supportsMultipleValues; } + } + + // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' + // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 + // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first + // non-whitespace after the separator ','. + public abstract bool TryParseValue(string value, ref int index, out T parsedValue); + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs index a0105e9b4cc..c8c82b93af5 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs @@ -1,37 +1,37 @@ -// -// 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 Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs - internal enum HttpParseResult - { - /// - /// Parsed successfully. - /// - Parsed, - - /// - /// Was not parsed. - /// - NotParsed, - - /// - /// Invalid format. - /// - InvalidFormat, - } -} \ No newline at end of file +// +// 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 Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs + internal enum HttpParseResult + { + /// + /// Parsed successfully. + /// + Parsed, + + /// + /// Was not parsed. + /// + NotParsed, + + /// + /// Invalid format. + /// + InvalidFormat, + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs index de5e3339e27..4959d6407e5 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs @@ -1,283 +1,283 @@ -// -// 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.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs - internal static class HttpRuleParser - { - internal const char CR = '\r'; - internal const char LF = '\n'; - internal const char SP = ' '; - internal const char Tab = '\t'; - internal const int MaxInt64Digits = 19; - internal const int MaxInt32Digits = 10; - - private const int MaxNestedCount = 5; - private static readonly bool[] TokenChars = CreateTokenChars(); - - internal static bool IsTokenChar(char character) - { - // Must be between 'space' (32) and 'DEL' (127) - if (character > 127) - { - return false; - } - - return TokenChars[character]; - } - - internal static int GetTokenLength(string input, int startIndex) - { - Contract.Requires(input != null); - Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); - - if (startIndex >= input.Length) - { - return 0; - } - - var current = startIndex; - - while (current < input.Length) - { - if (!IsTokenChar(input[current])) - { - return current - startIndex; - } - - current++; - } - - return input.Length - startIndex; - } - - internal static int GetWhitespaceLength(string input, int startIndex) - { - Contract.Requires(input != null); - Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); - - if (startIndex >= input.Length) - { - return 0; - } - - var current = startIndex; - - char c; - while (current < input.Length) - { - c = input[current]; - - if ((c == SP) || (c == Tab)) - { - current++; - continue; - } - - if (c == CR) - { - // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. - if ((current + 2 < input.Length) && (input[current + 1] == LF)) - { - char spaceOrTab = input[current + 2]; - if ((spaceOrTab == SP) || (spaceOrTab == Tab)) - { - current += 3; - continue; - } - } - } - - return current - startIndex; - } - - // All characters between startIndex and the end of the string are LWS characters. - return input.Length - startIndex; - } - - internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) - { - var nestedCount = 0; - return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); - } - - // quoted-pair = "\" CHAR - // CHAR = - internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) - { - Contract.Requires(input != null); - Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); - Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && - (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); - - length = 0; - - if (input[startIndex] != '\\') - { - return HttpParseResult.NotParsed; - } - - // Quoted-char has 2 characters. Check whether there are 2 chars left ('\' + char) - // If so, check whether the character is in the range 0-127. If not, it's an invalid value. - if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) - { - return HttpParseResult.InvalidFormat; - } - - // We don't care what the char next to '\' is. - length = 2; - return HttpParseResult.Parsed; - } - - private static bool[] CreateTokenChars() - { - // token = 1* - // CTL = - var tokenChars = new bool[128]; // everything is false - - for (int i = 33; i < 127; i++) - { - // skip Space (32) & DEL (127) - tokenChars[i] = true; - } - - // remove separators: these are not valid token characters - tokenChars[(byte)'('] = false; - tokenChars[(byte)')'] = false; - tokenChars[(byte)'<'] = false; - tokenChars[(byte)'>'] = false; - tokenChars[(byte)'@'] = false; - tokenChars[(byte)','] = false; - tokenChars[(byte)';'] = false; - tokenChars[(byte)':'] = false; - tokenChars[(byte)'\\'] = false; - tokenChars[(byte)'"'] = false; - tokenChars[(byte)'/'] = false; - tokenChars[(byte)'['] = false; - tokenChars[(byte)']'] = false; - tokenChars[(byte)'?'] = false; - tokenChars[(byte)'='] = false; - tokenChars[(byte)'{'] = false; - tokenChars[(byte)'}'] = false; - - return tokenChars; - } - - // TEXT = - // LWS = [CRLF] 1*( SP | HT ) - // CTL = - // - // Since we don't really care about the content of a quoted string or comment, we're more tolerant and - // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). - // - // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like - // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested - // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) - // is unusual. - private static HttpParseResult GetExpressionLength( - string input, - int startIndex, - char openChar, - char closeChar, - bool supportsNesting, - ref int nestedCount, - out int length) - { - Contract.Requires(input != null); - Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); - Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || - (Contract.ValueAtReturn(out length) > 0)); - - length = 0; - - if (input[startIndex] != openChar) - { - return HttpParseResult.NotParsed; - } - - var current = startIndex + 1; // Start parsing with the character next to the first open-char - while (current < input.Length) - { - // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. - // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. - var quotedPairLength = 0; - if ((current + 2 < input.Length) && - (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) - { - // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, - // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only - // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars). - current = current + quotedPairLength; - continue; - } - - // If we support nested expressions and we find an open-char, then parse the nested expressions. - if (supportsNesting && (input[current] == openChar)) - { - nestedCount++; - try - { - // Check if we exceeded the number of nested calls. - if (nestedCount > MaxNestedCount) - { - return HttpParseResult.InvalidFormat; - } - - var nestedLength = 0; - HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); - - switch (nestedResult) - { - case HttpParseResult.Parsed: - current += nestedLength; // add the length of the nested expression and continue. - break; - - case HttpParseResult.NotParsed: - Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression parsing, because we found the open-char. So either it's a valid nested expression or it has invalid format."); - break; - - case HttpParseResult.InvalidFormat: - // If the nested expression is invalid, we can't continue, so we fail with invalid format. - return HttpParseResult.InvalidFormat; - - default: - Contract.Assert(false, "Unknown enum result: " + nestedResult); - break; - } - } - finally - { - nestedCount--; - } - } - - if (input[current] == closeChar) - { - length = current - startIndex + 1; - return HttpParseResult.Parsed; - } - - current++; - } - - // We didn't see the final quote, therefore we have an invalid expression string. - return HttpParseResult.InvalidFormat; - } - } -} \ No newline at end of file +// +// 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.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs + internal static class HttpRuleParser + { + internal const char CR = '\r'; + internal const char LF = '\n'; + internal const char SP = ' '; + internal const char Tab = '\t'; + internal const int MaxInt64Digits = 19; + internal const int MaxInt32Digits = 10; + + private const int MaxNestedCount = 5; + private static readonly bool[] TokenChars = CreateTokenChars(); + + internal static bool IsTokenChar(char character) + { + // Must be between 'space' (32) and 'DEL' (127) + if (character > 127) + { + return false; + } + + return TokenChars[character]; + } + + internal static int GetTokenLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + while (current < input.Length) + { + if (!IsTokenChar(input[current])) + { + return current - startIndex; + } + + current++; + } + + return input.Length - startIndex; + } + + internal static int GetWhitespaceLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + char c; + while (current < input.Length) + { + c = input[current]; + + if ((c == SP) || (c == Tab)) + { + current++; + continue; + } + + if (c == CR) + { + // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. + if ((current + 2 < input.Length) && (input[current + 1] == LF)) + { + char spaceOrTab = input[current + 2]; + if ((spaceOrTab == SP) || (spaceOrTab == Tab)) + { + current += 3; + continue; + } + } + } + + return current - startIndex; + } + + // All characters between startIndex and the end of the string are LWS characters. + return input.Length - startIndex; + } + + internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) + { + var nestedCount = 0; + return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); + } + + // quoted-pair = "\" CHAR + // CHAR = + internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && + (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); + + length = 0; + + if (input[startIndex] != '\\') + { + return HttpParseResult.NotParsed; + } + + // Quoted-char has 2 characters. Check whether there are 2 chars left ('\' + char) + // If so, check whether the character is in the range 0-127. If not, it's an invalid value. + if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) + { + return HttpParseResult.InvalidFormat; + } + + // We don't care what the char next to '\' is. + length = 2; + return HttpParseResult.Parsed; + } + + private static bool[] CreateTokenChars() + { + // token = 1* + // CTL = + var tokenChars = new bool[128]; // everything is false + + for (int i = 33; i < 127; i++) + { + // skip Space (32) & DEL (127) + tokenChars[i] = true; + } + + // remove separators: these are not valid token characters + tokenChars[(byte)'('] = false; + tokenChars[(byte)')'] = false; + tokenChars[(byte)'<'] = false; + tokenChars[(byte)'>'] = false; + tokenChars[(byte)'@'] = false; + tokenChars[(byte)','] = false; + tokenChars[(byte)';'] = false; + tokenChars[(byte)':'] = false; + tokenChars[(byte)'\\'] = false; + tokenChars[(byte)'"'] = false; + tokenChars[(byte)'/'] = false; + tokenChars[(byte)'['] = false; + tokenChars[(byte)']'] = false; + tokenChars[(byte)'?'] = false; + tokenChars[(byte)'='] = false; + tokenChars[(byte)'{'] = false; + tokenChars[(byte)'}'] = false; + + return tokenChars; + } + + // TEXT = + // LWS = [CRLF] 1*( SP | HT ) + // CTL = + // + // Since we don't really care about the content of a quoted string or comment, we're more tolerant and + // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). + // + // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like + // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested + // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) + // is unusual. + private static HttpParseResult GetExpressionLength( + string input, + int startIndex, + char openChar, + char closeChar, + bool supportsNesting, + ref int nestedCount, + out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || + (Contract.ValueAtReturn(out length) > 0)); + + length = 0; + + if (input[startIndex] != openChar) + { + return HttpParseResult.NotParsed; + } + + var current = startIndex + 1; // Start parsing with the character next to the first open-char + while (current < input.Length) + { + // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. + // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. + var quotedPairLength = 0; + if ((current + 2 < input.Length) && + (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) + { + // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, + // but we actually have a quoted-string: e.g. '\' followed by a char >127 - quoted-pair only + // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars. + current = current + quotedPairLength; + continue; + } + + // If we support nested expressions and we find an open-char, then parse the nested expressions. + if (supportsNesting && (input[current] == openChar)) + { + nestedCount++; + try + { + // Check if we exceeded the number of nested calls. + if (nestedCount > MaxNestedCount) + { + return HttpParseResult.InvalidFormat; + } + + var nestedLength = 0; + HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); + + switch (nestedResult) + { + case HttpParseResult.Parsed: + current += nestedLength; // add the length of the nested expression and continue. + break; + + case HttpParseResult.NotParsed: + Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression parsing, because we found the open-char. So either it's a valid nested expression or it has invalid format."); + break; + + case HttpParseResult.InvalidFormat: + // If the nested expression is invalid, we can't continue, so we fail with invalid format. + return HttpParseResult.InvalidFormat; + + default: + Contract.Assert(false, "Unknown enum result: " + nestedResult); + break; + } + } + finally + { + nestedCount--; + } + } + + if (input[current] == closeChar) + { + length = current - startIndex + 1; + return HttpParseResult.Parsed; + } + + current++; + } + + // We didn't see the final quote, therefore we have an invalid expression string. + return HttpParseResult.InvalidFormat; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs index a0dd899445b..ad29e226ca5 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs @@ -1,130 +1,130 @@ -// -// 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.Contracts; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs - - // According to the RFC, in places where a "parameter" is required, the value is mandatory - // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports - // name-only values in addition to name/value pairs. - internal class NameValueHeaderValue - { - private static readonly HttpHeaderParser SingleValueParser - = new GenericHeaderParser(false, GetNameValueLength); - - private string name; - private string value; - - private NameValueHeaderValue() - { - // Used by the parser to create a new instance of this type. - } - - public string Name - { - get { return this.name; } - } - - public string Value - { - get { return this.value; } - } - - public static bool TryParse(string input, out NameValueHeaderValue parsedValue) - { - var index = 0; - return SingleValueParser.TryParseValue(input, ref index, out parsedValue); - } - - internal static int GetValueLength(string input, int startIndex) - { - Contract.Requires(input != null); - - if (startIndex >= input.Length) - { - return 0; - } - - var valueLength = HttpRuleParser.GetTokenLength(input, startIndex); - - if (valueLength == 0) - { - // A value can either be a token or a quoted string. Check if it is a quoted string. - if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed) - { - // We have an invalid value. Reset the name and return. - return 0; - } - } - - return valueLength; - } - - private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue) - { - Contract.Requires(input != null); - Contract.Requires(startIndex >= 0); - - parsedValue = null; - - if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) - { - return 0; - } - - // Parse the name, i.e. in name/value string "=". Caller must remove - // leading whitespaces. - var nameLength = HttpRuleParser.GetTokenLength(input, startIndex); - - if (nameLength == 0) - { - return 0; - } - - var name = input.Substring(startIndex, nameLength); - var current = startIndex + nameLength; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - // Parse the separator between name and value - if ((current == input.Length) || (input[current] != '=')) - { - // We only have a name and that's OK. Return. - parsedValue = new NameValueHeaderValue(); - parsedValue.name = name; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces - return current - startIndex; - } - - current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); - - // Parse the value, i.e. in name/value string "=" - int valueLength = GetValueLength(input, current); - - // Value after the '=' may be empty - // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. - parsedValue = new NameValueHeaderValue(); - parsedValue.name = name; - parsedValue.value = input.Substring(current, valueLength); - current = current + valueLength; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces - return current - startIndex; - } - } -} \ No newline at end of file +// +// 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.Contracts; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs + + // According to the RFC, in places where a "parameter" is required, the value is mandatory + // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports + // name-only values in addition to name/value pairs. + internal class NameValueHeaderValue + { + private static readonly HttpHeaderParser SingleValueParser + = new GenericHeaderParser(false, GetNameValueLength); + + private string name; + private string value; + + private NameValueHeaderValue() + { + // Used by the parser to create a new instance of this type. + } + + public string Name + { + get { return this.name; } + } + + public string Value + { + get { return this.value; } + } + + public static bool TryParse(string input, out NameValueHeaderValue parsedValue) + { + var index = 0; + return SingleValueParser.TryParseValue(input, ref index, out parsedValue); + } + + internal static int GetValueLength(string input, int startIndex) + { + Contract.Requires(input != null); + + if (startIndex >= input.Length) + { + return 0; + } + + var valueLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (valueLength == 0) + { + // A value can either be a token or a quoted string. Check if it is a quoted string. + if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed) + { + // We have an invalid value. Reset the name and return. + return 0; + } + } + + return valueLength; + } + + private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue) + { + Contract.Requires(input != null); + Contract.Requires(startIndex >= 0); + + parsedValue = null; + + if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) + { + return 0; + } + + // Parse the name, i.e. in name/value string "=". Caller must remove + // leading whitespaces. + var nameLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (nameLength == 0) + { + return 0; + } + + var name = input.Substring(startIndex, nameLength); + var current = startIndex + nameLength; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the separator between name and value + if ((current == input.Length) || (input[current] != '=')) + { + // We only have a name and that's OK. Return. + parsedValue = new NameValueHeaderValue(); + parsedValue.name = name; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the value, i.e. in name/value string "=" + int valueLength = GetValueLength(input, current); + + // Value after the '=' may be empty + // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. + parsedValue = new NameValueHeaderValue(); + parsedValue.name = name; + parsedValue.value = input.Substring(current, valueLength); + current = current + valueLength; + current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + } +} diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj index ed41c1df670..4c8fc26a8be 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj @@ -1,21 +1,21 @@ - - - net461 - A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. - - $(NoWarn),1591,CS0618 - core- - - - - - - - - - - - - \ No newline at end of file + + + net461 + A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. + + $(NoWarn),1591,CS0618 + core- + + + + + + + + + + + + diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/README.md b/src/Microsoft.AspNet.TelemetryCorrelation/README.md index 6d5f844c0cf..3ee8460a596 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/README.md +++ b/src/Microsoft.AspNet.TelemetryCorrelation/README.md @@ -7,9 +7,8 @@ Telemetry correlation http module enables cross tier telemetry tracking. ## Usage 1. Install NuGet for your app. -2. Enable diagnostics source listener using code below. Note, some - telemetry vendors like Azure Application Insights will enable it - automatically. +2. Enable diagnostics source listener using code below. Note, some telemetry + vendors like Azure Application Insights will enable it automatically. ``` csharp public class NoopDiagnosticsListener : IObserver> @@ -31,29 +30,31 @@ Telemetry correlation http module enables cross tier telemetry tracking. public void OnNext(DiagnosticListener listener) { - if (listener.Name == "Microsoft.AspNet.TelemetryCorrelation" || listener.Name == "System.Net.Http" ) + if (listener.Name == "Microsoft.AspNet.TelemetryCorrelation" || + listener.Name == "System.Net.Http" ) { listener.Subscribe(new NoopDiagnosticsListener()); } } } ``` -3. Double check that http module was registered in `web.config` for your - app. + +3. Double check that http module was registered in `web.config` for your app. Once enabled - this http module will: - Reads correlation http headers - Start/Stops Activity for the http request -- Ensure the Activity ambient state is transferred thru the IIS - callbacks +- Ensure the Activity ambient state is transferred thru the IIS callbacks -See http protocol [specifications][http-protocol-specification] for -details. +See http protocol [specifications][http-protocol-specification] for details. This http module is used by Application Insights. See [documentation][usage-in-ai-docs] and [code][usage-in-ai-code]. -[http-protocol-specification]: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md -[usage-in-ai-docs]: https://docs.microsoft.com/azure/application-insights/application-insights-correlation -[usage-in-ai-code]: https://github.com/Microsoft/ApplicationInsights-dotnet-server +[http-protocol-specification]: +https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md +[usage-in-ai-docs]: +https://docs.microsoft.com/azure/application-insights/application-insights-correlation +[usage-in-ai-code]: +https://github.com/Microsoft/ApplicationInsights-dotnet-server diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs index 86f985184d0..6427dbd3ea7 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs @@ -1,172 +1,172 @@ -// -// 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.ComponentModel; -using System.Diagnostics; -using System.Reflection; -using System.Web; - -namespace Microsoft.AspNet.TelemetryCorrelation -{ - /// - /// Http Module sets ambient state using Activity API from DiagnosticsSource package. - /// - public class TelemetryCorrelationHttpModule : IHttpModule - { - private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled"; - - // 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 MethodInfo onStepMethodInfo = null; - - static TelemetryCorrelationHttpModule() - { - onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); - } - - /// - /// Gets or sets a value indicating whether TelemetryCorrelationHttpModule should parse headers to get correlation ids. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool ParseHeaders { get; set; } = true; - - /// - public void Dispose() - { - } - - /// - public void Init(HttpApplication context) - { - context.BeginRequest += this.Application_BeginRequest; - context.EndRequest += this.Application_EndRequest; - context.Error += this.Application_Error; - - // OnExecuteRequestStep is availabile starting with 4.7.1 - // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), - // we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute - if (onStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) - { - try - { - onStepMethodInfo.Invoke(context, new object[] { (Action)this.OnExecuteRequestStep }); - } - catch (Exception e) - { - AspNetTelemetryCorrelationEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); - } - } - else - { - context.PreRequestHandlerExecute += this.Application_PreRequestHandlerExecute; - } - } - - /// - /// Restores Activity before each pipeline step if it was lost. - /// - /// HttpContext instance. - /// Step to be executed. - internal void OnExecuteRequestStep(HttpContextBase context, Action step) - { - // Once we have public Activity.Current setter (https://github.com/dotnet/corefx/issues/29207) this method will be - // simplified to just assign Current if is was lost. - // In the mean time, we are creating child Activity to restore the context. We have to send - // event with this Activity to tracing system. It created a lot of issues for listeners as - // we may potentially have a lot of them for different stages. - // To reduce amount of events, we only care about ExecuteRequestHandler stage - restore activity here and - // stop/report it to tracing system in EndRequest. - if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) - { - ActivityHelper.RestoreActivityIfNeeded(context.Items); - } - - step(); - } - - private void Application_BeginRequest(object sender, EventArgs e) - { - var context = ((HttpApplication)sender).Context; - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); - ActivityHelper.CreateRootActivity(context, this.ParseHeaders); - context.Items[BeginCalledFlag] = true; - } - - private void Application_PreRequestHandlerExecute(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); - ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items); - } - - private void Application_EndRequest(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_EndRequest"); - bool trackActivity = true; - - var context = ((HttpApplication)sender).Context; - - // EndRequest does it's best effort to notify that request has ended - // BeginRequest has never been called - if (!context.Items.Contains(BeginCalledFlag)) - { - // 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 pipleline 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 - ActivityHelper.CreateRootActivity(context, this.ParseHeaders); - } - } - - if (trackActivity) - { - ActivityHelper.StopAspNetActivity(context.Items); - } - } - - private void Application_Error(object sender, EventArgs e) - { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_Error"); - - var context = ((HttpApplication)sender).Context; - - var exception = context.Error; - if (exception != null) - { - if (!context.Items.Contains(BeginCalledFlag)) - { - ActivityHelper.CreateRootActivity(context, this.ParseHeaders); - } - - ActivityHelper.WriteActivityException(context.Items, exception); - } - } - } -} +// +// 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.ComponentModel; +using System.Diagnostics; +using System.Reflection; +using System.Web; + +namespace Microsoft.AspNet.TelemetryCorrelation +{ + /// + /// Http Module sets ambient state using Activity API from DiagnosticsSource package. + /// + public class TelemetryCorrelationHttpModule : IHttpModule + { + private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled"; + + // 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 MethodInfo onStepMethodInfo = null; + + static TelemetryCorrelationHttpModule() + { + onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); + } + + /// + /// Gets or sets a value indicating whether TelemetryCorrelationHttpModule should parse headers to get correlation ids. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ParseHeaders { get; set; } = true; + + /// + public void Dispose() + { + } + + /// + public void Init(HttpApplication context) + { + context.BeginRequest += this.Application_BeginRequest; + context.EndRequest += this.Application_EndRequest; + context.Error += this.Application_Error; + + // OnExecuteRequestStep is availabile starting with 4.7.1 + // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), + // we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute + if (onStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) + { + try + { + onStepMethodInfo.Invoke(context, new object[] { (Action)this.OnExecuteRequestStep }); + } + catch (Exception e) + { + AspNetTelemetryCorrelationEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); + } + } + else + { + context.PreRequestHandlerExecute += this.Application_PreRequestHandlerExecute; + } + } + + /// + /// Restores Activity before each pipeline step if it was lost. + /// + /// HttpContext instance. + /// Step to be executed. + internal void OnExecuteRequestStep(HttpContextBase context, Action step) + { + // Once we have public Activity.Current setter (https://github.com/dotnet/corefx/issues/29207) this method will be + // simplified to just assign Current if is was lost. + // In the mean time, we are creating child Activity to restore the context. We have to send + // event with this Activity to tracing system. It created a lot of issues for listeners as + // we may potentially have a lot of them for different stages. + // To reduce amount of events, we only care about ExecuteRequestHandler stage - restore activity here and + // stop/report it to tracing system in EndRequest. + if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification) + { + ActivityHelper.RestoreActivityIfNeeded(context.Items); + } + + step(); + } + + private void Application_BeginRequest(object sender, EventArgs e) + { + var context = ((HttpApplication)sender).Context; + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); + ActivityHelper.CreateRootActivity(context, this.ParseHeaders); + context.Items[BeginCalledFlag] = true; + } + + private void Application_PreRequestHandlerExecute(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); + ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items); + } + + private void Application_EndRequest(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_EndRequest"); + bool trackActivity = true; + + var context = ((HttpApplication)sender).Context; + + // EndRequest does it's best effort to notify that request has ended + // BeginRequest has never been called + if (!context.Items.Contains(BeginCalledFlag)) + { + // 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 pipleline 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 + ActivityHelper.CreateRootActivity(context, this.ParseHeaders); + } + } + + if (trackActivity) + { + ActivityHelper.StopAspNetActivity(context.Items); + } + } + + private void Application_Error(object sender, EventArgs e) + { + AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_Error"); + + var context = ((HttpApplication)sender).Context; + + var exception = context.Error; + if (exception != null) + { + if (!context.Items.Contains(BeginCalledFlag)) + { + ActivityHelper.CreateRootActivity(context, this.ParseHeaders); + } + + ActivityHelper.WriteActivityException(context.Items, exception); + } + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs index 9f51e8dc590..20cb0a1f744 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs @@ -1,322 +1,322 @@ -// -// 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 Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Linq; - using Xunit; - - public class ActivityExtensionsTest - { - private const string TestActivityName = "Activity.Test"; - - [Fact] - public void Restore_Nothing_If_Header_Does_Not_Contain_RequestId() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection(); - - Assert.False(activity.Extract(requestHeaders)); - - Assert.True(string.IsNullOrEmpty(activity.ParentId)); - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Restore_First_RequestId_When_Multiple_RequestId_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Extract_RequestId_Is_Ignored_When_Traceparent_Is_Present() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.False(activity.Recorded); - - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Extract_First_Traceparent_When_Multiple_Traceparents_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.False(activity.Recorded); - - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Extract_RootActivity_From_W3C_Headers_And_CC() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - }; - - Assert.True(activity.Extract(requestHeaders)); - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.True(activity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789"), - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Extract_Empty_Traceparent() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, string.Empty }, - }; - - Assert.False(activity.Extract(requestHeaders)); - - Assert.Equal(default, activity.ParentSpanId); - Assert.Null(activity.ParentId); - } - - [Fact] - public void Can_Extract_Multi_Line_Tracestate() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1" }, - { ActivityExtensions.TracestateHeaderName, "ts2=v2" }, - }; - - Assert.True(activity.Extract(requestHeaders)); - activity.Start(); - Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); - Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); - Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); - Assert.True(activity.Recorded); - - Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); - } - - [Fact] - public void Restore_Empty_RequestId_Should_Not_Throw_Exception() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, string.Empty }, - }; - Assert.False(activity.Extract(requestHeaders)); - - Assert.Null(activity.ParentId); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Restore_Empty_Traceparent_Should_Not_Throw_Exception() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.TraceparentHeaderName, string.Empty }, - }; - Assert.False(activity.Extract(requestHeaders)); - - Assert.Null(activity.ParentId); - Assert.Null(activity.TraceStateString); - Assert.Empty(activity.Baggage); - } - - [Fact] - public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789"), - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - { ActivityExtensions.CorrelationContextHeaderName, "key4=abc,key5=def" }, - { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789"), - new KeyValuePair("key4", "abc"), - new KeyValuePair("key5", "def"), - new KeyValuePair("key6", "xyz"), - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Headers() - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, - { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, - { ActivityExtensions.CorrelationContextHeaderName, "key4=abc;key5=def" }, - { ActivityExtensions.CorrelationContextHeaderName, "key6????xyz" }, - { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" }, - }; - Assert.True(activity.Extract(requestHeaders)); - - Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); - var baggageItems = new List> - { - new KeyValuePair("key1", "123"), - new KeyValuePair("key2", "456"), - new KeyValuePair("key3", "789"), - }; - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Theory] - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx", 1023)] // 1023 chars - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx1", 1024)] // 1024 chars - [InlineData( - "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + - "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + - "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + - "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + - "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + - "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + - "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + - "key70=value70,key71=value71,key72=value72,key73=value73,key74=value74", 1029)] // more than 1024 chars - public void Validates_Correlation_Context_Length(string correlationContext, int expectedLength) - { - var activity = new Activity(TestActivityName); - var requestHeaders = new NameValueCollection - { - { ActivityExtensions.RequestIdHeaderName, "|abc.1" }, - { ActivityExtensions.CorrelationContextHeaderName, correlationContext }, - }; - Assert.True(activity.Extract(requestHeaders)); - - var baggageItems = Enumerable.Range(0, 74).Select(i => new KeyValuePair("key" + i, "value" + i)).ToList(); - if (expectedLength < 1024) - { - baggageItems.Add(new KeyValuePair("k100", "vx")); - } - - var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); - var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); - Assert.Equal(expectedBaggage, actualBaggage); - } - } -} +// +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Linq; + using Xunit; + + public class ActivityExtensionsTest + { + private const string TestActivityName = "Activity.Test"; + + [Fact] + public void Restore_Nothing_If_Header_Does_Not_Contain_RequestId() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection(); + + Assert.False(activity.Extract(requestHeaders)); + + Assert.True(string.IsNullOrEmpty(activity.ParentId)); + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Restore_First_RequestId_When_Multiple_RequestId_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b22222.1" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Extract_RequestId_Is_Ignored_When_Traceparent_Is_Present() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.False(activity.Recorded); + + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Extract_First_Traceparent_When_Multiple_Traceparents_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + { ActivityExtensions.TraceparentHeaderName, "00-fedcba09876543210fedcba09876543210-fedcba09876543210-01" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00", activity.ParentId); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.False(activity.Recorded); + + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Extract_RootActivity_From_W3C_Headers_And_CC() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + }; + + Assert.True(activity.Extract(requestHeaders)); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(activity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Extract_Empty_Traceparent() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, string.Empty }, + }; + + Assert.False(activity.Extract(requestHeaders)); + + Assert.Equal(default, activity.ParentSpanId); + Assert.Null(activity.ParentId); + } + + [Fact] + public void Can_Extract_Multi_Line_Tracestate() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1" }, + { ActivityExtensions.TracestateHeaderName, "ts2=v2" }, + }; + + Assert.True(activity.Extract(requestHeaders)); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(activity.Recorded); + + Assert.Equal("ts1=v1,ts2=v2", activity.TraceStateString); + } + + [Fact] + public void Restore_Empty_RequestId_Should_Not_Throw_Exception() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, string.Empty }, + }; + Assert.False(activity.Extract(requestHeaders)); + + Assert.Null(activity.ParentId); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Restore_Empty_Traceparent_Should_Not_Throw_Exception() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.TraceparentHeaderName, string.Empty }, + }; + Assert.False(activity.Extract(requestHeaders)); + + Assert.Null(activity.ParentId); + Assert.Null(activity.TraceStateString); + Assert.Empty(activity.Baggage); + } + + [Fact] + public void Can_Restore_Baggages_When_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Restore_Baggages_When_Multiple_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + { ActivityExtensions.CorrelationContextHeaderName, "key4=abc,key5=def" }, + { ActivityExtensions.CorrelationContextHeaderName, "key6=xyz" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + new KeyValuePair("key4", "abc"), + new KeyValuePair("key5", "def"), + new KeyValuePair("key6", "xyz"), + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Restore_Baggages_When_Some_MalFormat_CorrelationContext_In_Headers() + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b11111.1" }, + { ActivityExtensions.CorrelationContextHeaderName, "key1=123,key2=456,key3=789" }, + { ActivityExtensions.CorrelationContextHeaderName, "key4=abc;key5=def" }, + { ActivityExtensions.CorrelationContextHeaderName, "key6????xyz" }, + { ActivityExtensions.CorrelationContextHeaderName, "key7=123=456" }, + }; + Assert.True(activity.Extract(requestHeaders)); + + Assert.Equal("|aba2f1e978b11111.1", activity.ParentId); + var baggageItems = new List> + { + new KeyValuePair("key1", "123"), + new KeyValuePair("key2", "456"), + new KeyValuePair("key3", "789"), + }; + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Theory] + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx", 1023)] // 1023 chars + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,k100=vx1", 1024)] // 1024 chars + [InlineData( + "key0=value0,key1=value1,key2=value2,key3=value3,key4=value4,key5=value5,key6=value6,key7=value7,key8=value8,key9=value9," + + "key10=value10,key11=value11,key12=value12,key13=value13,key14=value14,key15=value15,key16=value16,key17=value17,key18=value18,key19=value19," + + "key20=value20,key21=value21,key22=value22,key23=value23,key24=value24,key25=value25,key26=value26,key27=value27,key28=value28,key29=value29," + + "key30=value30,key31=value31,key32=value32,key33=value33,key34=value34,key35=value35,key36=value36,key37=value37,key38=value38,key39=value39," + + "key40=value40,key41=value41,key42=value42,key43=value43,key44=value44,key45=value45,key46=value46,key47=value47,key48=value48,key49=value49," + + "key50=value50,key51=value51,key52=value52,key53=value53,key54=value54,key55=value55,key56=value56,key57=value57,key58=value58,key59=value59," + + "key60=value60,key61=value61,key62=value62,key63=value63,key64=value64,key65=value65,key66=value66,key67=value67,key68=value68,key69=value69," + + "key70=value70,key71=value71,key72=value72,key73=value73,key74=value74", 1029)] // more than 1024 chars + public void Validates_Correlation_Context_Length(string correlationContext, int expectedLength) + { + var activity = new Activity(TestActivityName); + var requestHeaders = new NameValueCollection + { + { ActivityExtensions.RequestIdHeaderName, "|abc.1" }, + { ActivityExtensions.CorrelationContextHeaderName, correlationContext }, + }; + Assert.True(activity.Extract(requestHeaders)); + + var baggageItems = Enumerable.Range(0, 74).Select(i => new KeyValuePair("key" + i, "value" + i)).ToList(); + if (expectedLength < 1024) + { + baggageItems.Add(new KeyValuePair("k100", "vx")); + } + + var expectedBaggage = baggageItems.OrderBy(kvp => kvp.Key); + var actualBaggage = activity.Baggage.OrderBy(kvp => kvp.Key); + Assert.Equal(expectedBaggage, actualBaggage); + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs index 40e86a9e8d7..cf903b34f32 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs @@ -1,569 +1,569 @@ -// -// 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 Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using System.Threading; - using System.Threading.Tasks; - using System.Web; - using Xunit; - - public class ActivityHelperTest : IDisposable - { - private const string TestActivityName = "Activity.Test"; - private readonly List> baggageItems; - private readonly string baggageInHeader; - private IDisposable subscriptionAllListeners; - private IDisposable subscriptionAspNetListener; - - public ActivityHelperTest() - { - this.baggageItems = new List> - { - new KeyValuePair("TestKey1", "123"), - new KeyValuePair("TestKey2", "456"), - new KeyValuePair("TestKey1", "789"), - }; - - this.baggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; - - // reset static fields - var allListenerField = typeof(DiagnosticListener). - GetField("s_allListenerObservable", BindingFlags.Static | BindingFlags.NonPublic); - allListenerField.SetValue(null, null); - var aspnetListenerField = typeof(ActivityHelper). - GetField("AspNetListener", BindingFlags.Static | BindingFlags.NonPublic); - aspnetListenerField.SetValue(null, new DiagnosticListener(ActivityHelper.AspNetListenerName)); - } - - public void Dispose() - { - this.subscriptionAspNetListener?.Dispose(); - this.subscriptionAllListeners?.Dispose(); - } - - [Fact] - public void Can_Restore_Activity() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.RestoreActivityIfNeeded(context.Items); - - Assert.Same(Activity.Current, rootActivity); - } - - [Fact] - public void Can_Stop_Lost_Activity() - { - this.EnableAll(pair => - { - Assert.NotNull(Activity.Current); - Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); - }); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.StopAspNetActivity(context.Items); - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Not_Stop_Lost_Activity_If_Not_In_Context() - { - this.EnableAll(pair => - { - Assert.NotNull(Activity.Current); - Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); - }); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - context.Items.Remove(ActivityHelper.ActivityKey); - rootActivity.AddTag("k1", "v1"); - rootActivity.AddTag("k2", "v2"); - - Activity.Current = null; - - ActivityHelper.StopAspNetActivity(context.Items); - Assert.True(rootActivity.Duration == TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() - { - this.EnableAll(); - ActivityHelper.RestoreActivityIfNeeded(HttpContextHelper.GetFakeHttpContext().Items); - - Assert.Null(Activity.Current); - } - - [Fact] - public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() - { - this.EnableAll(); - var root = new Activity("root").Start(); - - var context = HttpContextHelper.GetFakeHttpContext(); - context.Items[ActivityHelper.ActivityKey] = root; - - var module = new TelemetryCorrelationHttpModule(); - - ActivityHelper.RestoreActivityIfNeeded(context.Items); - - Assert.Equal(root, Activity.Current); - } - - [Fact] - public void Can_Stop_Activity_Without_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = this.CreateActivity(); - rootActivity.Start(); - context.Items[ActivityHelper.ActivityKey] = rootActivity; - Thread.Sleep(100); - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Activity_With_AspNetListener_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = this.CreateActivity(); - rootActivity.Start(); - context.Items[ActivityHelper.ActivityKey] = rootActivity; - Thread.Sleep(100); - this.EnableAspNetListenerOnly(); - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Root_Activity_With_All_Children() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - - var child = new Activity("child").Start(); - new Activity("grandchild").Start(); - - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(rootActivity.Duration != TimeSpan.Zero); - Assert.True(child.Duration == TimeSpan.Zero); - Assert.Null(rootActivity.Parent); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void Can_Stop_Root_While_Child_Is_Current() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - var child = new Activity("child").Start(); - - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(child.Duration == TimeSpan.Zero); - Assert.Null(Activity.Current); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - } - - [Fact] - public void OnImportActivity_Is_Called() - { - bool onImportIsCalled = false; - Activity importedActivity = null; - this.EnableAll(onImport: (activity, _) => - { - onImportIsCalled = true; - importedActivity = activity; - Assert.Null(Activity.Current); - }); - - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - Assert.True(onImportIsCalled); - Assert.NotNull(importedActivity); - Assert.Equal(importedActivity, Activity.Current); - Assert.Equal(importedActivity, rootActivity); - } - - [Fact] - public void OnImportActivity_Can_Set_Parent() - { - this.EnableAll(onImport: (activity, _) => - { - Assert.Null(activity.ParentId); - activity.SetParentId("|guid.123."); - }); - - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, false); - - Assert.Equal("|guid.123.", Activity.Current.ParentId); - } - - [Fact] - public async Task Can_Stop_Root_Activity_If_It_Is_Broken() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var root = ActivityHelper.CreateRootActivity(context, false); - 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(context.Items); - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - Assert.Null(Activity.Current); - } - - [Fact] - public void Stop_Root_Activity_With_129_Nesting_Depth() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - var root = ActivityHelper.CreateRootActivity(context, false); - - for (int i = 0; i < 129; i++) - { - new Activity("child" + i).Start(); - } - - // can stop any activity regardless of the stack depth - ActivityHelper.StopAspNetActivity(context.Items); - - Assert.True(root.Duration != TimeSpan.Zero); - Assert.Null(context.Items[ActivityHelper.ActivityKey]); - Assert.Null(Activity.Current); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() - { - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerOnly(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled_With_Arguments() - { - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndDisableActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.Null(rootActivity); - } - - [Fact] - public void Can_Create_RootActivity_And_Restore_Info_From_Request_Header() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, - { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.True(rootActivity.ParentId == "|aba2f1e978b2cab6.1."); - var expectedBaggage = this.baggageItems.OrderBy(item => item.Value); - var actualBaggage = rootActivity.Baggage.OrderBy(item => item.Value); - Assert.Equal(expectedBaggage, actualBaggage); - } - - [Fact] - public void Can_Create_RootActivity_From_W3C_Traceparent() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - 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.False(rootActivity.Recorded); - - Assert.Null(rootActivity.TraceStateString); - Assert.Empty(rootActivity.Baggage); - } - - [Fact] - public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, - { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - 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); - } - - [Fact] - public void Can_Create_RootActivity_And_Ignore_Info_From_Request_Header_If_ParseHeaders_Is_False() - { - this.EnableAll(); - var requestHeaders = new Dictionary - { - { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, - { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader }, - }; - - var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, parseHeaders: false); - - Assert.NotNull(rootActivity); - Assert.Null(rootActivity.ParentId); - Assert.Empty(rootActivity.Baggage); - } - - [Fact] - public void Can_Create_RootActivity_And_Start_Activity() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); - } - - [Fact] - public void Can_Create_RootActivity_And_Saved_In_HttContext() - { - this.EnableAll(); - var context = HttpContextHelper.GetFakeHttpContext(); - this.EnableAspNetListenerAndActivity(); - var rootActivity = ActivityHelper.CreateRootActivity(context, true); - - Assert.NotNull(rootActivity); - Assert.Same(rootActivity, context.Items[ActivityHelper.ActivityKey]); - } - - private Activity CreateActivity() - { - var activity = new Activity(TestActivityName); - this.baggageItems.ForEach(kv => activity.AddBaggage(kv.Key, kv.Value)); - - return activity; - } - - private void EnableAll(Action> onNext = null, Action onImport = null) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, a1, a2) => true, - (a, o) => onImport?.Invoke(a, o), - (a, o) => { }); - } - }); - } - - private void EnableAspNetListenerAndDisableActivity( - Action> onNext = null, - string activityName = ActivityHelper.AspNetActivityName) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, arg1, arg2) => name == activityName && arg1 == null); - } - }); - } - - private void EnableAspNetListenerAndActivity( - Action> onNext = null, - string activityName = ActivityHelper.AspNetActivityName) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - (name, arg1, arg2) => name == activityName); - } - }); - } - - private void EnableAspNetListenerOnly(Action> onNext = null) - { - this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => - { - // if AspNetListener has subscription, then it is enabled - if (listener.Name == ActivityHelper.AspNetListenerName) - { - this.subscriptionAspNetListener = listener.Subscribe( - new TestDiagnosticListener(onNext), - activityName => false); - } - }); - } - - private class TestHttpRequest : HttpRequestBase - { - private readonly NameValueCollection headers = new NameValueCollection(); - - 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; } - } - } -} +// +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + using Xunit; + + public class ActivityHelperTest : IDisposable + { + private const string TestActivityName = "Activity.Test"; + private readonly List> baggageItems; + private readonly string baggageInHeader; + private IDisposable subscriptionAllListeners; + private IDisposable subscriptionAspNetListener; + + public ActivityHelperTest() + { + this.baggageItems = new List> + { + new KeyValuePair("TestKey1", "123"), + new KeyValuePair("TestKey2", "456"), + new KeyValuePair("TestKey1", "789"), + }; + + this.baggageInHeader = "TestKey1=123,TestKey2=456,TestKey1=789"; + + // reset static fields + var allListenerField = typeof(DiagnosticListener). + GetField("s_allListenerObservable", BindingFlags.Static | BindingFlags.NonPublic); + allListenerField.SetValue(null, null); + var aspnetListenerField = typeof(ActivityHelper). + GetField("AspNetListener", BindingFlags.Static | BindingFlags.NonPublic); + aspnetListenerField.SetValue(null, new DiagnosticListener(ActivityHelper.AspNetListenerName)); + } + + public void Dispose() + { + this.subscriptionAspNetListener?.Dispose(); + this.subscriptionAllListeners?.Dispose(); + } + + [Fact] + public void Can_Restore_Activity() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.RestoreActivityIfNeeded(context.Items); + + Assert.Same(Activity.Current, rootActivity); + } + + [Fact] + public void Can_Stop_Lost_Activity() + { + this.EnableAll(pair => + { + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Not_Stop_Lost_Activity_If_Not_In_Context() + { + this.EnableAll(pair => + { + Assert.NotNull(Activity.Current); + Assert.Equal(ActivityHelper.AspNetActivityName, Activity.Current.OperationName); + }); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + context.Items.Remove(ActivityHelper.ActivityKey); + rootActivity.AddTag("k1", "v1"); + rootActivity.AddTag("k2", "v2"); + + Activity.Current = null; + + ActivityHelper.StopAspNetActivity(context.Items); + Assert.True(rootActivity.Duration == TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Do_Not_Restore_Activity_When_There_Is_No_Activity_In_Context() + { + this.EnableAll(); + ActivityHelper.RestoreActivityIfNeeded(HttpContextHelper.GetFakeHttpContext().Items); + + Assert.Null(Activity.Current); + } + + [Fact] + public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() + { + this.EnableAll(); + var root = new Activity("root").Start(); + + var context = HttpContextHelper.GetFakeHttpContext(); + context.Items[ActivityHelper.ActivityKey] = root; + + var module = new TelemetryCorrelationHttpModule(); + + ActivityHelper.RestoreActivityIfNeeded(context.Items); + + Assert.Equal(root, Activity.Current); + } + + [Fact] + public void Can_Stop_Activity_Without_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = this.CreateActivity(); + rootActivity.Start(); + context.Items[ActivityHelper.ActivityKey] = rootActivity; + Thread.Sleep(100); + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Activity_With_AspNetListener_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = this.CreateActivity(); + rootActivity.Start(); + context.Items[ActivityHelper.ActivityKey] = rootActivity; + Thread.Sleep(100); + this.EnableAspNetListenerOnly(); + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Root_Activity_With_All_Children() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + + var child = new Activity("child").Start(); + new Activity("grandchild").Start(); + + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(rootActivity.Duration != TimeSpan.Zero); + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(rootActivity.Parent); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void Can_Stop_Root_While_Child_Is_Current() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + var child = new Activity("child").Start(); + + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(child.Duration == TimeSpan.Zero); + Assert.Null(Activity.Current); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + } + + [Fact] + public void OnImportActivity_Is_Called() + { + bool onImportIsCalled = false; + Activity importedActivity = null; + this.EnableAll(onImport: (activity, _) => + { + onImportIsCalled = true; + importedActivity = activity; + Assert.Null(Activity.Current); + }); + + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + Assert.True(onImportIsCalled); + Assert.NotNull(importedActivity); + Assert.Equal(importedActivity, Activity.Current); + Assert.Equal(importedActivity, rootActivity); + } + + [Fact] + public void OnImportActivity_Can_Set_Parent() + { + this.EnableAll(onImport: (activity, _) => + { + Assert.Null(activity.ParentId); + activity.SetParentId("|guid.123."); + }); + + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, false); + + Assert.Equal("|guid.123.", Activity.Current.ParentId); + } + + [Fact] + public async Task Can_Stop_Root_Activity_If_It_Is_Broken() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var root = ActivityHelper.CreateRootActivity(context, false); + 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(context.Items); + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + Assert.Null(Activity.Current); + } + + [Fact] + public void Stop_Root_Activity_With_129_Nesting_Depth() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + var root = ActivityHelper.CreateRootActivity(context, false); + + for (int i = 0; i < 129; i++) + { + new Activity("child" + i).Start(); + } + + // can stop any activity regardless of the stack depth + ActivityHelper.StopAspNetActivity(context.Items); + + Assert.True(root.Duration != TimeSpan.Zero); + Assert.Null(context.Items[ActivityHelper.ActivityKey]); + Assert.Null(Activity.Current); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetListener_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerOnly(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Should_Not_Create_RootActivity_If_AspNetActivity_Not_Enabled_With_Arguments() + { + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndDisableActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.Null(rootActivity); + } + + [Fact] + public void Can_Create_RootActivity_And_Restore_Info_From_Request_Header() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.True(rootActivity.ParentId == "|aba2f1e978b2cab6.1."); + var expectedBaggage = this.baggageItems.OrderBy(item => item.Value); + var actualBaggage = rootActivity.Baggage.OrderBy(item => item.Value); + Assert.Equal(expectedBaggage, actualBaggage); + } + + [Fact] + public void Can_Create_RootActivity_From_W3C_Traceparent() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + 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.False(rootActivity.Recorded); + + Assert.Null(rootActivity.TraceStateString); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivityWithTraceState_From_W3C_TraceContext() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.TraceparentHeaderName, "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" }, + { ActivityExtensions.TracestateHeaderName, "ts1=v1,ts2=v2" }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + 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); + } + + [Fact] + public void Can_Create_RootActivity_And_Ignore_Info_From_Request_Header_If_ParseHeaders_Is_False() + { + this.EnableAll(); + var requestHeaders = new Dictionary + { + { ActivityExtensions.RequestIdHeaderName, "|aba2f1e978b2cab6.1." }, + { ActivityExtensions.CorrelationContextHeaderName, this.baggageInHeader }, + }; + + var context = HttpContextHelper.GetFakeHttpContext(headers: requestHeaders); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, parseHeaders: false); + + Assert.NotNull(rootActivity); + Assert.Null(rootActivity.ParentId); + Assert.Empty(rootActivity.Baggage); + } + + [Fact] + public void Can_Create_RootActivity_And_Start_Activity() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.True(!string.IsNullOrEmpty(rootActivity.Id)); + } + + [Fact] + public void Can_Create_RootActivity_And_Saved_In_HttContext() + { + this.EnableAll(); + var context = HttpContextHelper.GetFakeHttpContext(); + this.EnableAspNetListenerAndActivity(); + var rootActivity = ActivityHelper.CreateRootActivity(context, true); + + Assert.NotNull(rootActivity); + Assert.Same(rootActivity, context.Items[ActivityHelper.ActivityKey]); + } + + private Activity CreateActivity() + { + var activity = new Activity(TestActivityName); + this.baggageItems.ForEach(kv => activity.AddBaggage(kv.Key, kv.Value)); + + return activity; + } + + private void EnableAll(Action> onNext = null, Action onImport = null) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, a1, a2) => true, + (a, o) => onImport?.Invoke(a, o), + (a, o) => { }); + } + }); + } + + private void EnableAspNetListenerAndDisableActivity( + Action> onNext = null, + string activityName = ActivityHelper.AspNetActivityName) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, arg1, arg2) => name == activityName && arg1 == null); + } + }); + } + + private void EnableAspNetListenerAndActivity( + Action> onNext = null, + string activityName = ActivityHelper.AspNetActivityName) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + (name, arg1, arg2) => name == activityName); + } + }); + } + + private void EnableAspNetListenerOnly(Action> onNext = null) + { + this.subscriptionAllListeners = DiagnosticListener.AllListeners.Subscribe(listener => + { + // if AspNetListener has subscription, then it is enabled + if (listener.Name == ActivityHelper.AspNetListenerName) + { + this.subscriptionAspNetListener = listener.Subscribe( + new TestDiagnosticListener(onNext), + activityName => false); + } + }); + } + + private class TestHttpRequest : HttpRequestBase + { + private readonly NameValueCollection headers = new NameValueCollection(); + + 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; } + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs index eb6feab52a5..48e1fd70e31 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs @@ -1,103 +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. -// - -namespace Microsoft.AspNet.TelemetryCorrelation.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 = HttpWorkerRequest.GetKnownRequestHeaderName(index); - - if (this.headers.ContainsKey(name)) - { - return this.headers[name]; - } - - return base.GetKnownRequestHeader(index); - } - } - } -} +// +// 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 Microsoft.AspNet.TelemetryCorrelation.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 = HttpWorkerRequest.GetKnownRequestHeaderName(index); + + if (this.headers.ContainsKey(name)) + { + return this.headers[name]; + } + + return base.GetKnownRequestHeader(index); + } + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj index 93819ccf2b3..d8d179c4587 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj @@ -1,33 +1,33 @@ - - - Unit test project for ASP.NET HttpModule - net461 - false - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - - - - Resources\web.config.install.xdt - - - Resources\web.config.uninstall.xdt - - - \ No newline at end of file + + + Unit test project for ASP.NET HttpModule + net461 + false + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + Resources\web.config.install.xdt + + + Resources\web.config.uninstall.xdt + + + diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs index 059b1c456ee..ca6260088cf 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs @@ -1,28 +1,28 @@ -// -// 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 Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.Reflection; - - internal static class PropertyExtensions - { - public static object GetProperty(this object obj, string propertyName) - { - return obj.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(obj); - } - } -} \ No newline at end of file +// +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.Reflection; + + internal static class PropertyExtensions + { + public static object GetProperty(this object obj, string propertyName) + { + return obj.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(obj); + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs index bd380d3e5e3..e6787d30c85 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs @@ -1,44 +1,44 @@ -// -// 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 Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System; - using System.Collections.Generic; - - internal class TestDiagnosticListener : IObserver> - { - private readonly Action> onNextCallBack; - - public TestDiagnosticListener(Action> onNext) - { - this.onNextCallBack = onNext; - } - - public void OnCompleted() - { - } - - public void OnError(Exception error) - { - } - - public void OnNext(KeyValuePair value) - { - this.onNextCallBack?.Invoke(value); - } - } -} \ No newline at end of file +// +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System; + using System.Collections.Generic; + + internal class TestDiagnosticListener : IObserver> + { + private readonly Action> onNextCallBack; + + public TestDiagnosticListener(Action> onNext) + { + this.onNextCallBack = onNext; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(KeyValuePair value) + { + this.onNextCallBack?.Invoke(value); + } + } +} diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs index 208a5e20988..2f4e023b989 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs @@ -1,411 +1,411 @@ -// -// 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 Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.IO; - using System.Xml.Linq; - using Microsoft.Web.XmlTransform; - using Xunit; - - public class WebConfigTransformTest - { - private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; - private const string UninstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.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; - } - } -} \ No newline at end of file +// +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.IO; + using System.Xml.Linq; + using Microsoft.Web.XmlTransform; + using Xunit; + + public class WebConfigTransformTest + { + private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; + private const string UninstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.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/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs index 4d8b3545468..1b7cd8f6ae2 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs @@ -1,441 +1,441 @@ -// -// 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 Microsoft.AspNet.TelemetryCorrelation.Tests -{ - using System.IO; - using System.Xml.Linq; - using Microsoft.Web.XmlTransform; - using Xunit; - - public class WebConfigWithLocationTagTransformTest - { - private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.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; - } - } -} +// +// 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 Microsoft.AspNet.TelemetryCorrelation.Tests +{ + using System.IO; + using System.Xml.Linq; + using Microsoft.Web.XmlTransform; + using Xunit; + + public class WebConfigWithLocationTagTransformTest + { + private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.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; + } + } +} From 808812ceaabc5e1c980d38600fb1bfe11e43c24c Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 4 Aug 2021 13:58:12 -0700 Subject: [PATCH 33/45] format code (#2232) --- .../ActivityExtensions.cs | 2 +- src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs | 2 +- .../AspNetTelemetryCorrelationEventSource.cs | 2 +- src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs | 2 +- .../Internal/BaseHeaderParser.cs | 2 +- .../Internal/GenericHeaderParser.cs | 2 +- .../Internal/HeaderUtilities.cs | 2 +- .../Internal/HttpHeaderParser.cs | 2 +- .../Internal/HttpParseResult.cs | 2 +- .../Internal/HttpRuleParser.cs | 2 +- .../Internal/NameValueHeaderValue.cs | 4 ++-- .../TelemetryCorrelationHttpModule.cs | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs index 8492e7327df..1daa409120e 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs index 057891fe8ce..0bf0a3bd508 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs index f439add5081..79711d74833 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs index d5967973522..514e712d184 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs index 3e884dd095f..2aed3ec446a 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs index 136dc224c81..d966b9ea4be 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs index 84734368871..0967da7fb54 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs index 6c2568be6ee..9bdbd31d174 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs index c8c82b93af5..0a8cabfb498 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs index 4959d6407e5..68cdbbada8c 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs index ad29e226ca5..b42d019e324 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.TelemetryCorrelation { // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs - // According to the RFC, in places where a "parameter" is required, the value is mandatory + // According to the RFC, in places where a "parameter" is required, the value is mandatory // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports // name-only values in addition to name/value pairs. internal class NameValueHeaderValue diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs index 6427dbd3ea7..721c0072a0b 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs +++ b/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); From 7668b6cf5886d01506a4312a25a8facc2a428aee Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Wed, 4 Aug 2021 15:37:51 -0700 Subject: [PATCH 34/45] Format code (#2235) --- .../ActivityExtensionsTest.cs | 2 +- .../ActivityHelperTest.cs | 2 +- .../HttpContextHelper.cs | 2 +- .../PropertyExtensions.cs | 2 +- .../TestDiagnosticListener.cs | 2 +- .../WebConfigTransformTest.cs | 2 +- .../WebConfigWithLocationTagTransformTest.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs index 20cb0a1f744..70f00409a7f 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs index cf903b34f32..8e8d5f210ac 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs index 48e1fd70e31..135bdd57b07 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs index ca6260088cf..cfb7c8527ca 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs index e6787d30c85..206f92c745f 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs index 2f4e023b989..e6928f91b10 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs index 1b7cd8f6ae2..3c309cfd638 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs +++ b/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); From ec118d4fde4b33279d754a3ea9514c0b9d1246e3 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 6 Aug 2021 22:01:58 -0700 Subject: [PATCH 35/45] Rename Microsoft.AspNet.TelemetryCorrelation -> OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule. Added project to the solution + updated references & namespaces. (#2238) --- OpenTelemetry.sln | 14 ++++- build/Common.props | 1 - examples/AspNet/Web.config | 2 +- .../ActivityExtensions.cs | 2 +- .../ActivityHelper.cs | 2 +- .../AspNetTelemetryCorrelationEventSource.cs | 2 +- .../AssemblyInfo.cs | 12 +---- .../Internal/BaseHeaderParser.cs | 2 +- .../Internal/GenericHeaderParser.cs | 2 +- .../Internal/HeaderUtilities.cs | 2 +- .../Internal/HttpHeaderParser.cs | 2 +- .../Internal/HttpParseResult.cs | 2 +- .../Internal/HttpRuleParser.cs | 2 +- .../Internal/NameValueHeaderValue.cs | 2 +- ...ntation.AspNet.TelemetryHttpModule.csproj} | 1 + .../README.md | 0 .../TelemetryCorrelationHttpModule.cs | 2 +- .../web.config.install.xdt | 0 .../web.config.uninstall.xdt | 0 ...penTelemetry.Instrumentation.AspNet.csproj | 5 +- .../README.md | 54 ++++++++++--------- .../ActivityExtensionsTest.cs | 2 +- .../ActivityHelperTest.cs | 2 +- .../HttpContextHelper.cs | 2 +- ...n.AspNet.TelemetryHttpModule.Tests.csproj} | 6 +-- .../PropertyExtensions.cs | 2 +- .../TestDiagnosticListener.cs | 2 +- .../WebConfigTransformTest.cs | 2 +- .../WebConfigWithLocationTagTransformTest.cs | 2 +- 29 files changed, 67 insertions(+), 64 deletions(-) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/ActivityExtensions.cs (99%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/ActivityHelper.cs (99%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/AspNetTelemetryCorrelationEventSource.cs (98%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/AssemblyInfo.cs (57%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/Internal/BaseHeaderParser.cs (98%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/Internal/GenericHeaderParser.cs (97%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/Internal/HeaderUtilities.cs (97%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/Internal/HttpHeaderParser.cs (97%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/Internal/HttpParseResult.cs (96%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/Internal/HttpRuleParser.cs (99%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/Internal/NameValueHeaderValue.cs (99%) rename src/{Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj} (90%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/README.md (100%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/TelemetryCorrelationHttpModule.cs (99%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/web.config.install.xdt (100%) rename src/{Microsoft.AspNet.TelemetryCorrelation => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule}/web.config.uninstall.xdt (100%) rename test/{Microsoft.AspNet.TelemetryCorrelation.Tests => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests}/ActivityExtensionsTest.cs (99%) rename test/{Microsoft.AspNet.TelemetryCorrelation.Tests => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests}/ActivityHelperTest.cs (99%) rename test/{Microsoft.AspNet.TelemetryCorrelation.Tests => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests}/HttpContextHelper.cs (98%) rename test/{Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj} (72%) rename test/{Microsoft.AspNet.TelemetryCorrelation.Tests => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests}/PropertyExtensions.cs (94%) rename test/{Microsoft.AspNet.TelemetryCorrelation.Tests => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests}/TestDiagnosticListener.cs (96%) rename test/{Microsoft.AspNet.TelemetryCorrelation.Tests => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests}/WebConfigTransformTest.cs (99%) rename test/{Microsoft.AspNet.TelemetryCorrelation.Tests => OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests}/WebConfigWithLocationTagTransformTest.cs (99%) diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 9669f7b6c17..63bf9532f5a 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -207,7 +207,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "customizing-the-sdk", "docs EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started", "docs\metrics\getting-started\getting-started.csproj", "{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Exporter.Prometheus", "src\OpenTelemetry.Exporter.Prometheus\OpenTelemetry.Exporter.Prometheus.csproj", "{52158A12-E7EF-45A1-859F-06F9B17410CB}" +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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -411,6 +415,14 @@ Global {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/build/Common.props b/build/Common.props index 600f86dcbf6..014f6bcead3 100644 --- a/build/Common.props +++ b/build/Common.props @@ -28,7 +28,6 @@ [2.25.0,3.0) [2.1.1,6.0) [2.1.1,6.0) - [1.0.7,2.0) [3.3.1] [16.10.0] [2.1.0,) diff --git a/examples/AspNet/Web.config b/examples/AspNet/Web.config index 06568322cee..7d1202545e9 100644 --- a/examples/AspNet/Web.config +++ b/examples/AspNet/Web.config @@ -23,7 +23,7 @@ - + diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityExtensions.cs similarity index 99% rename from src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityExtensions.cs index 1daa409120e..5b5cc20c143 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityExtensions.cs @@ -19,7 +19,7 @@ using System.ComponentModel; using System.Diagnostics; -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { /// /// Extensions of Activity class. diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs similarity index 99% rename from src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs index 0bf0a3bd508..cca48f03c43 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs @@ -19,7 +19,7 @@ using System.Diagnostics; using System.Web; -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { /// /// Activity helper class. diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryCorrelationEventSource.cs similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryCorrelationEventSource.cs index 79711d74833..f6c72b403d6 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryCorrelationEventSource.cs @@ -18,7 +18,7 @@ using System.Diagnostics.Tracing; #pragma warning disable SA1600 // Elements must be documented -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { /// /// ETW EventSource tracing class. diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AssemblyInfo.cs similarity index 57% rename from src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AssemblyInfo.cs index 514e712d184..3d679575e65 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/AssemblyInfo.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AssemblyInfo.cs @@ -19,16 +19,8 @@ [assembly: ComVisible(false)] -[assembly: InternalsVisibleTo("Microsoft.AspNet.TelemetryCorrelation.Tests" + AssemblyInfo.PublicKey)] - #if SIGNED -internal static class AssemblyInfo -{ - public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; -} +[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] #else -internal static class AssemblyInfo -{ - public const string PublicKey = ""; -} +[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests")] #endif diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/BaseHeaderParser.cs similarity index 98% rename from src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/BaseHeaderParser.cs index 2aed3ec446a..072a8c3f50a 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/BaseHeaderParser.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/BaseHeaderParser.cs @@ -14,7 +14,7 @@ // limitations under the License. // -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs internal abstract class BaseHeaderParser : HttpHeaderParser diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/GenericHeaderParser.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/GenericHeaderParser.cs index d966b9ea4be..448837e5f0f 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/GenericHeaderParser.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/GenericHeaderParser.cs @@ -16,7 +16,7 @@ using System; -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs internal sealed class GenericHeaderParser : BaseHeaderParser diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HeaderUtilities.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HeaderUtilities.cs index 0967da7fb54..ab41f1c58cf 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HeaderUtilities.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HeaderUtilities.cs @@ -16,7 +16,7 @@ using System.Diagnostics.Contracts; -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs internal static class HeaderUtilities diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpHeaderParser.cs similarity index 97% rename from src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpHeaderParser.cs index 9bdbd31d174..05584edcd8d 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpHeaderParser.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpHeaderParser.cs @@ -14,7 +14,7 @@ // limitations under the License. // -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs internal abstract class HttpHeaderParser diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpParseResult.cs similarity index 96% rename from src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpParseResult.cs index 0a8cabfb498..288e1faa070 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpParseResult.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpParseResult.cs @@ -14,7 +14,7 @@ // limitations under the License. // -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs internal enum HttpParseResult diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpRuleParser.cs similarity index 99% rename from src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpRuleParser.cs index 68cdbbada8c..c99e4675b2c 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/HttpRuleParser.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpRuleParser.cs @@ -16,7 +16,7 @@ using System.Diagnostics.Contracts; -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs internal static class HttpRuleParser diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/NameValueHeaderValue.cs similarity index 99% rename from src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/NameValueHeaderValue.cs index b42d019e324..7f5c2363e28 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Internal/NameValueHeaderValue.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/NameValueHeaderValue.cs @@ -16,7 +16,7 @@ using System.Diagnostics.Contracts; -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj similarity index 90% rename from src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj index 4c8fc26a8be..576e5b3c782 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/Microsoft.AspNet.TelemetryCorrelation.csproj +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj @@ -2,6 +2,7 @@ net461 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/Microsoft.AspNet.TelemetryCorrelation/README.md b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/README.md similarity index 100% rename from src/Microsoft.AspNet.TelemetryCorrelation/README.md rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/README.md diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryCorrelationHttpModule.cs similarity index 99% rename from src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryCorrelationHttpModule.cs index 721c0072a0b..8cbee9bf7d9 100644 --- a/src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryCorrelationHttpModule.cs @@ -20,7 +20,7 @@ using System.Reflection; using System.Web; -namespace Microsoft.AspNet.TelemetryCorrelation +namespace OpenTelemetry.Instrumentation.AspNet { /// /// Http Module sets ambient state using Activity API from DiagnosticsSource package. diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/web.config.install.xdt b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.install.xdt similarity index 100% rename from src/Microsoft.AspNet.TelemetryCorrelation/web.config.install.xdt rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.install.xdt diff --git a/src/Microsoft.AspNet.TelemetryCorrelation/web.config.uninstall.xdt b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.uninstall.xdt similarity index 100% rename from src/Microsoft.AspNet.TelemetryCorrelation/web.config.uninstall.xdt rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.uninstall.xdt diff --git a/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj b/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj index 71a98d5cc51..46790741b95 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj +++ b/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj @@ -10,12 +10,9 @@ - - - - + diff --git a/src/OpenTelemetry.Instrumentation.AspNet/README.md b/src/OpenTelemetry.Instrumentation.AspNet/README.md index 56098a55f52..eb342b2d7d6 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/README.md +++ b/src/OpenTelemetry.Instrumentation.AspNet/README.md @@ -24,17 +24,18 @@ dotnet add package OpenTelemetry.Instrumentation.AspNet `OpenTelemetry.Instrumentation.AspNet` requires adding an additional HttpModule to your web server. This additional HttpModule is shipped as part of -[`Microsoft.AspNet.TelemetryCorrelation`](https://www.nuget.org/packages/Microsoft.AspNet.TelemetryCorrelation/) +[`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 - + ``` @@ -44,8 +45,8 @@ following shows changes required to your `Web.config` when using IIS web server. 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. +[`OpenTelemetry.Exporter.Jaeger`](../OpenTelemetry.Exporter.Jaeger/README.md) to +the application. ```csharp using OpenTelemetry; @@ -71,18 +72,19 @@ public class WebApiApplication : HttpApplication ## Advanced configuration This instrumentation can be configured to change the default behavior by using -`AspNetInstrumentationOptions`, which allows configuring `Filter` as explained below. +`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. +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. +The following code snippet shows how to use `Filter` to only allow GET requests. ```csharp this.tracerProvider = Sdk.CreateTracerProviderBuilder() @@ -103,13 +105,13 @@ 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` +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`. @@ -136,10 +138,10 @@ this.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 `HttpRequest` and `HttpResponse`. +[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`. ## References diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityExtensionsTest.cs similarity index 99% rename from test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs rename to test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityExtensionsTest.cs index 70f00409a7f..8bb6505ca48 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityExtensionsTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityExtensionsTest.cs @@ -14,7 +14,7 @@ // limitations under the License. // -namespace Microsoft.AspNet.TelemetryCorrelation.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests { using System.Collections.Generic; using System.Collections.Specialized; diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs similarity index 99% rename from test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs rename to test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs index 8e8d5f210ac..12dfc5d2a40 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/ActivityHelperTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs @@ -14,7 +14,7 @@ // limitations under the License. // -namespace Microsoft.AspNet.TelemetryCorrelation.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests { using System; using System.Collections; diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/HttpContextHelper.cs similarity index 98% rename from test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs rename to test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/HttpContextHelper.cs index 135bdd57b07..0c0e2827803 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/HttpContextHelper.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/HttpContextHelper.cs @@ -14,7 +14,7 @@ // limitations under the License. // -namespace Microsoft.AspNet.TelemetryCorrelation.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests { using System.Collections.Generic; using System.Globalization; diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj similarity index 72% rename from test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj rename to test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj index d8d179c4587..951aef42891 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/Microsoft.AspNet.TelemetryCorrelation.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj @@ -19,14 +19,14 @@ - + - + Resources\web.config.install.xdt - + Resources\web.config.uninstall.xdt diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/PropertyExtensions.cs similarity index 94% rename from test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs rename to test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/PropertyExtensions.cs index cfb7c8527ca..135a4a97236 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/PropertyExtensions.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/PropertyExtensions.cs @@ -14,7 +14,7 @@ // limitations under the License. // -namespace Microsoft.AspNet.TelemetryCorrelation.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests { using System.Reflection; diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/TestDiagnosticListener.cs similarity index 96% rename from test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs rename to test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/TestDiagnosticListener.cs index 206f92c745f..4b29e2ab9f2 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/TestDiagnosticListener.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/TestDiagnosticListener.cs @@ -14,7 +14,7 @@ // limitations under the License. // -namespace Microsoft.AspNet.TelemetryCorrelation.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests { using System; using System.Collections.Generic; diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs similarity index 99% rename from test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs rename to test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs index e6928f91b10..0e393a48735 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigTransformTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs @@ -14,7 +14,7 @@ // limitations under the License. // -namespace Microsoft.AspNet.TelemetryCorrelation.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests { using System.IO; using System.Xml.Linq; diff --git a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs similarity index 99% rename from test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs rename to test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs index 3c309cfd638..52ec038637a 100644 --- a/test/Microsoft.AspNet.TelemetryCorrelation.Tests/WebConfigWithLocationTagTransformTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs @@ -14,7 +14,7 @@ // limitations under the License. // -namespace Microsoft.AspNet.TelemetryCorrelation.Tests +namespace OpenTelemetry.Instrumentation.AspNet.Tests { using System.IO; using System.Xml.Linq; From 2a3043deece507e8cc5198eef80f79d9ad3081f6 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Mon, 9 Aug 2021 10:37:01 -0700 Subject: [PATCH 36/45] add changelog for OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule (#2242) --- .../CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md new file mode 100644 index 00000000000..7f0685a7419 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +## Unreleased + +* 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)) + +* 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)) From dc28d6b9a7b4e023073fe92b4c7035b33ea24a29 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 10 Aug 2021 09:25:51 -0700 Subject: [PATCH 37/45] AspNet.TelemetryHttpModule public api + test fixes + renames (#2240) --- examples/AspNet/Web.config | 2 +- .../.publicApi/net461/PublicAPI.Shipped.txt | 0 .../.publicApi/net461/PublicAPI.Unshipped.txt | 9 +++ .../ActivityExtensions.cs | 8 +- .../ActivityHelper.cs | 8 +- ...ource.cs => AspNetTelemetryEventSource.cs} | 10 +-- .../Internal/BaseHeaderParser.cs | 10 +-- .../Internal/GenericHeaderParser.cs | 9 +-- .../Internal/HeaderUtilities.cs | 4 +- .../Internal/HttpRuleParser.cs | 14 ++-- .../Internal/NameValueHeaderValue.cs | 24 +++--- ...entation.AspNet.TelemetryHttpModule.csproj | 7 +- .../README.md | 5 +- ...onHttpModule.cs => TelemetryHttpModule.cs} | 30 +++---- .../web.config.install.xdt | 16 ++-- .../web.config.uninstall.xdt | 10 +-- .../AspNetInstrumentation.cs | 2 +- .../README.md | 4 +- .../ActivityHelperTest.cs | 2 +- .../WebConfigTransformTest.cs | 78 +++++++++---------- .../WebConfigWithLocationTagTransformTest.cs | 50 ++++++------ 21 files changed, 148 insertions(+), 154 deletions(-) create mode 100644 src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net461/PublicAPI.Shipped.txt create mode 100644 src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net461/PublicAPI.Unshipped.txt rename src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/{AspNetTelemetryCorrelationEventSource.cs => AspNetTelemetryEventSource.cs} (87%) rename src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/{TelemetryCorrelationHttpModule.cs => TelemetryHttpModule.cs} (84%) diff --git a/examples/AspNet/Web.config b/examples/AspNet/Web.config index 7d1202545e9..fcf320ae33e 100644 --- a/examples/AspNet/Web.config +++ b/examples/AspNet/Web.config @@ -23,7 +23,7 @@ - + diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net461/PublicAPI.Shipped.txt b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net461/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net461/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net461/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..7e00f288d20 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/net461/PublicAPI.Unshipped.txt @@ -0,0 +1,9 @@ +OpenTelemetry.Instrumentation.AspNet.ActivityExtensions +OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule +OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Dispose() -> void +OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Init(System.Web.HttpApplication context) -> void +OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.ParseHeaders.get -> bool +OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.ParseHeaders.set -> void +OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.TelemetryHttpModule() -> void +static OpenTelemetry.Instrumentation.AspNet.ActivityExtensions.Extract(this System.Diagnostics.Activity activity, System.Collections.Specialized.NameValueCollection requestHeaders) -> bool +static OpenTelemetry.Instrumentation.AspNet.ActivityExtensions.TryParse(this System.Diagnostics.Activity activity, System.Collections.Specialized.NameValueCollection requestHeaders) -> bool \ No newline at end of file diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityExtensions.cs index 5b5cc20c143..e5a542c76fb 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityExtensions.cs @@ -62,19 +62,19 @@ public static bool Extract(this Activity activity, NameValueCollection requestHe { if (activity == null) { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("activity is null"); + AspNetTelemetryEventSource.Log.ActvityExtractionError("activity is null"); return false; } if (activity.ParentId != null) { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); + AspNetTelemetryEventSource.Log.ActvityExtractionError("ParentId is already set on activity"); return false; } if (activity.Id != null) { - AspNetTelemetryCorrelationEventSource.Log.ActvityExtractionError("Activity is already started"); + AspNetTelemetryEventSource.Log.ActvityExtractionError("Activity is already started"); return false; } @@ -131,7 +131,7 @@ public static bool Extract(this Activity activity, NameValueCollection requestHe } else { - AspNetTelemetryCorrelationEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); + AspNetTelemetryEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair); } } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs index cca48f03c43..7826968996e 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs @@ -29,7 +29,7 @@ internal static class ActivityHelper /// /// Listener name. /// - public const string AspNetListenerName = "Microsoft.AspNet.TelemetryCorrelation"; + public const string AspNetListenerName = "OpenTelemetry.Instrumentation.AspNet.Telemetry"; /// /// Activity name for http request. @@ -72,7 +72,7 @@ public static void StopAspNetActivity(IDictionary contextItems) contextItems[ActivityKey] = null; } - AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); + AspNetTelemetryEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName); } /// @@ -97,7 +97,7 @@ public static Activity CreateRootActivity(HttpContext context, bool parseHeaders if (StartAspNetActivity(rootActivity)) { context.Items[ActivityKey] = rootActivity; - AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id); + AspNetTelemetryEventSource.Log.ActivityStarted(rootActivity.Id); return rootActivity; } } @@ -117,7 +117,7 @@ public static void WriteActivityException(IDictionary contextItems, Exception ex } AspNetListener.Write(aspNetActivity.OperationName + ".Exception", exception); - AspNetTelemetryCorrelationEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, exception); + AspNetTelemetryEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, exception); } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryCorrelationEventSource.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs similarity index 87% rename from src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryCorrelationEventSource.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs index f6c72b403d6..602c5df99e9 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryCorrelationEventSource.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,20 +16,19 @@ using System; using System.Diagnostics.Tracing; -#pragma warning disable SA1600 // Elements must be documented namespace OpenTelemetry.Instrumentation.AspNet { /// /// ETW EventSource tracing class. /// - [EventSource(Name = "Microsoft-AspNet-Telemetry-Correlation", Guid = "ace2021e-e82c-5502-d81d-657f27612673")] - internal sealed class AspNetTelemetryCorrelationEventSource : EventSource + [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 AspNetTelemetryCorrelationEventSource Log = new AspNetTelemetryCorrelationEventSource(); + public static readonly AspNetTelemetryEventSource Log = new AspNetTelemetryEventSource(); [NonEvent] public void ActivityException(string id, string eventName, Exception ex) @@ -107,4 +106,3 @@ public void ActivityException(string id, string eventName, string ex) } } } -#pragma warning restore SA1600 // Elements must be documented diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/BaseHeaderParser.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/BaseHeaderParser.cs index 072a8c3f50a..4c429212023 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/BaseHeaderParser.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/BaseHeaderParser.cs @@ -26,7 +26,7 @@ protected BaseHeaderParser(bool supportsMultipleValues) public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) { - parsedValue = default(T); + parsedValue = default; // If multiple values are supported (i.e. list of values), then accept an empty string: The header may // be added multiple times to the request/response message. E.g. @@ -38,8 +38,7 @@ public sealed override bool TryParseValue(string value, ref int index, out T par return this.SupportsMultipleValues; } - var separatorFound = false; - var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, this.SupportsMultipleValues, out separatorFound); + var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, this.SupportsMultipleValues, out var separatorFound); if (separatorFound && !this.SupportsMultipleValues) { @@ -56,15 +55,14 @@ public sealed override bool TryParseValue(string value, ref int index, out T par return this.SupportsMultipleValues; } - T result; - var length = this.GetParsedValueLength(value, current, out result); + var length = this.GetParsedValueLength(value, current, out var result); if (length == 0) { return false; } - current = current + length; + current += length; current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, this.SupportsMultipleValues, out separatorFound); // If we support multiple values and we've not reached the end of the string, then we must have a separator. diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/GenericHeaderParser.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/GenericHeaderParser.cs index 448837e5f0f..37824a0d021 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/GenericHeaderParser.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/GenericHeaderParser.cs @@ -21,17 +21,12 @@ namespace OpenTelemetry.Instrumentation.AspNet // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs internal sealed class GenericHeaderParser : BaseHeaderParser { - private GetParsedValueLengthDelegate getParsedValueLength; + private readonly GetParsedValueLengthDelegate getParsedValueLength; internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) : base(supportsMultipleValues) { - if (getParsedValueLength == null) - { - throw new ArgumentNullException(nameof(getParsedValueLength)); - } - - this.getParsedValueLength = getParsedValueLength; + this.getParsedValueLength = getParsedValueLength ?? throw new ArgumentNullException(nameof(getParsedValueLength)); } internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HeaderUtilities.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HeaderUtilities.cs index ab41f1c58cf..05644b3a8bc 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HeaderUtilities.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HeaderUtilities.cs @@ -42,14 +42,14 @@ internal static int GetNextNonEmptyOrWhitespaceIndex( // empty values, continue until the current character is neither a separator nor a whitespace. separatorFound = true; current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); + current += HttpRuleParser.GetWhitespaceLength(input, current); if (skipEmptyValues) { while ((current < input.Length) && (input[current] == ',')) { current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); + current += HttpRuleParser.GetWhitespaceLength(input, current); } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpRuleParser.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpRuleParser.cs index c99e4675b2c..bd3b86b30f6 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpRuleParser.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/HttpRuleParser.cs @@ -123,8 +123,8 @@ internal static HttpParseResult GetQuotedPairLength(string input, int startIndex { Contract.Requires(input != null); Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); - Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && - (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); + Contract.Ensures((Contract.ValueAtReturn(out _) >= 0) && + (Contract.ValueAtReturn(out _) <= (input.Length - startIndex))); length = 0; @@ -202,7 +202,7 @@ private static HttpParseResult GetExpressionLength( Contract.Requires(input != null); Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || - (Contract.ValueAtReturn(out length) > 0)); + (Contract.ValueAtReturn(out _) > 0)); length = 0; @@ -216,14 +216,13 @@ private static HttpParseResult GetExpressionLength( { // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. - var quotedPairLength = 0; if ((current + 2 < input.Length) && - (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) + (GetQuotedPairLength(input, current, out var quotedPairLength) == HttpParseResult.Parsed)) { // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, // but we actually have a quoted-string: e.g. '\' followed by a char >127 - quoted-pair only // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars. - current = current + quotedPairLength; + current += quotedPairLength; continue; } @@ -239,8 +238,7 @@ private static HttpParseResult GetExpressionLength( return HttpParseResult.InvalidFormat; } - var nestedLength = 0; - HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); + HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out var nestedLength); switch (nestedResult) { diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/NameValueHeaderValue.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/NameValueHeaderValue.cs index 7f5c2363e28..5add297c6f4 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/NameValueHeaderValue.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/Internal/NameValueHeaderValue.cs @@ -99,31 +99,35 @@ private static int GetNameValueLength(string input, int startIndex, out NameValu var name = input.Substring(startIndex, nameLength); var current = startIndex + nameLength; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); + current += HttpRuleParser.GetWhitespaceLength(input, current); // Parse the separator between name and value if ((current == input.Length) || (input[current] != '=')) { // We only have a name and that's OK. Return. - parsedValue = new NameValueHeaderValue(); - parsedValue.name = name; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + parsedValue = new NameValueHeaderValue + { + name = name, + }; + current += HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces return current - startIndex; } current++; // skip delimiter. - current = current + HttpRuleParser.GetWhitespaceLength(input, current); + current += HttpRuleParser.GetWhitespaceLength(input, current); // Parse the value, i.e. in name/value string "=" int valueLength = GetValueLength(input, current); // Value after the '=' may be empty // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. - parsedValue = new NameValueHeaderValue(); - parsedValue.name = name; - parsedValue.value = input.Substring(current, valueLength); - current = current + valueLength; - current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + parsedValue = new NameValueHeaderValue + { + name = name, + value = input.Substring(current, valueLength), + }; + current += valueLength; + current += HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces return current - startIndex; } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj index 576e5b3c782..0616df5946f 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj @@ -3,15 +3,14 @@ net461 A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. $(PackageTags);distributed-tracing;AspNet;MVC;WebAPI - - $(NoWarn),1591,CS0618 core- + + + diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/README.md b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/README.md index 3ee8460a596..f9e7a90cb9c 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/README.md +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/README.md @@ -1,6 +1,6 @@ # Telemetry correlation http module -[![NuGet](https://img.shields.io/nuget/v/Microsoft.AspNet.TelemetryCorrelation.svg)](https://www.nuget.org/packages/Microsoft.AspNet.TelemetryCorrelation/) +[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/) Telemetry correlation http module enables cross tier telemetry tracking. @@ -30,8 +30,7 @@ Telemetry correlation http module enables cross tier telemetry tracking. public void OnNext(DiagnosticListener listener) { - if (listener.Name == "Microsoft.AspNet.TelemetryCorrelation" || - listener.Name == "System.Net.Http" ) + if (listener.Name == "OpenTelemetry.Instrumentation.AspNet.Telemetry") { listener.Subscribe(new NoopDiagnosticsListener()); } diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryCorrelationHttpModule.cs b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModule.cs similarity index 84% rename from src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryCorrelationHttpModule.cs rename to src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModule.cs index 8cbee9bf7d9..953f047773a 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryCorrelationHttpModule.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModule.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,6 @@ using System; using System.ComponentModel; -using System.Diagnostics; using System.Reflection; using System.Web; @@ -25,9 +24,9 @@ namespace OpenTelemetry.Instrumentation.AspNet /// /// Http Module sets ambient state using Activity API from DiagnosticsSource package. /// - public class TelemetryCorrelationHttpModule : IHttpModule + public class TelemetryHttpModule : IHttpModule { - private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled"; + private const string BeginCalledFlag = "OpenTelemetry.Instrumentation.AspNet.BeginCalled"; // ServerVariable set only on rewritten HttpContext by URL Rewrite module. private const string URLRewriteRewrittenRequest = "IIS_WasUrlRewritten"; @@ -35,15 +34,10 @@ public class TelemetryCorrelationHttpModule : IHttpModule // ServerVariable set on every request if URL module is registered in HttpModule pipeline. private const string URLRewriteModuleVersion = "IIS_UrlRewriteModule"; - private static MethodInfo onStepMethodInfo = null; - - static TelemetryCorrelationHttpModule() - { - onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); - } + private static readonly MethodInfo OnStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep"); /// - /// Gets or sets a value indicating whether TelemetryCorrelationHttpModule should parse headers to get correlation ids. + /// Gets or sets a value indicating whether TelemetryHttpModule should parse headers to get correlation ids. /// [EditorBrowsable(EditorBrowsableState.Never)] public bool ParseHeaders { get; set; } = true; @@ -63,15 +57,15 @@ public void Init(HttpApplication context) // OnExecuteRequestStep is availabile starting with 4.7.1 // If this is executed in 4.7.1 runtime (regardless of targeted .NET version), // we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute - if (onStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) + if (OnStepMethodInfo != null && HttpRuntime.UsingIntegratedPipeline) { try { - onStepMethodInfo.Invoke(context, new object[] { (Action)this.OnExecuteRequestStep }); + OnStepMethodInfo.Invoke(context, new object[] { (Action)this.OnExecuteRequestStep }); } catch (Exception e) { - AspNetTelemetryCorrelationEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); + AspNetTelemetryEventSource.Log.OnExecuteRequestStepInvokationError(e.Message); } } else @@ -105,20 +99,20 @@ internal void OnExecuteRequestStep(HttpContextBase context, Action step) private void Application_BeginRequest(object sender, EventArgs e) { var context = ((HttpApplication)sender).Context; - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_BeginRequest"); + AspNetTelemetryEventSource.Log.TraceCallback("Application_BeginRequest"); ActivityHelper.CreateRootActivity(context, this.ParseHeaders); context.Items[BeginCalledFlag] = true; } private void Application_PreRequestHandlerExecute(object sender, EventArgs e) { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); + AspNetTelemetryEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute"); ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items); } private void Application_EndRequest(object sender, EventArgs e) { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_EndRequest"); + AspNetTelemetryEventSource.Log.TraceCallback("Application_EndRequest"); bool trackActivity = true; var context = ((HttpApplication)sender).Context; @@ -153,7 +147,7 @@ private void Application_EndRequest(object sender, EventArgs e) private void Application_Error(object sender, EventArgs e) { - AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_Error"); + AspNetTelemetryEventSource.Log.TraceCallback("Application_Error"); var context = ((HttpApplication)sender).Context; diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.install.xdt b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.install.xdt index b2ab96ba36d..6605354657d 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.install.xdt +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.install.xdt @@ -9,13 +9,13 @@ - globally - under location[@path='.'] - under location[count(@path)=0] - If any of above contains httpModules section - it will be reused. + If any of above contains httpModules section - it will be reused. Otherwise it will be created under /configuration/system.web (globally) --> - - + + @@ -31,17 +31,17 @@ - globally - under location[@path='.'] - under location[count(@path)=0] - If any of above contains httpModules section - it will be reused. + If any of above contains httpModules section - it will be reused. Otherwise it will be created under /configuration/system.web (globally) - + see https://github.com/Microsoft/ApplicationInsights-dotnet-server/blob/develop/Src/Web/Web.Nuget/Resources/web.config.install.xdt --> - - + - diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.uninstall.xdt b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.uninstall.xdt index 7ed679c2954..5b76f2bc420 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.uninstall.xdt +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/web.config.uninstall.xdt @@ -2,16 +2,16 @@ - + - - + - \ No newline at end of file + diff --git a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs b/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs index 5761e4dea63..3360b08abea 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs @@ -23,7 +23,7 @@ namespace OpenTelemetry.Instrumentation.AspNet /// internal class AspNetInstrumentation : IDisposable { - internal const string AspNetDiagnosticListenerName = "Microsoft.AspNet.TelemetryCorrelation"; + internal const string AspNetDiagnosticListenerName = "OpenTelemetry.Instrumentation.AspNet.Telemetry"; private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber; diff --git a/src/OpenTelemetry.Instrumentation.AspNet/README.md b/src/OpenTelemetry.Instrumentation.AspNet/README.md index eb342b2d7d6..81ea5fc6627 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/README.md +++ b/src/OpenTelemetry.Instrumentation.AspNet/README.md @@ -32,8 +32,8 @@ following shows changes required to your `Web.config` when using IIS web server. diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs index 12dfc5d2a40..8cfc2b76919 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/ActivityHelperTest.cs @@ -139,7 +139,7 @@ public void Do_Not_Restore_Activity_When_It_Is_Not_Lost() var context = HttpContextHelper.GetFakeHttpContext(); context.Items[ActivityHelper.ActivityKey] = root; - var module = new TelemetryCorrelationHttpModule(); + var module = new TelemetryHttpModule(); ActivityHelper.RestoreActivityIfNeeded(context.Items); diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs index 0e393a48735..37be99c7976 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigTransformTest.cs @@ -23,8 +23,8 @@ namespace OpenTelemetry.Instrumentation.AspNet.Tests public class WebConfigTransformTest { - private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; - private const string UninstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.uninstall.xdt"; + 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() @@ -43,13 +43,13 @@ public void VerifyInstallationToBasicWebConfig() - + - - + + @@ -66,12 +66,12 @@ public void VerifyUpdateWithTypeRenamingWebConfig() - + - + "; @@ -80,13 +80,13 @@ public void VerifyUpdateWithTypeRenamingWebConfig() - + - - + + @@ -103,12 +103,12 @@ public void VerifyUpdateNewerVersionWebConfig() - + - + "; @@ -117,14 +117,14 @@ public void VerifyUpdateNewerVersionWebConfig() - + - - - + + + @@ -141,13 +141,13 @@ public void VerifyUpdateWithIntegratedModeWebConfig() - + - + "; @@ -156,14 +156,14 @@ public void VerifyUpdateWithIntegratedModeWebConfig() - + - - + + "; @@ -179,13 +179,13 @@ public void VerifyUninstallationWithBasicWebConfig() - + - - + + "; @@ -211,13 +211,13 @@ public void VerifyUninstallWithIntegratedPrecondition() - + - - + + "; @@ -244,14 +244,14 @@ public void VerifyUninstallationWithUserModules() - + - + - + "; @@ -296,14 +296,14 @@ public void VerifyInstallationToWebConfigWithUserModules() - + - - + + @@ -322,14 +322,14 @@ public void VerifyInstallationToEmptyWebConfig() - + - - + + "; @@ -348,13 +348,13 @@ public void VerifyInstallationToWebConfigWithoutModules() - - + + - + "; diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs index 52ec038637a..f67edfd9062 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/WebConfigWithLocationTagTransformTest.cs @@ -23,7 +23,7 @@ namespace OpenTelemetry.Instrumentation.AspNet.Tests public class WebConfigWithLocationTagTransformTest { - private const string InstallConfigTransformationResourceName = "Microsoft.AspNet.TelemetryCorrelation.Tests.Resources.web.config.install.xdt"; + private const string InstallConfigTransformationResourceName = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.Resources.web.config.install.xdt"; [Fact] public void VerifyInstallationWhenNonGlobalLocationTagExists() @@ -50,14 +50,14 @@ public void VerifyInstallationWhenNonGlobalLocationTagExists() - + - - + + "; @@ -105,14 +105,14 @@ public void VerifyInstallationWhenGlobalAndNonGlobalLocationTagExists() - + - - + + @@ -152,14 +152,14 @@ public void VerifyInstallationToLocationTagWithDotPathAndExistingModules() - + - - + + @@ -197,14 +197,14 @@ public void VerifyInstallationToLocationTagWithEmptyPathAndExistingModules() - + - - + + @@ -239,14 +239,14 @@ public void VerifyInstallationToLocationTagWithDotPathWithNoModules() - + - - + + @@ -278,14 +278,14 @@ public void VerifyInstallationToLocationTagWithEmptyPathWithNoModules() - + - - + + @@ -334,14 +334,14 @@ public void VerifyInstallationToLocationTagWithDotPathWithGlobalModules() - + - - + + "; @@ -376,14 +376,14 @@ public void VerifyInstallationToLocationTagWithEmptyPathWithGlobalModules() - + - - + + From 5af3d9387d2f33b3c3ee520bc435e1d7ee0784bb Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Tue, 10 Aug 2021 11:09:05 -0700 Subject: [PATCH 38/45] disable TelemetryHttpModule API Compat for now (#2244) --- ...emetry.Instrumentation.AspNet.TelemetryHttpModule.csproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj index 0616df5946f..c9f90a4892d 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj @@ -6,6 +6,12 @@ core- + + + false + + From c9c78691cec293af20972e55b661b14b126cb55a Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Tue, 10 Aug 2021 20:38:10 -0700 Subject: [PATCH 39/45] remove trailing space --- src/OpenTelemetry.Instrumentation.AspNet/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Instrumentation.AspNet/README.md b/src/OpenTelemetry.Instrumentation.AspNet/README.md index 81ea5fc6627..9a465608fbd 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/README.md +++ b/src/OpenTelemetry.Instrumentation.AspNet/README.md @@ -31,7 +31,7 @@ following shows changes required to your `Web.config` when using IIS web server. ```xml - Date: Wed, 11 Aug 2021 23:46:57 +0100 Subject: [PATCH 40/45] Mark Microsoft.CodeCoverage as PrivateAssets (#2237) --- build/Common.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Common.props b/build/Common.props index 014f6bcead3..0db3be2760e 100644 --- a/build/Common.props +++ b/build/Common.props @@ -56,7 +56,7 @@ All - + From b663b654a07a1440945a31c5c07b160dfc464fbf Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Wed, 11 Aug 2021 17:50:20 -0700 Subject: [PATCH 41/45] Modify minvertag prefix for aspnet telemetry module project (#2250) --- ...metry.Instrumentation.AspNet.TelemetryHttpModule.csproj | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj index c9f90a4892d..896fbed7bdd 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj @@ -3,13 +3,6 @@ net461 A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. $(PackageTags);distributed-tracing;AspNet;MVC;WebAPI - core- - - - - - false From adb2b6cef6a2efa28f1b4e88668e0ab9214d1a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Fri, 13 Aug 2021 20:09:21 +0200 Subject: [PATCH 42/45] Update OTLP Exporter insecure channel docs (#2255) --- src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md index 5f39d810b80..5deba232f19 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md @@ -53,10 +53,8 @@ values of the `OtlpExporterOptions` ## Special case when using insecure channel -If your application is [.NET Standard -2.1](https://docs.microsoft.com/dotnet/standard/net-standard) or above, and you -are using an insecure (http) endpoint, the following switch must be set before -adding `OtlpExporter`. +If your application is targeting .NET Core 3.1, and you are using an insecure +(HTTP) endpoint, the following switch must be set before adding `OtlpExporter`. ```csharp AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", From 0959e562d6af1fac5e178607b2ec85956ac5722c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Fri, 13 Aug 2021 20:22:30 +0200 Subject: [PATCH 43/45] Add environment variable detectors to the default ResourceBuilder (#2247) --- src/OpenTelemetry/CHANGELOG.md | 4 +++ src/OpenTelemetry/README.md | 4 +-- .../Resources/ResourceBuilder.cs | 6 +++- .../Resources/ResourceTest.cs | 28 ++++++++++++++----- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 1749e7c1a0f..f3d9fd1eb9f 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +* `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)) diff --git a/src/OpenTelemetry/README.md b/src/OpenTelemetry/README.md index 33a840986a0..d7b17455c97 100644 --- a/src/OpenTelemetry/README.md +++ b/src/OpenTelemetry/README.md @@ -245,8 +245,8 @@ using var tracerProvider = Sdk.CreateTracerProviderBuilder() .Build(); ``` -It is also possible to configure the `Resource` by using `AddEnvironmentVariableDetector` -together with following environmental variables: +It is also possible to configure the `Resource` by using following +environmental variables: | Environment variable | Description | diff --git a/src/OpenTelemetry/Resources/ResourceBuilder.cs b/src/OpenTelemetry/Resources/ResourceBuilder.cs index 4bc8cc500cd..99aff474daf 100644 --- a/src/OpenTelemetry/Resources/ResourceBuilder.cs +++ b/src/OpenTelemetry/Resources/ResourceBuilder.cs @@ -43,10 +43,14 @@ private ResourceBuilder() /// service.name added. See resource /// semantic conventions for details. + /// Additionally it adds resource attributes parsed from OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME environment variables + /// to a following the Resource + /// SDK. /// /// Created . public static ResourceBuilder CreateDefault() - => new ResourceBuilder().AddResource(DefaultResource); + => new ResourceBuilder().AddResource(DefaultResource).AddEnvironmentVariableDetector(); /// /// Creates an empty instance. diff --git a/test/OpenTelemetry.Tests/Resources/ResourceTest.cs b/test/OpenTelemetry.Tests/Resources/ResourceTest.cs index 12288630a65..cc5bc3bc237 100644 --- a/test/OpenTelemetry.Tests/Resources/ResourceTest.cs +++ b/test/OpenTelemetry.Tests/Resources/ResourceTest.cs @@ -391,7 +391,7 @@ public void MergeResource_UpdatingResourceOverridesCurrentResource() public void GetResourceWithTelemetrySDKAttributes() { // Arrange - var resource = ResourceBuilder.CreateDefault().AddTelemetrySdk().AddEnvironmentVariableDetector().Build(); + var resource = ResourceBuilder.CreateDefault().AddTelemetrySdk().Build(); // Assert var attributes = resource.Attributes; @@ -403,7 +403,7 @@ public void GetResourceWithTelemetrySDKAttributes() public void GetResourceWithDefaultAttributes_EmptyResource() { // Arrange - var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().Build(); + var resource = ResourceBuilder.CreateDefault().Build(); // Assert var attributes = resource.Attributes; @@ -415,7 +415,7 @@ public void GetResourceWithDefaultAttributes_EmptyResource() public void GetResourceWithDefaultAttributes_ResourceWithAttrs() { // Arrange - var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddAttributes(this.CreateAttributes(2)).Build(); + var resource = ResourceBuilder.CreateDefault().AddAttributes(this.CreateAttributes(2)).Build(); // Assert var attributes = resource.Attributes; @@ -429,7 +429,7 @@ public void GetResourceWithDefaultAttributes_WithResourceEnvVar() { // Arrange Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "EVKey1=EVVal1,EVKey2=EVVal2"); - var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddAttributes(this.CreateAttributes(2)).Build(); + var resource = ResourceBuilder.CreateDefault().AddAttributes(this.CreateAttributes(2)).Build(); // Assert var attributes = resource.Attributes; @@ -440,12 +440,26 @@ public void GetResourceWithDefaultAttributes_WithResourceEnvVar() Assert.Contains(new KeyValuePair("EVKey2", "EVVal2"), attributes); } + [Fact] + public void EnvironmentVariableDetectors_DoNotDuplicateAttributes() + { + // Arrange + Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "EVKey1=EVVal1,EVKey2=EVVal2"); + var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddEnvironmentVariableDetector().Build(); + + // Assert + var attributes = resource.Attributes; + Assert.Equal(3, attributes.Count()); + Assert.Contains(new KeyValuePair("EVKey1", "EVVal1"), attributes); + Assert.Contains(new KeyValuePair("EVKey2", "EVVal2"), attributes); + } + [Fact] public void GetResource_WithServiceEnvVar() { // Arrange Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, "some-service"); - var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddAttributes(this.CreateAttributes(2)).Build(); + var resource = ResourceBuilder.CreateDefault().AddAttributes(this.CreateAttributes(2)).Build(); // Assert var attributes = resource.Attributes; @@ -460,7 +474,7 @@ public void GetResource_WithServiceNameSetWithTwoEnvVars() // Arrange Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "service.name=from-resource-attr"); Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, "from-service-name"); - var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddAttributes(this.CreateAttributes(2)).Build(); + var resource = ResourceBuilder.CreateDefault().AddAttributes(this.CreateAttributes(2)).Build(); // Assert var attributes = resource.Attributes; @@ -475,7 +489,7 @@ public void GetResource_WithServiceNameSetWithTwoEnvVarsAndCode() // Arrange Environment.SetEnvironmentVariable(OtelEnvResourceDetector.EnvVarKey, "service.name=from-resource-attr"); Environment.SetEnvironmentVariable(OtelServiceNameEnvVarDetector.EnvVarKey, "from-service-name"); - var resource = ResourceBuilder.CreateDefault().AddEnvironmentVariableDetector().AddService("from-code").AddAttributes(this.CreateAttributes(2)).Build(); + var resource = ResourceBuilder.CreateDefault().AddService("from-code").AddAttributes(this.CreateAttributes(2)).Build(); // Assert var attributes = resource.Attributes; From 95e685165e789bf651acb08d42241c25ec887299 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Wed, 18 Aug 2021 16:00:13 -0400 Subject: [PATCH 44/45] Update RELEASING.md (#2263) --- build/RELEASING.md | 196 ++++++++++++++++++++++----------------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/build/RELEASING.md b/build/RELEASING.md index 8becf9b3cb6..e1008586085 100644 --- a/build/RELEASING.md +++ b/build/RELEASING.md @@ -2,130 +2,130 @@ Only for Maintainers. -1.Decide the tag name (version name) to be released. - eg: 1.0.0-rc2, 1.0.0 etc. - -2.Run the following PowerShell from the root of the - repo to get combined changelog (to be used later). - -```powershell - $changelogs = Get-ChildItem -Path . -Recurse -Filter changelog.md - foreach ($changelog in $changelogs) - { - Add-Content -Path .\combinedchangelog.md -Value "**$($changelog.Directory.Name)**" - $lines = Get-Content -Path $changelog.FullName - $started = $false - $ended = $false - foreach ($line in $lines) - { - if($line -like "## Unreleased" -and $started -ne $true) - { - $started = $true - } - elseif($line -like "## *" -and $started -eq $true) - { - $ended = $true - break - } - else - { - if ($started -eq $true) + 1. Decide the tag name (version name) to be released. + eg: 1.0.0-rc2, 1.0.0 etc. + + 2. Run the following PowerShell from the root of the + repo to get combined changelog (to be used later). + + ```powershell + $changelogs = Get-ChildItem -Path . -Recurse -Filter changelog.md + foreach ($changelog in $changelogs) + { + Add-Content -Path .\combinedchangelog.md -Value "**$($changelog.Directory.Name)**" + $lines = Get-Content -Path $changelog.FullName + $started = $false + $ended = $false + foreach ($line in $lines) + { + if($line -like "## Unreleased" -and $started -ne $true) { - Add-Content -Path .\combinedchangelog.md $line + $started = $true } - } - } - } -``` + elseif($line -like "## *" -and $started -eq $true) + { + $ended = $true + break + } + else + { + if ($started -eq $true) + { + Add-Content -Path .\combinedchangelog.md $line + } + } + } + } + ``` - This generates combined changelog to be used in Github release. - Once contents of combined changelog is saved somewhere, - delete the file. + This generates combined changelog to be used in Github release. + Once contents of combined changelog is saved somewhere, + delete the file. -3.Run the following PowerShell script from the root of the repo. - This updates all the changelog to have release date for the - current version being released. - Replace the version with actual version. - The actual version would be the tag name from step1. + 3. Run the following PowerShell script from the root of the repo. + This updates all the changelog to have release date for the + current version being released. + Replace the version with actual version. + The actual version would be the tag name from step1. -```powershell - $changelogs = Get-ChildItem -Path . -Recurse -Filter changelog.md - foreach ($changelog in $changelogs) - { - (Get-Content -Path $changelog.FullName) -replace "Unreleased", "Unreleased + ```powershell + $changelogs = Get-ChildItem -Path . -Recurse -Filter changelog.md + foreach ($changelog in $changelogs) + { + (Get-Content -Path $changelog.FullName) -replace "Unreleased", "Unreleased -## 1.0.0-rc2 + ## 1.0.0-rc2 -Released $(Get-Date -UFormat '%Y-%b-%d')" | Set-Content -Path $changelog.FullName - } -``` + Released $(Get-Date -UFormat '%Y-%b-%d')" | Set-Content -Path $changelog.FullName + } + ``` -4.Submit PR with the above changes, and get it merged. + 4. Submit PR with the above changes, and get it merged. -5.Tag Git with version to be released e.g.: + 5. Tag Git with version to be released e.g.: - ```sh - git tag -a 1.0.0-rc2 -m "1.0.0-rc2" - git push origin 1.0.0-rc2 - ``` + ```sh + git tag -a 1.0.0-rc2 -m "1.0.0-rc2" + git push origin 1.0.0-rc2 + ``` -We use [MinVer](https://github.com/adamralph/minver) to do versioning, -which produces version numbers based on git tags. + We use [MinVer](https://github.com/adamralph/minver) to do versioning, + which produces version numbers based on git tags. -Note: -If releasing only core components, prefix the tag -with "core-". For example: -git tag -a core-1.1.0-beta1 -m "1.1.0-beta1 of all core components" + Note: + If releasing only core components, prefix the tag + with "core-". For example: + git tag -a core-1.1.0-beta1 -m "1.1.0-beta1 of all core components" -If releasing only non-core components, use tags without -prefix. For example: -git tag -a 1.0.0-rc3 -m "1.0.0-rc3 of all non-core components" + If releasing only non-core components, use tags without + prefix. For example: + git tag -a 1.0.0-rc3 -m "1.0.0-rc3 of all non-core components" -If releasing both, push both tags above. + If releasing both, push both tags above. -6.Open [Pack and publish to MyGet - workflow](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) - and manually trigger a build. At the end of this, MyGet will have the - packages. The package name will be the tag name used in step 5. + 6. Open [Pack and publish to MyGet + workflow](https://github.com/open-telemetry/opentelemetry-dotnet/actions/workflows/publish-packages-1.0.yml) + and manually trigger a build. At the end of this, MyGet will have the + packages. The package name will be the tag name used in step 5. -7.Validate using MyGet packages. Basic sanity checks :) + 7. Validate using MyGet packages. Basic sanity checks :) -8.From the above build, get the artifacts from the drop, which has all the - NuGet packages. + 8. From the above build, get the artifacts from the drop, which has all the + NuGet packages. -9.Copy all the NuGet files and symbols into a local folder. If only - releasing core packages, only copy them over. + 9. Copy all the NuGet files and symbols into a local folder. If only + releasing core packages, only copy them over. -10.Download latest [nuget.exe](https://www.nuget.org/downloads) into - the same folder from step 9. +10. Download latest [nuget.exe](https://www.nuget.org/downloads) into + the same folder from step 9. -11.Obtain the API key from nuget.org (Only maintainers have access) +11. Obtain the API key from nuget.org (Only maintainers have access) -12.Run the following commands from PowerShell from local folder used in step 9: +12. Run the following commands from PowerShell from local folder used in step 9: - ```powershell - .\nuget.exe setApiKey + ```powershell + .\nuget.exe setApiKey - get-childitem -Recurse | where {$_.extension -eq - ".nupkg"} | foreach ($_) {.\nuget.exe push $_.fullname -Source - https://api.nuget.org/v3/index.json} - ``` + get-childitem -Recurse | where {$_.extension -eq + ".nupkg"} | foreach ($_) {.\nuget.exe push $_.fullname -Source + https://api.nuget.org/v3/index.json} + ``` -13.Packages would be available in nuget.org in few minutes. - Validate that the package is uploaded. +13. Packages would be available in nuget.org in few minutes. + Validate that the package is uploaded. -14.Delete the API key generated in step 11. +14. Delete the API key generated in step 11. -15.Make the Github release with tag from Step5 -and contents of combinedchangelog from Step2. +15. Make the Github release with tag from Step5 + and contents of combinedchangelog from Step2. -TODO: Add tagging for Metrics release. -TODO: Separate version for instrumention/hosting/OTshim package. + TODO: Add tagging for Metrics release. + TODO: Separate version for instrumention/hosting/OTshim package. -16.Update the OpenTelemetry.io document -[here](https://github.com/open-telemetry/opentelemetry.io/tree/main/content/en/docs/net) -by sending a Pull Request. +16. Update the OpenTelemetry.io document + [here](https://github.com/open-telemetry/opentelemetry.io/tree/main/content/en/docs/net) + by sending a Pull Request. -17.If a new stable version of the core packages were released, - update `OTelPreviousStableVer` in Common.props - to the just released stable version. +17. If a new stable version of the core packages were released, + update `OTelPreviousStableVer` in Common.props + to the just released stable version. From d649b3d591c0764aa532f6c0fcfb413e48aa33b7 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 20 Aug 2021 17:25:13 -0700 Subject: [PATCH 45/45] Docs and fixes for histogram (#2269) --- OpenTelemetry.sln | 8 ++ docs/metrics/README.md | 4 + .../getting-started-histogram/Program.cs | 55 +++++++++++++ .../getting-started-histogram/README.md | 67 +++++++++++++++ .../getting-started-histogram.csproj | 6 ++ .../ConsoleMetricExporter.cs | 14 +++- .../MetricAggregators/HistogramBucket.cs | 6 +- .../MetricAggregators/HistogramMetric.cs | 70 ++++++++++++++++ .../HistogramMetricAggregator.cs | 82 ++++++------------- .../Metrics/AggregatorTest.cs | 12 +-- 10 files changed, 257 insertions(+), 67 deletions(-) create mode 100644 docs/metrics/README.md create mode 100644 docs/metrics/getting-started-histogram/Program.cs create mode 100644 docs/metrics/getting-started-histogram/README.md create mode 100644 docs/metrics/getting-started-histogram/getting-started-histogram.csproj create mode 100644 src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetric.cs diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 63bf9532f5a..ec69c97b5fc 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -161,6 +161,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "metrics", "metrics", "{3277B1C0-BDFE-4460-9B0D-D9A661FB48DB}" ProjectSection(SolutionItems) = preProject docs\metrics\building-your-own-exporter.md = docs\metrics\building-your-own-exporter.md + docs\metrics\README.md = docs\metrics\README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logs", "logs", "{3862190B-E2C5-418E-AFDC-DB281FB5C705}" @@ -213,6 +214,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentati 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "getting-started-histogram", "docs\metrics\getting-started-histogram\getting-started-histogram.csproj", "{92ED77A6-37B4-447D-B4C4-15DB005A589C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -423,6 +426,10 @@ Global {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 + {92ED77A6-37B4-447D-B4C4-15DB005A589C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92ED77A6-37B4-447D-B4C4-15DB005A589C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92ED77A6-37B4-447D-B4C4-15DB005A589C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92ED77A6-37B4-447D-B4C4-15DB005A589C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -455,6 +462,7 @@ Global {08D29501-F0A3-468F-B18D-BD1821A72383} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} {64E3D8BB-93AB-4571-93F7-ED8D64DFFD06} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} {DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8} = {3277B1C0-BDFE-4460-9B0D-D9A661FB48DB} + {92ED77A6-37B4-447D-B4C4-15DB005A589C} = {3277B1C0-BDFE-4460-9B0D-D9A661FB48DB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521} diff --git a/docs/metrics/README.md b/docs/metrics/README.md new file mode 100644 index 00000000000..c1f16fa5116 --- /dev/null +++ b/docs/metrics/README.md @@ -0,0 +1,4 @@ +# Getting Started with OpenTelemetry .NET Metrics in 5 Minutes + +* [Getting started with Counter](.\getting-started\README.md) +* [Getting started with Histogram](.\getting-started-histogram\README.md) diff --git a/docs/metrics/getting-started-histogram/Program.cs b/docs/metrics/getting-started-histogram/Program.cs new file mode 100644 index 00000000000..9b74b5e5052 --- /dev/null +++ b/docs/metrics/getting-started-histogram/Program.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 System.Diagnostics.Metrics; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry; +using OpenTelemetry.Metrics; + +public class Program +{ + private static readonly Meter MyMeter = new Meter("TestMeter", "0.0.1"); + private static readonly Histogram MyHistogram = MyMeter.CreateHistogram("histogram"); + private static readonly Random RandomGenerator = new Random(); + + public static async Task Main(string[] args) + { + using var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddSource("TestMeter") + .AddConsoleExporter() + .Build(); + + using var token = new CancellationTokenSource(); + Task writeMetricTask = new Task(() => + { + while (!token.IsCancellationRequested) + { + MyHistogram.Record( + RandomGenerator.Next(1, 1000), + new KeyValuePair("tag1", "value1"), + new KeyValuePair("tag2", "value2")); + Task.Delay(10).Wait(); + } + }); + writeMetricTask.Start(); + + token.CancelAfter(10000); + await writeMetricTask; + } +} diff --git a/docs/metrics/getting-started-histogram/README.md b/docs/metrics/getting-started-histogram/README.md new file mode 100644 index 00000000000..0b8e9fabc9f --- /dev/null +++ b/docs/metrics/getting-started-histogram/README.md @@ -0,0 +1,67 @@ +# Getting Started with OpenTelemetry .NET in 5 Minutes + +First, download and install the [.NET Core +SDK](https://dotnet.microsoft.com/download) on your computer. + +Create a new console application and run it: + +```sh +dotnet new console --output getting-started-histogram +cd getting-started +dotnet run +``` + +You should see the following output: + +```text +Hello World! +``` + +Install the +[OpenTelemetry.Exporter.Console](../../../src/OpenTelemetry.Exporter.Console/README.md) +package: + +```sh +dotnet add package OpenTelemetry.Exporter.Console +``` + +Update the `Program.cs` file with the code from [Program.cs](./Program.cs): + +Run the application again (using `dotnet run`) and you should see the metric +output from the console, similar to shown below: + + +```text +Export 14:30:58.201 14:30:59.177 histogram [tag1=value1;tag2=value2] Histogram, Meter: TestMeter/0.0.1 +Value: Sum: 33862 Count: 62 +(-? - 0) : 0 +(0 - 5) : 0 +(5 - 10) : 0 +(10 - 25) : 2 +(25 - 50) : 0 +(50 - 75) : 1 +(75 - 100) : 1 +(100 - 250) : 6 +(250 - 500) : 18 +(500 - 1000) : 34 +(1000 - ?) : 0 +``` + + +Congratulations! You are now collecting histogram metrics using OpenTelemetry. + +What does the above program do? + +The program creates a +[Meter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#meter) +instance named "TestMeter" and then creates a +[Histogram](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#histogram) +instrument from it. This histogram is used to repeatedly report random metric +measurements until exited after 10 seconds. + +An OpenTelemetry +[MeterProvider](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#meterprovider) +is configured to subscribe to instruments from the Meter `TestMeter`, and +aggregate the measurements in-memory. The pre-aggregated metrics are exported +every 1 second to a `ConsoleExporter`. `ConsoleExporter` simply displays it on +the console. diff --git a/docs/metrics/getting-started-histogram/getting-started-histogram.csproj b/docs/metrics/getting-started-histogram/getting-started-histogram.csproj new file mode 100644 index 00000000000..9f5b6b79bc3 --- /dev/null +++ b/docs/metrics/getting-started-histogram/getting-started-histogram.csproj @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs index e641db63d81..f07e7480fcd 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs @@ -88,7 +88,15 @@ public override ExportResult Export(in Batch batch) case MetricType.Histogram: { var histogramMetric = metric as IHistogramMetric; - valueDisplay = string.Format("Sum: {0} Count: {1}", histogramMetric.PopulationSum, histogramMetric.PopulationCount); + var bucketsBuilder = new StringBuilder(); + bucketsBuilder.Append($"Sum: {histogramMetric.PopulationSum} Count: {histogramMetric.PopulationCount} \n"); + foreach (var bucket in histogramMetric.Buckets) + { + bucketsBuilder.Append($"({bucket.LowBoundary} - {bucket.HighBoundary}) : {bucket.Count}"); + bucketsBuilder.AppendLine(); + } + + valueDisplay = bucketsBuilder.ToString(); break; } @@ -102,7 +110,7 @@ public override ExportResult Export(in Batch batch) string time = $"{metric.StartTimeExclusive.ToLocalTime().ToString("HH:mm:ss.fff")} {metric.EndTimeInclusive.ToLocalTime().ToString("HH:mm:ss.fff")}"; - var msg = new StringBuilder($"Export {time} {metric.Name} [{string.Join(";", tags)}] {metric.MetricType} Value: {valueDisplay}"); + var msg = new StringBuilder($"Export {time} {metric.Name} [{string.Join(";", tags)}] {metric.MetricType}"); if (!string.IsNullOrEmpty(metric.Description)) { @@ -124,6 +132,8 @@ public override ExportResult Export(in Batch batch) } } + msg.AppendLine(); + msg.Append($"Value: {valueDisplay}"); Console.WriteLine(msg); } } diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/HistogramBucket.cs b/src/OpenTelemetry/Metrics/MetricAggregators/HistogramBucket.cs index 448bf1215d3..b54ae5e9dd2 100644 --- a/src/OpenTelemetry/Metrics/MetricAggregators/HistogramBucket.cs +++ b/src/OpenTelemetry/Metrics/MetricAggregators/HistogramBucket.cs @@ -18,8 +18,8 @@ namespace OpenTelemetry.Metrics { public struct HistogramBucket { - internal double LowBoundary; - internal double HighBoundary; - internal long Count; + public double LowBoundary; + public double HighBoundary; + public long Count; } } diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetric.cs b/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetric.cs new file mode 100644 index 00000000000..cf740093875 --- /dev/null +++ b/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetric.cs @@ -0,0 +1,70 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; + +namespace OpenTelemetry.Metrics +{ + internal class HistogramMetric : IHistogramMetric + { + internal HistogramMetric(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair[] attributes, int bucketCount) + { + this.Name = name; + this.Description = description; + this.Unit = unit; + this.Meter = meter; + this.StartTimeExclusive = startTimeExclusive; + this.Attributes = attributes; + this.MetricType = MetricType.Histogram; + this.BucketsArray = new HistogramBucket[bucketCount]; + } + + public string Name { get; private set; } + + public string Description { get; private set; } + + public string Unit { get; private set; } + + public Meter Meter { get; private set; } + + public DateTimeOffset StartTimeExclusive { get; internal set; } + + public DateTimeOffset EndTimeInclusive { get; internal set; } + + public KeyValuePair[] Attributes { get; private set; } + + public bool IsDeltaTemporality { get; internal set; } + + public IEnumerable Exemplars { get; private set; } = new List(); + + public long PopulationCount { get; internal set; } + + public double PopulationSum { get; internal set; } + + public IEnumerable Buckets => this.BucketsArray; + + public MetricType MetricType { get; private set; } + + internal HistogramBucket[] BucketsArray { get; set; } + + public string ToDisplayString() + { + return $"Count={this.PopulationCount},Sum={this.PopulationSum}"; + } + } +} diff --git a/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs b/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs index 0f1c79aec4c..7b5229fdbd3 100644 --- a/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs +++ b/src/OpenTelemetry/Metrics/MetricAggregators/HistogramMetricAggregator.cs @@ -20,14 +20,17 @@ namespace OpenTelemetry.Metrics { - internal class HistogramMetricAggregator : IHistogramMetric, IAggregator + internal class HistogramMetricAggregator : IAggregator { private static readonly double[] DefaultBoundaries = new double[] { 0, 5, 10, 25, 50, 75, 100, 250, 500, 1000 }; private readonly object lockUpdate = new object(); private HistogramBucket[] buckets; - + private long populationCount; + private double populationSum; private double[] boundaries; + private DateTimeOffset startTimeExclusive; + private HistogramMetric histogramMetric; internal HistogramMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair[] attributes) : this(name, description, unit, meter, startTimeExclusive, attributes, DefaultBoundaries) @@ -36,12 +39,8 @@ internal HistogramMetricAggregator(string name, string description, string unit, internal HistogramMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair[] attributes, double[] boundaries) { - this.Name = name; - this.Description = description; - this.Unit = unit; - this.Meter = meter; - this.StartTimeExclusive = startTimeExclusive; - this.Attributes = attributes; + this.startTimeExclusive = startTimeExclusive; + this.histogramMetric = new HistogramMetric(name, description, unit, meter, startTimeExclusive, attributes, boundaries.Length + 1); if (boundaries.Length == 0) { @@ -50,35 +49,8 @@ internal HistogramMetricAggregator(string name, string description, string unit, this.boundaries = boundaries; this.buckets = this.InitializeBucket(boundaries); - this.MetricType = MetricType.Summary; } - public string Name { get; private set; } - - public string Description { get; private set; } - - public string Unit { get; private set; } - - public Meter Meter { get; private set; } - - public DateTimeOffset StartTimeExclusive { get; private set; } - - public DateTimeOffset EndTimeInclusive { get; private set; } - - public KeyValuePair[] Attributes { get; private set; } - - public bool IsDeltaTemporality { get; private set; } - - public IEnumerable Exemplars { get; private set; } = new List(); - - public long PopulationCount { get; private set; } - - public double PopulationSum { get; private set; } - - public MetricType MetricType { get; private set; } - - public IEnumerable Buckets => this.buckets; - public void Update(T value) where T : struct { @@ -111,37 +83,34 @@ public void Update(T value) lock (this.lockUpdate) { - this.PopulationCount++; - this.PopulationSum += val; + this.populationCount++; + this.populationSum += val; this.buckets[i].Count++; } } public IMetric Collect(DateTimeOffset dt, bool isDelta) { - if (this.PopulationCount == 0) + if (this.populationCount == 0) { // TODO: Output stale markers return null; } - var cloneItem = new HistogramMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes, this.boundaries); - lock (this.lockUpdate) { - cloneItem.Exemplars = this.Exemplars; - cloneItem.EndTimeInclusive = dt; - cloneItem.PopulationCount = this.PopulationCount; - cloneItem.PopulationSum = this.PopulationSum; - cloneItem.boundaries = this.boundaries; - this.buckets.CopyTo(cloneItem.buckets, 0); - cloneItem.IsDeltaTemporality = isDelta; + this.histogramMetric.StartTimeExclusive = this.startTimeExclusive; + this.histogramMetric.EndTimeInclusive = dt; + this.histogramMetric.PopulationCount = this.populationCount; + this.histogramMetric.PopulationSum = this.populationSum; + this.buckets.CopyTo(this.histogramMetric.BucketsArray, 0); + this.histogramMetric.IsDeltaTemporality = isDelta; if (isDelta) { - this.StartTimeExclusive = dt; - this.PopulationCount = 0; - this.PopulationSum = 0; + this.startTimeExclusive = dt; + this.populationCount = 0; + this.populationSum = 0; for (int i = 0; i < this.buckets.Length; i++) { this.buckets[i].Count = 0; @@ -149,12 +118,13 @@ public IMetric Collect(DateTimeOffset dt, bool isDelta) } } - return cloneItem; - } - - public string ToDisplayString() - { - return $"Count={this.PopulationCount},Sum={this.PopulationSum}"; + // TODO: Confirm that this approach of + // re-using the same instance is correct. + // This avoids allocating a new instance. + // It is read only for Exporters, + // and also there is no parallel + // Collect allowed. + return this.histogramMetric; } private HistogramBucket[] InitializeBucket(double[] boundaries) diff --git a/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs b/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs index fb8437dd687..e31fe0214a3 100644 --- a/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs @@ -44,9 +44,9 @@ public void HistogramDistributeToAllBuckets() var metric = hist.Collect(DateTimeOffset.UtcNow, false); Assert.NotNull(metric); - Assert.IsType(metric); + Assert.IsType(metric); - if (metric is HistogramMetricAggregator agg) + if (metric is HistogramMetric agg) { int len = 0; foreach (var bucket in agg.Buckets) @@ -71,9 +71,9 @@ public void HistogramCustomBoundaries() var metric = hist.Collect(DateTimeOffset.UtcNow, false); Assert.NotNull(metric); - Assert.IsType(metric); + Assert.IsType(metric); - if (metric is HistogramMetricAggregator agg) + if (metric is HistogramMetric agg) { int len = 0; foreach (var bucket in agg.Buckets) @@ -102,9 +102,9 @@ public void HistogramWithEmptyBuckets() var metric = hist.Collect(DateTimeOffset.UtcNow, false); Assert.NotNull(metric); - Assert.IsType(metric); + Assert.IsType(metric); - if (metric is HistogramMetricAggregator agg) + if (metric is HistogramMetric agg) { var expectedCounts = new int[] { 3, 0, 2, 1 }; int len = 0;