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
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