Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge aspnet-telemetrycorrelation branch back to main #2233

Merged
merged 18 commits into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion OpenTelemetry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "customizing-the-sdk", "docs
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started", "docs\metrics\getting-started\getting-started.csproj", "{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Exporter.Prometheus", "src\OpenTelemetry.Exporter.Prometheus\OpenTelemetry.Exporter.Prometheus.csproj", "{52158A12-E7EF-45A1-859F-06F9B17410CB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Prometheus", "src\OpenTelemetry.Exporter.Prometheus\OpenTelemetry.Exporter.Prometheus.csproj", "{52158A12-E7EF-45A1-859F-06F9B17410CB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule", "src\OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule\OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj", "{F38E511B-1877-4E8A-8051-7879FC7DF8A4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests", "test\OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests\OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj", "{4D7201BC-7124-4401-AD65-FAB58A053D45}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -411,6 +415,14 @@ Global
{52158A12-E7EF-45A1-859F-06F9B17410CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52158A12-E7EF-45A1-859F-06F9B17410CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52158A12-E7EF-45A1-859F-06F9B17410CB}.Release|Any CPU.Build.0 = Release|Any CPU
{F38E511B-1877-4E8A-8051-7879FC7DF8A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F38E511B-1877-4E8A-8051-7879FC7DF8A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F38E511B-1877-4E8A-8051-7879FC7DF8A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F38E511B-1877-4E8A-8051-7879FC7DF8A4}.Release|Any CPU.Build.0 = Release|Any CPU
{4D7201BC-7124-4401-AD65-FAB58A053D45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D7201BC-7124-4401-AD65-FAB58A053D45}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D7201BC-7124-4401-AD65-FAB58A053D45}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D7201BC-7124-4401-AD65-FAB58A053D45}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
1 change: 0 additions & 1 deletion build/Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
<GrpcToolsPkgVer>[2.25.0,3.0)</GrpcToolsPkgVer>
<MicrosoftAspNetCoreHttpAbstractionsPkgVer>[2.1.1,6.0)</MicrosoftAspNetCoreHttpAbstractionsPkgVer>
<MicrosoftAspNetCoreHttpFeaturesPkgVer>[2.1.1,6.0)</MicrosoftAspNetCoreHttpFeaturesPkgVer>
<MicrosoftAspNetTelemetryCorrelationPkgVer>[1.0.7,2.0)</MicrosoftAspNetTelemetryCorrelationPkgVer>
<MicrosoftCodeAnalysisAnalyzersPkgVer>[3.3.1]</MicrosoftCodeAnalysisAnalyzersPkgVer>
<MicrosoftCodeCoveragePkgVer>[16.10.0]</MicrosoftCodeCoveragePkgVer>
<MicrosoftExtensionsHostingAbstractionsPkgVer>[2.1.0,)</MicrosoftExtensionsHostingAbstractionsPkgVer>
Expand Down
2 changes: 1 addition & 1 deletion examples/AspNet/Web.config
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0"/>
</handlers>
<modules>
<add name="TelemetryCorrelationHttpModule" type="Microsoft.AspNet.TelemetryCorrelation.TelemetryCorrelationHttpModule, Microsoft.AspNet.TelemetryCorrelation" preCondition="integratedMode,managedHandler"/>
<add name="TelemetryHttpModule" type="OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule, OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule" preCondition="integratedMode,managedHandler"/>
</modules>
</system.webServer>
<runtime>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
OpenTelemetry.Instrumentation.AspNet.ActivityExtensions
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Dispose() -> void
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Init(System.Web.HttpApplication context) -> void
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.ParseHeaders.get -> bool
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.ParseHeaders.set -> void
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.TelemetryHttpModule() -> void
static OpenTelemetry.Instrumentation.AspNet.ActivityExtensions.Extract(this System.Diagnostics.Activity activity, System.Collections.Specialized.NameValueCollection requestHeaders) -> bool
static OpenTelemetry.Instrumentation.AspNet.ActivityExtensions.TryParse(this System.Diagnostics.Activity activity, System.Collections.Specialized.NameValueCollection requestHeaders) -> bool
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// <copyright file="ActivityExtensions.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;

namespace OpenTelemetry.Instrumentation.AspNet
{
/// <summary>
/// Extensions of Activity class.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class ActivityExtensions
{
/// <summary>
/// Http header name to carry the Request Id: https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md.
/// </summary>
internal const string RequestIdHeaderName = "Request-Id";

/// <summary>
/// Http header name to carry the traceparent: https://www.w3.org/TR/trace-context/.
/// </summary>
internal const string TraceparentHeaderName = "traceparent";

/// <summary>
/// Http header name to carry the tracestate: https://www.w3.org/TR/trace-context/.
/// </summary>
internal const string TracestateHeaderName = "tracestate";

/// <summary>
/// Http header name to carry the correlation context.
/// </summary>
internal const string CorrelationContextHeaderName = "Correlation-Context";

/// <summary>
/// Maximum length of Correlation-Context header value.
/// </summary>
internal const int MaxCorrelationContextLength = 1024;

/// <summary>
/// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity.
/// </summary>
/// <param name="activity">Instance of activity that has not been started yet.</param>
/// <param name="requestHeaders">Request headers collection.</param>
/// <returns>true if request was parsed successfully, false - otherwise.</returns>
public static bool Extract(this Activity activity, NameValueCollection requestHeaders)
{
if (activity == null)
{
AspNetTelemetryEventSource.Log.ActvityExtractionError("activity is null");
return false;
}

if (activity.ParentId != null)
{
AspNetTelemetryEventSource.Log.ActvityExtractionError("ParentId is already set on activity");
return false;
}

if (activity.Id != null)
{
AspNetTelemetryEventSource.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
{
AspNetTelemetryEventSource.Log.HeaderParsingError(CorrelationContextHeaderName, pair);
}
}
}
}

return true;
}

return false;
}

/// <summary>
/// Reads Request-Id and Correlation-Context headers and sets ParentId and Baggage on Activity.
/// </summary>
/// <param name="activity">Instance of activity that has not been started yet.</param>
/// <param name="requestHeaders">Request headers collection.</param>
/// <returns>true if request was parsed successfully, false - otherwise.</returns>
[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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// <copyright file="ActivityHelper.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

using System;
using System.Collections;
using System.Diagnostics;
using System.Web;

namespace OpenTelemetry.Instrumentation.AspNet
{
/// <summary>
/// Activity helper class.
/// </summary>
internal static class ActivityHelper
{
/// <summary>
/// Listener name.
/// </summary>
public const string AspNetListenerName = "OpenTelemetry.Instrumentation.AspNet.Telemetry";

/// <summary>
/// Activity name for http request.
/// </summary>
public const string AspNetActivityName = "Microsoft.AspNet.HttpReqIn";

/// <summary>
/// Event name for the activity start event.
/// </summary>
public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start";

/// <summary>
/// Key to store the activity in HttpContext.
/// </summary>
public const string ActivityKey = "__AspnetActivity__";

private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName);

private static readonly object EmptyPayload = new object();

/// <summary>
/// Stops the activity and notifies listeners about it.
/// </summary>
/// <param name="contextItems">HttpContext.Items.</param>
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;
}

AspNetTelemetryEventSource.Log.ActivityStopped(currentActivity?.Id, currentActivity?.OperationName);
}

/// <summary>
/// Creates root (first level) activity that describes incoming request.
/// </summary>
/// <param name="context">Current HttpContext.</param>
/// <param name="parseHeaders">Determines if headers should be parsed get correlation ids.</param>
/// <returns>New root activity.</returns>
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;
AspNetTelemetryEventSource.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);
AspNetTelemetryEventSource.Log.ActivityException(aspNetActivity.Id, aspNetActivity.OperationName, exception);
}
}

/// <summary>
/// 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.
/// </summary>
/// <param name="contextItems">HttpContext.Items dictionary.</param>
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;
}
}
}
Loading