diff --git a/.github/workflows/package-Instrumentation.Owin.yml b/.github/workflows/package-Instrumentation.Owin.yml
new file mode 100644
index 0000000000..5ae2534edb
--- /dev/null
+++ b/.github/workflows/package-Instrumentation.Owin.yml
@@ -0,0 +1,49 @@
+name: Pack OpenTelemetry.Contrib.Instrumentation.Owin
+
+on:
+ workflow_dispatch:
+ inputs:
+ logLevel:
+ description: 'Log level'
+ required: true
+ default: 'warning'
+ push:
+ tags:
+ - 'Instrumentation.Owin-*'
+
+jobs:
+ build-test-pack:
+ runs-on: ${{ matrix.os }}
+ env:
+ PROJECT: OpenTelemetry.Contrib.Instrumentation.Owin
+
+ strategy:
+ matrix:
+ os: [windows-latest]
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0 # fetching all
+
+ - name: Install dependencies
+ run: dotnet restore
+
+ - name: dotnet build ${{env.PROJECT}}
+ run: dotnet build src/${{env.PROJECT}} --configuration Release --no-restore -p:Deterministic=true
+
+ - name: dotnet test ${{env.PROJECT}}
+ run: dotnet test test/${{env.PROJECT}}.Tests
+
+ - name: dotnet pack ${{env.PROJECT}}
+ run: dotnet pack src/${{env.PROJECT}} --configuration Release --no-build
+
+ - name: Publish Artifacts
+ uses: actions/upload-artifact@v2
+ with:
+ name: ${{env.PROJECT}}-packages
+ path: '**/${{env.PROJECT}}/bin/**/*.*nupkg'
+
+ - name: Publish Nuget
+ run: |
+ nuget push **/${{env.PROJECT}}/bin/**/*.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey ${{ secrets.NUGET_TOKEN }} -SymbolApiKey ${{ secrets.NUGET_TOKEN }}
diff --git a/CODEOWNERS b/CODEOWNERS
index 12d3abdfd4..d22258b37e 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -8,8 +8,9 @@ src/OpenTelemetry.Contrib.Exporter.Stackdriver/ @ope
src/OpenTelemetry.Contrib.Extensions.AWSXRay/ @open-telemetry/dotnet-contrib-approvers @srprash @lupengamzn
src/OpenTelemetry.Contrib.Instrumentation.ElasticsearchClient/ @open-telemetry/dotnet-contrib-approvers @ejsmith
src/OpenTelemetry.Contrib.Instrumentation.EntityFrameworkCore/ @open-telemetry/dotnet-contrib-approvers
-src/OpenTelemetry.Contrib.Instrumentation.MassTransit/ @open-telemetry/dotnet-contrib-approvers @alexvaluyskiy
src/OpenTelemetry.Contrib.Instrumentation.GrpcCore/ @open-telemetry/dotnet-contrib-approvers @pcwiese
+src/OpenTelemetry.Contrib.Instrumentation.MassTransit/ @open-telemetry/dotnet-contrib-approvers @alexvaluyskiy
+src/OpenTelemetry.Contrib.Instrumentation.Owin/ @open-telemetry/dotnet-contrib-approvers @codeblanch
src/OpenTelemetry.Contrib.Instrumentation.Wcf/ @open-telemetry/dotnet-contrib-approvers @codeblanch
src/OpenTelemetry.Contrib.Instrumentation.MySqlData/ @open-telemetry/dotnet-contrib-approvers @moonheart
src/OpenTelemetry.Contrib.Preview/ @open-telemetry/dotnet-contrib-approvers @codeblanch
@@ -18,8 +19,9 @@ test/OpenTelemetry.Contrib.Exporter.Stackdriver.Tests/ @ope
test/OpenTelemetry.Contrib.Extensions.AWSXRay.Tests/ @open-telemetry/dotnet-contrib-approvers @srprash @lupengamzn
test/OpenTelemetry.Contrib.Instrumentation.ElasticsearchClient.Tests/ @open-telemetry/dotnet-contrib-approvers @ejsmith
test/OpenTelemetry.Contrib.Instrumentation.EntityFrameworkCoreTests/ @open-telemetry/dotnet-contrib-approvers
-test/OpenTelemetry.Contrib.Instrumentation.MassTransit.Tests/ @open-telemetry/dotnet-contrib-approvers @alexvaluyskiy
test/OpenTelemetry.Contrib.Instrumentation.GrpcCore.Tests/ @open-telemetry/dotnet-contrib-approvers @pcwiese
+test/OpenTelemetry.Contrib.Instrumentation.MassTransit.Tests/ @open-telemetry/dotnet-contrib-approvers @alexvaluyskiy
+test/OpenTelemetry.Contrib.Instrumentation.Owin.Tests/ @open-telemetry/dotnet-contrib-approvers @codeblanch
test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/ @open-telemetry/dotnet-contrib-approvers @codeblanch
test/OpenTelemetry.Contrib.Instrumentation.MySqlData.Tests/ @open-telemetry/dotnet-contrib-approvers @moonheart
test/OpenTelemetry.Contrib.Preview.Tests/ @open-telemetry/dotnet-contrib-approvers @codeblanch
diff --git a/build/Common.nonprod.props b/build/Common.nonprod.props
index b43fa19ccf..3dd5e62aa5 100644
--- a/build/Common.nonprod.props
+++ b/build/Common.nonprod.props
@@ -4,11 +4,6 @@
false
$(MSBuildThisFileDirectory)/OpenTelemetryContrib.test.ruleset
-
- true
- $(MSBuildThisFileDirectory)/debug.snk
- $(DefineConstants);SIGNED
- true
$(NoWarn),1574,1591
diff --git a/build/Common.prod.props b/build/Common.prod.props
index 41dc196e13..fa47e5e8d7 100644
--- a/build/Common.prod.props
+++ b/build/Common.prod.props
@@ -1,13 +1,6 @@
-
- true
- $(MSBuildThisFileDirectory)debug.snk
- $(DefineConstants);SIGNED
- true
-
-
git
https://github.com/open-telemetry/opentelemetry-dotnet-contrib
diff --git a/build/Common.props b/build/Common.props
index 00cea01585..275cfee36c 100644
--- a/build/Common.props
+++ b/build/Common.props
@@ -26,9 +26,10 @@
[16.7.1]
[2.1.0,5.0)
[1.0.0,2.0)
+ [4.1.1]
[3.3.2]
[1.0.0,2.0)
- [1.1.118,2.0)
+ [1.2.0-beta.354,2.0)
diff --git a/examples/owin/Controllers/TestController.cs b/examples/owin/Controllers/TestController.cs
new file mode 100644
index 0000000000..4b8e29eddc
--- /dev/null
+++ b/examples/owin/Controllers/TestController.cs
@@ -0,0 +1,30 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Web.Http;
+
+namespace Examples.Owin.Controllers
+{
+ public class TestController : ApiController
+ {
+ // GET api/test/{id}
+ public string Get(string id = null)
+ {
+ return $"id:{id}";
+ }
+ }
+}
diff --git a/examples/owin/Examples.Owin.csproj b/examples/owin/Examples.Owin.csproj
new file mode 100644
index 0000000000..0fbabb0964
--- /dev/null
+++ b/examples/owin/Examples.Owin.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net461
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/owin/Program.cs b/examples/owin/Program.cs
new file mode 100644
index 0000000000..9ee8b2ff03
--- /dev/null
+++ b/examples/owin/Program.cs
@@ -0,0 +1,88 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Diagnostics;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http;
+using Microsoft.Owin.Hosting;
+using OpenTelemetry;
+using OpenTelemetry.Resources;
+using OpenTelemetry.Trace;
+using Owin;
+
+namespace Examples.Owin
+{
+ internal static class Program
+ {
+ public static void Main()
+ {
+ using var host = WebApp.Start(
+ "http://localhost:9000",
+ appBuilder =>
+ {
+ // Add OpenTelemetry early in the pipeline to start timing
+ // the request as soon as possible.
+ appBuilder.UseOpenTelemetry();
+
+ HttpConfiguration config = new HttpConfiguration();
+
+ config.MessageHandlers.Add(new ActivityDisplayNameRouteEnrichingHandler());
+
+ config.Routes.MapHttpRoute(
+ name: "DefaultApi",
+ routeTemplate: "api/{controller}/{id}",
+ defaults: new { id = RouteParameter.Optional });
+
+ appBuilder.UseWebApi(config);
+ });
+
+ using var openTelemetry = Sdk.CreateTracerProviderBuilder()
+ .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Owin-Example"))
+ .AddOwinInstrumentation()
+ .AddConsoleExporter()
+ .Build();
+
+ Console.WriteLine("Service listening. Press enter to exit.");
+ Console.ReadLine();
+ }
+
+ private class ActivityDisplayNameRouteEnrichingHandler : DelegatingHandler
+ {
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ var activity = Activity.Current;
+ if (activity != null)
+ {
+ var routeData = request.GetRouteData();
+ if (routeData != null)
+ {
+ activity.DisplayName = routeData.Route.RouteTemplate;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/examples/owin/README.md b/examples/owin/README.md
new file mode 100644
index 0000000000..db8d94a0c8
--- /dev/null
+++ b/examples/owin/README.md
@@ -0,0 +1,7 @@
+# OWIN Instrumentation for OpenTelemetry .NET - Example
+
+An example application that shows how to use
+`OpenTelemetry.Contrib.Instrumentation.Owin` to capture telemetry from a
+self-hosted WebAPI .NET Framework service.
+
+Span names are set to the route template resolved by WebAPI.
diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln
index c78fe4d4ff..8cf3585a72 100644
--- a/opentelemetry-dotnet-contrib.sln
+++ b/opentelemetry-dotnet-contrib.sln
@@ -36,6 +36,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\package-Instrumentation.GrpcCore.yml = .github\workflows\package-Instrumentation.GrpcCore.yml
.github\workflows\package-Instrumentation.MassTransit.yml = .github\workflows\package-Instrumentation.MassTransit.yml
.github\workflows\package-Instrumentation.MySqlData.yml = .github\workflows\package-Instrumentation.MySqlData.yml
+ .github\workflows\package-Instrumentation.Owin.yml = .github\workflows\package-Instrumentation.Owin.yml
.github\workflows\package-Instrumentation.Wcf.yml = .github\workflows\package-Instrumentation.Wcf.yml
.github\workflows\package-Preview.yml = .github\workflows\package-Preview.yml
.github\workflows\pr_build.yml = .github\workflows\pr_build.yml
@@ -143,6 +144,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Contrib.Exten
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Contrib.Extensions.AzureMonitor.Tests", "test\OpenTelemetry.Contrib.Extensions.AzureMonitor.Tests\OpenTelemetry.Contrib.Extensions.AzureMonitor.Tests.csproj", "{771651AA-010F-4FF6-9CB2-BAFFE5E04FC2}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Contrib.Instrumentation.Owin", "src\OpenTelemetry.Contrib.Instrumentation.Owin\OpenTelemetry.Contrib.Instrumentation.Owin.csproj", "{530255C1-D130-4B43-981D-911E54F7C787}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "owin", "owin", "{8D11A34C-D0EF-4DE1-8230-32168E67044D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Owin", "examples\owin\Examples.Owin.csproj", "{6B3AA3F2-89A7-433F-918A-1E5E6AAF8423}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Contrib.Instrumentation.Owin.Tests", "test\OpenTelemetry.Contrib.Instrumentation.Owin.Tests\OpenTelemetry.Contrib.Instrumentation.Owin.Tests.csproj", "{D52558C8-B7BF-4F59-A0FA-9AA629E68012}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -273,6 +282,18 @@ Global
{771651AA-010F-4FF6-9CB2-BAFFE5E04FC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{771651AA-010F-4FF6-9CB2-BAFFE5E04FC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{771651AA-010F-4FF6-9CB2-BAFFE5E04FC2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {530255C1-D130-4B43-981D-911E54F7C787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {530255C1-D130-4B43-981D-911E54F7C787}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {530255C1-D130-4B43-981D-911E54F7C787}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {530255C1-D130-4B43-981D-911E54F7C787}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B3AA3F2-89A7-433F-918A-1E5E6AAF8423}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B3AA3F2-89A7-433F-918A-1E5E6AAF8423}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B3AA3F2-89A7-433F-918A-1E5E6AAF8423}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B3AA3F2-89A7-433F-918A-1E5E6AAF8423}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D52558C8-B7BF-4F59-A0FA-9AA629E68012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D52558C8-B7BF-4F59-A0FA-9AA629E68012}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D52558C8-B7BF-4F59-A0FA-9AA629E68012}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D52558C8-B7BF-4F59-A0FA-9AA629E68012}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -314,6 +335,10 @@ Global
{4172D671-3AAC-443F-9EB0-A6608B154AF0} = {2097345F-4DD3-477D-BC54-A922F9B2B402}
{9C2D6D1A-8580-4527-B718-E206D0690635} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63}
{771651AA-010F-4FF6-9CB2-BAFFE5E04FC2} = {2097345F-4DD3-477D-BC54-A922F9B2B402}
+ {530255C1-D130-4B43-981D-911E54F7C787} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63}
+ {8D11A34C-D0EF-4DE1-8230-32168E67044D} = {B75EE478-97F7-4E9F-9A5A-DB3D0988EDEA}
+ {6B3AA3F2-89A7-433F-918A-1E5E6AAF8423} = {8D11A34C-D0EF-4DE1-8230-32168E67044D}
+ {D52558C8-B7BF-4F59-A0FA-9AA629E68012} = {2097345F-4DD3-477D-BC54-A922F9B2B402}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B0816796-CDB3-47D7-8C3C-946434DE3B66}
diff --git a/src/OpenTelemetry.Contrib.Extensions.AWSXRay/AWSXRayIdGenerator.cs b/src/OpenTelemetry.Contrib.Extensions.AWSXRay/AWSXRayIdGenerator.cs
index 770fe9d18a..5e05d14436 100644
--- a/src/OpenTelemetry.Contrib.Extensions.AWSXRay/AWSXRayIdGenerator.cs
+++ b/src/OpenTelemetry.Contrib.Extensions.AWSXRay/AWSXRayIdGenerator.cs
@@ -179,7 +179,7 @@ private static ActivitySamplingResult ComputeRootActivitySamplingResult(
{
SamplingDecision.RecordAndSample => ActivitySamplingResult.AllDataAndRecorded,
SamplingDecision.RecordOnly => ActivitySamplingResult.AllData,
- _ => ActivitySamplingResult.PropagationData
+ _ => ActivitySamplingResult.PropagationData,
};
if (activitySamplingResult != ActivitySamplingResult.PropagationData)
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/AppBuilderExtensions.cs b/src/OpenTelemetry.Contrib.Instrumentation.Owin/AppBuilderExtensions.cs
new file mode 100644
index 0000000000..902cfe3286
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/AppBuilderExtensions.cs
@@ -0,0 +1,35 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Diagnostics;
+using OpenTelemetry.Contrib.Instrumentation.Owin;
+
+namespace Owin
+{
+ ///
+ /// Provides extension methods for the class.
+ ///
+ public static class AppBuilderExtensions
+ {
+ /// Adds a component to the OWIN pipeline for instrumenting
+ /// incoming request with and notifying listeners
+ /// with .
+ /// .
+ /// Supplied for chaining.
+ public static IAppBuilder UseOpenTelemetry(this IAppBuilder appBuilder)
+ => appBuilder.Use();
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/AssemblyInfo.cs b/src/OpenTelemetry.Contrib.Instrumentation.Owin/AssemblyInfo.cs
new file mode 100644
index 0000000000..f4836c41f8
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+
+#if SIGNED
+[assembly: InternalsVisibleTo("OpenTelemetry.Contrib.Instrumentation.Owin.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")]
+#else
+[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.Owin.Tests")]
+#endif
+
+[assembly: CLSCompliant(false)]
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/CHANGELOG.md b/src/OpenTelemetry.Contrib.Instrumentation.Owin/CHANGELOG.md
new file mode 100644
index 0000000000..134621e04d
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+## Unreleased
+
+* Initial release
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/Implementation/DiagnosticsMiddleware.cs b/src/OpenTelemetry.Contrib.Instrumentation.Owin/Implementation/DiagnosticsMiddleware.cs
new file mode 100644
index 0000000000..058500e658
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/Implementation/DiagnosticsMiddleware.cs
@@ -0,0 +1,224 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Microsoft.Owin;
+using OpenTelemetry.Context.Propagation;
+using OpenTelemetry.Trace;
+
+namespace OpenTelemetry.Contrib.Instrumentation.Owin
+{
+ ///
+ /// Instruments incoming request with and notifies listeners with .
+ ///
+ internal sealed class DiagnosticsMiddleware : OwinMiddleware
+ {
+ private const string ContextKey = "__OpenTelemetry.Context__";
+ private static readonly Func> OwinRequestHeaderValuesGetter
+ = (request, name) => request.Headers.GetValues(name);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// An optional pointer to the next component.
+ public DiagnosticsMiddleware(OwinMiddleware next)
+ : base(next)
+ {
+ }
+
+ ///
+ public override async Task Invoke(IOwinContext owinContext)
+ {
+ try
+ {
+ BeginRequest(owinContext);
+ await this.Next.Invoke(owinContext).ConfigureAwait(false);
+ RequestEnd(owinContext, null);
+ }
+ catch (Exception ex)
+ {
+ RequestEnd(owinContext, ex);
+ throw;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void BeginRequest(IOwinContext owinContext)
+ {
+ try
+ {
+ if (OwinInstrumentationActivitySource.Options == null || OwinInstrumentationActivitySource.Options.Filter?.Invoke(owinContext) == false)
+ {
+ OwinInstrumentationEventSource.Log.RequestIsFilteredOut();
+ return;
+ }
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ OwinInstrumentationEventSource.Log.RequestFilterException(ex);
+ return;
+ }
+
+ var textMapPropagator = Propagators.DefaultTextMapPropagator;
+ var ctx = textMapPropagator.Extract(default, owinContext.Request, OwinRequestHeaderValuesGetter);
+
+ Activity activity = OwinInstrumentationActivitySource.ActivitySource.StartActivity(
+ OwinInstrumentationActivitySource.IncomingRequestActivityName,
+ ActivityKind.Server,
+ ctx.ActivityContext);
+
+ if (activity != null)
+ {
+ var request = owinContext.Request;
+
+ /*
+ * Note: Display name is intentionally set to a low cardinality
+ * value because OWIN does not expose any kind of
+ * route/template. See:
+ * https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#name
+ */
+ activity.DisplayName = request.Method switch
+ {
+ "GET" => "HTTP GET",
+ "POST" => "HTTP POST",
+ "PUT" => "HTTP PUT",
+ "DELETE" => "HTTP DELETE",
+ _ => $"HTTP {request.Method}",
+ };
+
+ if (activity.IsAllDataRequested)
+ {
+ if (request.Uri.Port == 80 || request.Uri.Port == 443)
+ {
+ activity.SetTag(SemanticConventions.AttributeHttpHost, request.Uri.Host);
+ }
+ else
+ {
+ activity.SetTag(SemanticConventions.AttributeHttpHost, request.Uri.Host + ":" + request.Uri.Port);
+ }
+
+ activity.SetTag(SemanticConventions.AttributeHttpMethod, request.Method);
+ activity.SetTag(SemanticConventions.AttributeHttpTarget, request.Uri.AbsolutePath);
+ activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUriTagValueFromRequestUri(request.Uri));
+
+ if (request.Headers.TryGetValue("User-Agent", out string[] userAgent) && userAgent.Length > 0)
+ {
+ activity.SetTag(SemanticConventions.AttributeHttpUserAgent, userAgent[0]);
+ }
+
+ try
+ {
+ OwinInstrumentationActivitySource.Options.Enrich?.Invoke(
+ activity,
+ OwinEnrichEventType.BeginRequest,
+ owinContext,
+ null);
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ OwinInstrumentationEventSource.Log.EnrichmentException(ex);
+ }
+ }
+
+ if (!(textMapPropagator is TraceContextPropagator))
+ {
+ Baggage.Current = ctx.Baggage;
+ }
+
+ owinContext.Environment[ContextKey] = activity;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void RequestEnd(IOwinContext owinContext, Exception exception)
+ {
+ if (owinContext.Environment.TryGetValue(ContextKey, out object context)
+ && context is Activity activity)
+ {
+ if (Activity.Current != activity)
+ {
+ Activity.Current = activity;
+ }
+
+ if (activity.IsAllDataRequested)
+ {
+ var response = owinContext.Response;
+
+ if (exception != null)
+ {
+ activity.SetStatus(Status.Error);
+
+ if (OwinInstrumentationActivitySource.Options.RecordException)
+ {
+ activity.RecordException(exception);
+ }
+ }
+ else if (activity.GetStatus().StatusCode == StatusCode.Unset)
+ {
+ activity.SetStatus(SpanHelper.ResolveSpanStatusForHttpStatusCode(response.StatusCode));
+ }
+
+ activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode);
+
+ try
+ {
+ OwinInstrumentationActivitySource.Options.Enrich?.Invoke(
+ activity,
+ OwinEnrichEventType.EndRequest,
+ owinContext,
+ exception);
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ OwinInstrumentationEventSource.Log.EnrichmentException(ex);
+ }
+ }
+
+ activity.Stop();
+
+ if (!(Propagators.DefaultTextMapPropagator is TraceContextPropagator))
+ {
+ Baggage.Current = default;
+ }
+ }
+ }
+
+ ///
+ /// Gets the OpenTelemetry standard uri tag value for a span based on its request .
+ ///
+ /// .
+ /// Span uri value.
+ private static string GetUriTagValueFromRequestUri(Uri uri)
+ {
+ if (string.IsNullOrEmpty(uri.UserInfo))
+ {
+ return uri.ToString();
+ }
+
+ return string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment);
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/Implementation/OwinInstrumentationActivitySource.cs b/src/OpenTelemetry.Contrib.Instrumentation.Owin/Implementation/OwinInstrumentationActivitySource.cs
new file mode 100644
index 0000000000..60bfb6e358
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/Implementation/OwinInstrumentationActivitySource.cs
@@ -0,0 +1,33 @@
+//
+// 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;
+
+namespace OpenTelemetry.Contrib.Instrumentation.Owin
+{
+ internal static class OwinInstrumentationActivitySource
+ {
+ public const string ActivitySourceName = "OpenTelemetry.OWIN";
+ public const string IncomingRequestActivityName = ActivitySourceName + ".IncomingRequest";
+
+ private static readonly Version Version = typeof(OwinInstrumentationActivitySource).Assembly.GetName().Version;
+
+ public static ActivitySource ActivitySource { get; } = new ActivitySource(ActivitySourceName, Version.ToString());
+
+ public static OwinInstrumentationOptions Options { get; set; }
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/Implementation/OwinInstrumentationEventSource.cs b/src/OpenTelemetry.Contrib.Instrumentation.Owin/Implementation/OwinInstrumentationEventSource.cs
new file mode 100644
index 0000000000..021d137d90
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/Implementation/OwinInstrumentationEventSource.cs
@@ -0,0 +1,94 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Diagnostics.Tracing;
+using System.Globalization;
+using System.Threading;
+
+namespace OpenTelemetry.Contrib.Instrumentation.Owin
+{
+ ///
+ /// EventSource events emitted from the project.
+ ///
+ [EventSource(Name = "OpenTelemetry-Instrumentation-Owin")]
+ internal sealed class OwinInstrumentationEventSource : EventSource
+ {
+ public static OwinInstrumentationEventSource Log { get; } = new OwinInstrumentationEventSource();
+
+ [NonEvent]
+ public void RequestFilterException(Exception ex)
+ {
+ if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1)))
+ {
+ this.RequestFilterException(ToInvariantString(ex));
+ }
+ }
+
+ [Event(EventIds.RequestIsFilteredOut, Message = "Request is filtered out.", Level = EventLevel.Verbose)]
+ public void RequestIsFilteredOut()
+ {
+ this.WriteEvent(EventIds.RequestIsFilteredOut);
+ }
+
+ [Event(EventIds.RequestFilterException, Message = "InstrumentationFilter threw exception. Request will not be collected. Exception {0}.", Level = EventLevel.Error)]
+ public void RequestFilterException(string exception)
+ {
+ this.WriteEvent(EventIds.RequestFilterException, exception);
+ }
+
+ [NonEvent]
+ public void EnrichmentException(Exception exception)
+ {
+ if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1)))
+ {
+ this.EnrichmentException(ToInvariantString(exception));
+ }
+ }
+
+ [Event(EventIds.EnrichmentException, Message = "Enrichment threw exception. Exception {0}.", Level = EventLevel.Error)]
+ public void EnrichmentException(string exception)
+ {
+ this.WriteEvent(EventIds.EnrichmentException, exception);
+ }
+
+ ///
+ /// Returns a culture-independent string representation of the given object,
+ /// appropriate for diagnostics tracing.
+ ///
+ private static string ToInvariantString(Exception exception)
+ {
+ var originalUICulture = Thread.CurrentThread.CurrentUICulture;
+
+ try
+ {
+ Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
+ return exception.ToString();
+ }
+ finally
+ {
+ Thread.CurrentThread.CurrentUICulture = originalUICulture;
+ }
+ }
+
+ private class EventIds
+ {
+ public const int RequestIsFilteredOut = 1;
+ public const int RequestFilterException = 2;
+ public const int EnrichmentException = 3;
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/OpenTelemetry.Contrib.Instrumentation.Owin.csproj b/src/OpenTelemetry.Contrib.Instrumentation.Owin/OpenTelemetry.Contrib.Instrumentation.Owin.csproj
new file mode 100644
index 0000000000..86e437fbde
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/OpenTelemetry.Contrib.Instrumentation.Owin.csproj
@@ -0,0 +1,21 @@
+
+
+ net461
+ OpenTelemetry instrumentation for OWIN
+ $(PackageTags);distributed-tracing;OWIN
+ Instrumentation.Owin-
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/OwinEnrichEventType.cs b/src/OpenTelemetry.Contrib.Instrumentation.Owin/OwinEnrichEventType.cs
new file mode 100644
index 0000000000..9d5e3260c3
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/OwinEnrichEventType.cs
@@ -0,0 +1,36 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System.Diagnostics;
+
+namespace OpenTelemetry.Contrib.Instrumentation.Owin
+{
+ ///
+ /// Describes the possible events fired when enriching an .
+ ///
+ public enum OwinEnrichEventType
+ {
+ ///
+ /// Begin request.
+ ///
+ BeginRequest,
+
+ ///
+ /// End request.
+ ///
+ EndRequest,
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/OwinInstrumentationOptions.cs b/src/OpenTelemetry.Contrib.Instrumentation.Owin/OwinInstrumentationOptions.cs
new file mode 100644
index 0000000000..0739e587fd
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/OwinInstrumentationOptions.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.Diagnostics;
+using Microsoft.Owin;
+using OpenTelemetry.Context.Propagation;
+
+namespace OpenTelemetry.Contrib.Instrumentation.Owin
+{
+ ///
+ /// Options for requests instrumentation.
+ ///
+ public class OwinInstrumentationOptions
+ {
+ ///
+ /// Gets or sets a Filter function that determines whether or not to collect telemetry about requests on a per request basis.
+ /// The Filter gets the , and should return a boolean.
+ /// If Filter returns true, the request is collected.
+ /// If Filter returns false or throw exception, the request is filtered out.
+ ///
+ public Func Filter { get; set; }
+
+ ///
+ /// Gets or sets an action to enrich the created by the instrumentation.
+ ///
+ public Action Enrich { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the exception will be recorded as or not.
+ ///
+ ///
+ /// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md.
+ ///
+ public bool RecordException { get; set; }
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/README.md b/src/OpenTelemetry.Contrib.Instrumentation.Owin/README.md
new file mode 100644
index 0000000000..c60f2c6135
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/README.md
@@ -0,0 +1,114 @@
+# OWIN Instrumentation for OpenTelemetry .NET
+
+[![nuget](https://img.shields.io/nuget/v/OpenTelemetry.Contrib.Instrumentation.Own.svg)](https://www.nuget.org/packages/OpenTelemetry.Contrib.Instrumentation.Owin/)
+
+This is an [Instrumentation
+Library](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/glossary.md#instrumentation-library),
+which instruments [OWIN/Katana](https://github.com/aspnet/AspNetKatana/) and
+collects telemetry about incoming requests.
+
+## Steps to enable OpenTelemetry.Contrib.Instrumentation.Owin
+
+An example project is available in the
+[examples/owin](../../examples/owin/) folder.
+
+### Step 1: Install Package
+
+Add a reference to the
+[`OpenTelemetry.Contrib.Instrumentation.Owin`](https://www.nuget.org/packages/opentelemetry.contrib.instrumentation.owin)
+package. Also, add any other instrumentations & exporters you will need.
+
+```shell
+dotnet add package OpenTelemetry.Contrib.Instrumentation.Owin
+```
+
+### Step 2: Configure OWIN middleware
+
+Call the `UseOpenTelemetry` `IAppBuilder` extension to register OpenTelemetry
+middleware which emits diagnostic events from th OWIN pipeline. This should be
+done before any other middleware registrations.
+
+```csharp
+ using var host = WebApp.Start(
+ "http://localhost:9000",
+ appBuilder =>
+ {
+ appBuilder.UseOpenTelemetry();
+ });
+```
+
+### Step 3: Configure OpenTelemetry TracerProvider
+
+Call the `AddOwinInstrumentation` `TracerProviderBuilder` extension to register
+OpenTelemetry instrumentation which listens to the OWIN diagnostic events.
+
+```csharp
+ using var openTelemetry = Sdk.CreateTracerProviderBuilder()
+ .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Owin-Example"))
+ .AddOwinInstrumentation()
+ .AddConsoleExporter()
+ .Build();
+```
+
+## Customize OWIN span names
+
+The OpenTelemetry OWIN instrumentation will create spans with very generic names
+based on the http method of the request. For example: `HTTP GET` or `HTTP POST`.
+The reason for this is the [OpenTelemetry Specification http semantic
+conventions](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#name)
+call specifically for low cardinality values and OWIN does not expose any kind
+of route template.
+
+To change the span name set `Activity.Current.DisplayName` to the value you want
+to display once a route has been resolved. Here is how this can be done using WebAPI:
+
+```csharp
+ using var host = WebApp.Start(
+ "http://localhost:9000",
+ appBuilder =>
+ {
+ appBuilder.UseOpenTelemetry();
+
+ HttpConfiguration config = new HttpConfiguration();
+
+ config.MessageHandlers.Add(new ActivityDisplayNameRouteEnrichingHandler());
+
+ config.Routes.MapHttpRoute(
+ name: "DefaultApi",
+ routeTemplate: "api/{controller}/{id}",
+ defaults: new { id = RouteParameter.Optional });
+
+ appBuilder.UseWebApi(config);
+ });
+
+ private class ActivityDisplayNameRouteEnrichingHandler : DelegatingHandler
+ {
+ protected override async Task SendAsync(
+ HttpRequestMessage request,
+ CancellationToken cancellationToken)
+ {
+ try
+ {
+ return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ var activity = Activity.Current;
+ if (activity != null)
+ {
+ var routeData = request.GetRouteData();
+ if (routeData != null)
+ {
+ activity.DisplayName = routeData.Route.RouteTemplate;
+ }
+ }
+ }
+ }
+ }
+```
+
+## References
+
+* [Open Web Interface for .NET](http://owin.org/)
+* [Katana Project](https://github.com/aspnet/AspNetKatana/)
+* [OpenTelemetry Project](https://opentelemetry.io/)
diff --git a/src/OpenTelemetry.Contrib.Instrumentation.Owin/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Contrib.Instrumentation.Owin/TracerProviderBuilderExtensions.cs
new file mode 100644
index 0000000000..f34ecfa534
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Instrumentation.Owin/TracerProviderBuilderExtensions.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 OpenTelemetry.Contrib.Instrumentation.Owin;
+
+namespace OpenTelemetry.Trace
+{
+ ///
+ /// Extension methods to simplify registering of OWIN request instrumentation.
+ ///
+ public static class TracerProviderBuilderExtensions
+ {
+ ///
+ /// Enables the incoming requests automatic data collection for OWIN.
+ ///
+ /// being configured.
+ /// OWIN Request configuration options.
+ /// The instance of to chain the calls.
+ public static TracerProviderBuilder AddOwinInstrumentation(
+ this TracerProviderBuilder builder,
+ Action configureOwinInstrumentationOptions = null)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ var owinOptions = new OwinInstrumentationOptions();
+ configureOwinInstrumentationOptions?.Invoke(owinOptions);
+
+ OwinInstrumentationActivitySource.Options = owinOptions;
+
+ return builder.AddSource(OwinInstrumentationActivitySource.ActivitySourceName);
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Contrib.Instrumentation.Owin.Tests/Controllers/TestController.cs b/test/OpenTelemetry.Contrib.Instrumentation.Owin.Tests/Controllers/TestController.cs
new file mode 100644
index 0000000000..661126c47d
--- /dev/null
+++ b/test/OpenTelemetry.Contrib.Instrumentation.Owin.Tests/Controllers/TestController.cs
@@ -0,0 +1,30 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Web.Http;
+
+namespace OpenTelemetry.Contrib.Instrumentation.Owin.Tests.Controllers
+{
+ public class TestController : ApiController
+ {
+ // GET api/test/{id}
+ public string Get(string id = null)
+ {
+ return $"id:{id}";
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Contrib.Instrumentation.Owin.Tests/DiagnosticsMiddlewareTests.cs b/test/OpenTelemetry.Contrib.Instrumentation.Owin.Tests/DiagnosticsMiddlewareTests.cs
new file mode 100644
index 0000000000..858232aa5e
--- /dev/null
+++ b/test/OpenTelemetry.Contrib.Instrumentation.Owin.Tests/DiagnosticsMiddlewareTests.cs
@@ -0,0 +1,227 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http;
+using Microsoft.Owin;
+using Microsoft.Owin.Hosting;
+using OpenTelemetry.Trace;
+using Owin;
+using Xunit;
+
+namespace OpenTelemetry.Contrib.Instrumentation.Owin.Tests
+{
+ public class DiagnosticsMiddlewareTests : IDisposable
+ {
+ private readonly Uri serviceBaseUri;
+ private readonly IDisposable listener;
+ private readonly EventWaitHandle requestCompleteHandle = new(false, EventResetMode.AutoReset);
+
+ public DiagnosticsMiddlewareTests()
+ {
+ Random random = new Random();
+ var retryCount = 5;
+ while (retryCount > 0)
+ {
+ try
+ {
+ this.serviceBaseUri = new Uri($"http://localhost:{random.Next(2000, 5000)}/");
+
+ this.listener = WebApp.Start(
+ this.serviceBaseUri.ToString(),
+ appBuilder =>
+ {
+ appBuilder.Use((context, next) =>
+ {
+ try
+ {
+ return next();
+ }
+ finally
+ {
+ this.requestCompleteHandle?.Set();
+ }
+ });
+
+ appBuilder.UseOpenTelemetry();
+
+ appBuilder.Use((context, next) =>
+ {
+ if (context.Request.Path == new PathString("/exception"))
+ {
+ context.Response.StatusCode = 500;
+ throw new InvalidOperationException("Unhandled exception requested by caller.");
+ }
+
+ return next();
+ });
+
+ HttpConfiguration config = new HttpConfiguration();
+ config.Routes.MapHttpRoute(
+ name: "DefaultApi",
+ routeTemplate: "api/{controller}/{id}",
+ defaults: new { id = RouteParameter.Optional });
+
+ appBuilder.UseWebApi(config);
+ });
+ break;
+ }
+ catch
+ {
+ this.listener.Dispose();
+ this.listener = null;
+ retryCount--;
+ }
+ }
+
+ if (this.listener == null)
+ {
+ throw new InvalidOperationException("HttpListener could not be started.");
+ }
+ }
+
+ public void Dispose()
+ {
+ this.listener?.Dispose();
+ this.requestCompleteHandle?.Dispose();
+ }
+
+ [Theory]
+ [InlineData(true, false)]
+ [InlineData(true, true)]
+ [InlineData(false)]
+ [InlineData(true, false, true)]
+ [InlineData(true, false, true, true)]
+ [InlineData(true, false, false, false, true)]
+ [InlineData(true, false, false, false, true, true)]
+ public async Task OutgoingRequestInstrumentationTest(
+ bool instrument,
+ bool filter = false,
+ bool enrich = false,
+ bool enrichmentException = false,
+ bool generateRemoteException = false,
+ bool recordException = false)
+ {
+ List stoppedActivities = new List();
+
+ var builder = Sdk.CreateTracerProviderBuilder()
+ .AddInMemoryExporter(stoppedActivities);
+
+ if (instrument)
+ {
+ builder
+ .AddOwinInstrumentation(options =>
+ {
+ if (enrich)
+ {
+ if (!enrichmentException)
+ {
+ options.Enrich = (activity, eventName, context, exception) =>
+ {
+ switch (eventName)
+ {
+ case OwinEnrichEventType.BeginRequest:
+ activity.SetTag("client.beginrequest", nameof(OwinEnrichEventType.BeginRequest));
+ break;
+ case OwinEnrichEventType.EndRequest:
+ activity.SetTag("client.endrequest", nameof(OwinEnrichEventType.EndRequest));
+ break;
+ }
+ };
+ }
+ else
+ {
+ options.Enrich = (activity, eventName, context, exception) => throw new Exception("Error while enriching activity");
+ }
+ }
+
+ options.Filter = _ => !filter;
+ options.RecordException = recordException;
+ });
+ }
+
+ using TracerProvider tracerProvider = builder.Build();
+
+ using HttpClient client = new HttpClient();
+
+ Uri requestUri = generateRemoteException
+ ? new Uri($"{this.serviceBaseUri}exception")
+ : new Uri($"{this.serviceBaseUri}api/test");
+
+ this.requestCompleteHandle.Reset();
+
+ using var response = await client.GetAsync(requestUri).ConfigureAwait(false);
+
+ /* Note: This code will continue executing as soon as the response
+ is available but Owin could still be working. We need to wait until
+ Owin has finished to inspect the activity status. */
+
+ Assert.True(this.requestCompleteHandle.WaitOne(3000));
+
+ if (instrument)
+ {
+ if (!filter)
+ {
+ Assert.NotEmpty(stoppedActivities);
+ Assert.Single(stoppedActivities);
+
+ Activity activity = stoppedActivities[0];
+ Assert.Equal(OwinInstrumentationActivitySource.IncomingRequestActivityName, activity.OperationName);
+
+ Assert.Equal(requestUri.Host + ":" + requestUri.Port, activity.TagObjects.FirstOrDefault(t => t.Key == SemanticConventions.AttributeHttpHost).Value);
+ Assert.Equal("GET", activity.TagObjects.FirstOrDefault(t => t.Key == SemanticConventions.AttributeHttpMethod).Value);
+ Assert.Equal(requestUri.AbsolutePath, activity.TagObjects.FirstOrDefault(t => t.Key == SemanticConventions.AttributeHttpTarget).Value);
+ Assert.Equal(requestUri.ToString(), activity.TagObjects.FirstOrDefault(t => t.Key == SemanticConventions.AttributeHttpUrl).Value);
+
+ Assert.Equal(generateRemoteException ? 500 : 200, activity.TagObjects.FirstOrDefault(t => t.Key == SemanticConventions.AttributeHttpStatusCode).Value);
+ if (generateRemoteException)
+ {
+ Assert.Equal(Status.Error, activity.GetStatus());
+
+ if (recordException)
+ {
+ Assert.Contains(activity.Events, ae => ae.Name == SemanticConventions.AttributeExceptionEventName);
+ }
+ }
+ else
+ {
+ Assert.Equal(Status.Unset, activity.GetStatus());
+ }
+
+ if (enrich && !enrichmentException)
+ {
+ Assert.Equal(nameof(OwinEnrichEventType.BeginRequest), activity.TagObjects.Single(t => t.Key == "client.beginrequest").Value);
+ Assert.Equal(nameof(OwinEnrichEventType.EndRequest), activity.TagObjects.Single(t => t.Key == "client.endrequest").Value);
+ }
+ }
+ else
+ {
+ Assert.Empty(stoppedActivities);
+ }
+ }
+ else
+ {
+ Assert.Empty(stoppedActivities);
+ }
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Contrib.Instrumentation.Owin.Tests/OpenTelemetry.Contrib.Instrumentation.Owin.Tests.csproj b/test/OpenTelemetry.Contrib.Instrumentation.Owin.Tests/OpenTelemetry.Contrib.Instrumentation.Owin.Tests.csproj
new file mode 100644
index 0000000000..7b9472399a
--- /dev/null
+++ b/test/OpenTelemetry.Contrib.Instrumentation.Owin.Tests/OpenTelemetry.Contrib.Instrumentation.Owin.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ Unit test project for OpenTelemetry OWIN instrumentation
+ net461
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
+
+
+
+
+
+