Skip to content

Commit de3e627

Browse files
author
Liudmila Molkova
authored
Restore Activity with OnExecuteRequestStep where available (#24)
Restore Activity with OnExecuteRequestStep where available
1 parent 55e4680 commit de3e627

File tree

10 files changed

+333
-140
lines changed

10 files changed

+333
-140
lines changed

src/Microsoft.AspNet.TelemetryCorrelation/ActivityHelper.cs

Lines changed: 82 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System.Collections;
45
using System.Diagnostics;
56
using System.Web;
67

@@ -27,53 +28,35 @@ internal static class ActivityHelper
2728
public const string AspNetActivityStartName = "Microsoft.AspNet.HttpReqIn.Start";
2829

2930
/// <summary>
30-
/// Event name for the activity stop event.
31+
/// Event name for the lost activity stop event.
3132
/// </summary>
3233
public const string AspNetActivityLostStopName = "Microsoft.AspNet.HttpReqIn.ActivityLost.Stop";
3334

35+
/// <summary>
36+
/// Event name for the restored activity stop event.
37+
/// </summary>
38+
public const string AspNetActivityRestoredStopName = "Microsoft.AspNet.HttpReqIn.ActivityRestored.Stop";
39+
3440
/// <summary>
3541
/// Key to store the activity in HttpContext.
3642
/// </summary>
3743
public const string ActivityKey = "__AspnetActivity__";
3844

39-
private const int MaxActivityStackSize = 128;
40-
private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName);
41-
4245
/// <summary>
43-
/// It's possible that a request is executed in both native threads and managed threads,
44-
/// in such case Activity.Current will be lost during native thread and managed thread switch.
45-
/// This method is intended to restore the current activity in order to correlate the child
46-
/// activities with the root activity of the request.
46+
/// Key to store the restored activity in HttpContext.
4747
/// </summary>
48-
/// <param name="root">Root activity id for the current request.</param>
49-
/// <returns>If it returns an activity, it will be silently stopped with the parent activity</returns>
50-
public static Activity RestoreCurrentActivity(Activity root)
51-
{
52-
Debug.Assert(root != null);
53-
54-
// workaround to restore the root activity, because we don't
55-
// have a way to change the Activity.Current
56-
var childActivity = new Activity(root.OperationName);
57-
childActivity.SetParentId(root.Id);
58-
childActivity.SetStartTime(root.StartTimeUtc);
59-
foreach (var item in root.Baggage)
60-
{
61-
childActivity.AddBaggage(item.Key, item.Value);
62-
}
48+
public const string RestoredActivityKey = "__AspnetActivityRestored__";
6349

64-
childActivity.Start();
65-
66-
AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(childActivity.Id);
67-
return childActivity;
68-
}
50+
private const int MaxActivityStackSize = 128;
51+
private static readonly DiagnosticListener AspNetListener = new DiagnosticListener(AspNetListenerName);
6952

7053
/// <summary>
7154
/// Stops the activity and notifies listeners about it.
7255
/// </summary>
7356
/// <param name="activity">Activity to stop.</param>
74-
/// <param name="context">Current HttpContext.</param>
57+
/// <param name="contextItems">HttpContext.Items.</param>
7558
/// <returns>True if activity was found in the stack, false otherwise.</returns>
76-
public static bool StopAspNetActivity(Activity activity, HttpContext context)
59+
public static bool StopAspNetActivity(Activity activity, IDictionary contextItems)
7760
{
7861
var currentActivity = Activity.Current;
7962
if (activity != null && currentActivity != null)
@@ -87,11 +70,11 @@ public static bool StopAspNetActivity(Activity activity, HttpContext context)
8770

8871
if (newCurrentActivity == null)
8972
{
90-
break;
73+
return false;
9174
}
9275

9376
// there could be a case when request or any child activity is stopped
94-
// from the child execution context. In this case, Activity is present in the Current Stack,
77+
// from the child execution context. In this case, Activity is present in the Current Stack,
9578
// but is finished, i.e. stopping it has no effect on the Current.
9679
if (newCurrentActivity == currentActivity)
9780
{
@@ -116,13 +99,11 @@ public static bool StopAspNetActivity(Activity activity, HttpContext context)
11699
}
117100

118101
// if activity is in the stack, stop it with Stop event
119-
if (Activity.Current != null)
120-
{
121-
AspNetListener.StopActivity(Activity.Current, new { });
122-
RemoveCurrentActivity(context);
123-
AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(activity.Id);
124-
return true;
125-
}
102+
AspNetListener.StopActivity(currentActivity, new { });
103+
contextItems[ActivityKey] = null;
104+
105+
AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(currentActivity.Id, currentActivity.OperationName);
106+
return true;
126107
}
127108

128109
return false;
@@ -135,12 +116,21 @@ public static bool StopAspNetActivity(Activity activity, HttpContext context)
135116
/// <param name="context">Current HttpContext.</param>
136117
public static void StopLostActivity(Activity activity, HttpContext context)
137118
{
138-
if (activity != null)
139-
{
140-
AspNetListener.Write(AspNetActivityLostStopName, new { activity });
141-
RemoveCurrentActivity(context);
142-
AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(activity.Id, true);
143-
}
119+
context.Items[ActivityKey] = null;
120+
AspNetListener.Write(AspNetActivityLostStopName, new { activity });
121+
AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(activity.Id, AspNetActivityLostStopName);
122+
}
123+
124+
/// <summary>
125+
/// Notifies listeners that there the lost activity was lost during execution and there was an intermediate activity.
126+
/// </summary>
127+
/// <param name="activity">Activity to notify about.</param>
128+
/// <param name="context">Current HttpContext.</param>
129+
public static void StopRestoredActivity(Activity activity, HttpContext context)
130+
{
131+
context.Items[RestoredActivityKey] = null;
132+
AspNetListener.Write(AspNetActivityRestoredStopName, new { Activity = activity });
133+
AspNetTelemetryCorrelationEventSource.Log.ActivityStopped(activity.Id, AspNetActivityRestoredStopName);
144134
}
145135

146136
/// <summary>
@@ -152,12 +142,12 @@ public static Activity CreateRootActivity(HttpContext context)
152142
{
153143
if (AspNetListener.IsEnabled() && AspNetListener.IsEnabled(AspNetActivityName))
154144
{
155-
var rootActivity = new Activity(ActivityHelper.AspNetActivityName);
145+
var rootActivity = new Activity(AspNetActivityName);
156146

157147
rootActivity.Extract(context.Request.Unvalidated.Headers);
158148
if (StartAspNetActivity(rootActivity))
159149
{
160-
SaveCurrentActivity(context, rootActivity);
150+
context.Items[ActivityKey] = rootActivity;
161151
AspNetTelemetryCorrelationEventSource.Log.ActivityStarted(rootActivity.Id);
162152
return rootActivity;
163153
}
@@ -167,16 +157,56 @@ public static Activity CreateRootActivity(HttpContext context)
167157
}
168158

169159
/// <summary>
170-
/// This should be called after the Activity starts and only for root activity of a request.
160+
/// Saves activity in the HttpContext.Items.
171161
/// </summary>
172-
/// <param name="context">Context to save context to.</param>
162+
/// <param name="contextItems">Context to save context to.</param>
163+
/// <param name="key">Slot name.</param>
173164
/// <param name="activity">Activity to save.</param>
174-
internal static void SaveCurrentActivity(HttpContext context, Activity activity)
165+
internal static void SaveCurrentActivity(IDictionary contextItems, string key, Activity activity)
175166
{
176-
Debug.Assert(context != null);
167+
Debug.Assert(contextItems != null);
177168
Debug.Assert(activity != null);
178169

179-
context.Items[ActivityKey] = activity;
170+
contextItems[key] = activity;
171+
}
172+
173+
/// <summary>
174+
/// It's possible that a request is executed in both native threads and managed threads,
175+
/// in such case Activity.Current will be lost during native thread and managed thread switch.
176+
/// This method is intended to restore the current activity in order to correlate the child
177+
/// activities with the root activity of the request.
178+
/// </summary>
179+
/// <param name="contextItems">HttpContext.Items dictionary.</param>
180+
internal static void RestoreActivityIfNeeded(IDictionary contextItems)
181+
{
182+
if (Activity.Current == null)
183+
{
184+
var rootActivity = (Activity)contextItems[ActivityKey];
185+
if (rootActivity != null && !contextItems.Contains(RestoredActivityKey))
186+
{
187+
contextItems[RestoredActivityKey] = RestoreActivity(rootActivity);
188+
}
189+
}
190+
}
191+
192+
private static Activity RestoreActivity(Activity root)
193+
{
194+
Debug.Assert(root != null);
195+
196+
// workaround to restore the root activity, because we don't
197+
// have a way to change the Activity.Current
198+
var childActivity = new Activity(root.OperationName);
199+
childActivity.SetParentId(root.Id);
200+
childActivity.SetStartTime(root.StartTimeUtc);
201+
foreach (var item in root.Baggage)
202+
{
203+
childActivity.AddBaggage(item.Key, item.Value);
204+
}
205+
206+
childActivity.Start();
207+
208+
AspNetTelemetryCorrelationEventSource.Log.ActivityRestored(childActivity.Id);
209+
return childActivity;
180210
}
181211

182212
private static bool StartAspNetActivity(Activity activity)
@@ -197,11 +227,5 @@ private static bool StartAspNetActivity(Activity activity)
197227

198228
return false;
199229
}
200-
201-
private static void RemoveCurrentActivity(HttpContext context)
202-
{
203-
Debug.Assert(context != null);
204-
context.Items[ActivityKey] = null;
205-
}
206230
}
207231
}

src/Microsoft.AspNet.TelemetryCorrelation/AspNetTelemetryCorrelationEventSource.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ public void ActivityStarted(string id)
2929
WriteEvent(2, id);
3030
}
3131

32-
[Event(3, Message = "Activity stopped, Id='{0}', lost {1}", Level = EventLevel.Verbose)]
33-
public void ActivityStopped(string id, bool lost = false)
32+
[Event(3, Message = "Activity stopped, Id='{0}', Name='{1}'", Level = EventLevel.Verbose)]
33+
public void ActivityStopped(string id, string eventName)
3434
{
35-
WriteEvent(3, id, lost);
35+
WriteEvent(3, id, eventName);
3636
}
3737

3838
[Event(4, Message = "Failed to parse header '{0}', value: '{1}'", Level = EventLevel.Informational)]
@@ -58,6 +58,12 @@ public void ActivityStackIsTooDeep(string id, string name)
5858
{
5959
WriteEvent(7, id, name);
6060
}
61+
62+
[Event(8, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)]
63+
public void ActivityRestored(string id)
64+
{
65+
WriteEvent(8, id);
66+
}
6167
}
6268
}
6369
#pragma warning restore SA1600 // Elements must be documented

src/Microsoft.AspNet.TelemetryCorrelation/TelemetryCorrelationHttpModule.cs

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Diagnostics;
6+
using System.Reflection;
67
using System.Web;
78

89
namespace Microsoft.AspNet.TelemetryCorrelation
@@ -13,6 +14,12 @@ namespace Microsoft.AspNet.TelemetryCorrelation
1314
public class TelemetryCorrelationHttpModule : IHttpModule
1415
{
1516
private const string BeginCalledFlag = "Microsoft.AspNet.TelemetryCorrelation.BeginCalled";
17+
private static MethodInfo onStepMethodInfo = null;
18+
19+
static TelemetryCorrelationHttpModule()
20+
{
21+
onStepMethodInfo = typeof(HttpApplication).GetMethod("OnExecuteRequestStep");
22+
}
1623

1724
/// <inheritdoc />
1825
public void Dispose()
@@ -24,7 +31,40 @@ public void Init(HttpApplication context)
2431
{
2532
context.BeginRequest += Application_BeginRequest;
2633
context.EndRequest += Application_EndRequest;
27-
context.PreRequestHandlerExecute += Application_PreRequestHandlerExecute;
34+
35+
// OnExecuteRequestStep is availabile starting with 4.7.1
36+
// If this is executed in 4.7.1 runtime (regardless of targeted .NET version),
37+
// we will use it to restore lost activity, otherwise keep PreRequestHandlerExecute
38+
if (onStepMethodInfo != null)
39+
{
40+
onStepMethodInfo.Invoke(context, new object[] { (Action<HttpContextBase, Action>)OnExecuteRequestStep });
41+
}
42+
else
43+
{
44+
context.PreRequestHandlerExecute += Application_PreRequestHandlerExecute;
45+
}
46+
}
47+
48+
/// <summary>
49+
/// Restores Activity before each pipeline step if it was lost.
50+
/// </summary>
51+
/// <param name="context">HttpContext instance.</param>
52+
/// <param name="step">Step to be executed.</param>
53+
internal void OnExecuteRequestStep(HttpContextBase context, Action step)
54+
{
55+
// Once we have public Activity.Current setter (https://github.com/dotnet/corefx/issues/29207) this method will be
56+
// simplified to just assign Current if is was lost.
57+
// In the mean time, we are creating child Activity to restore the context. We have to send
58+
// event with this Activity to tracing system. It created a lot of issues for listeners as
59+
// we may potentially have a lot of them for different stages.
60+
// To reduce amount of events, we only care about ExecuteRequestHandler stage - restore activity here and
61+
// stop/report it to tracing system in EndRequest.
62+
if (context.CurrentNotification == RequestNotification.ExecuteRequestHandler && !context.IsPostNotification)
63+
{
64+
ActivityHelper.RestoreActivityIfNeeded(context.Items);
65+
}
66+
67+
step();
2868
}
2969

3070
private void Application_BeginRequest(object sender, EventArgs e)
@@ -38,13 +78,7 @@ private void Application_BeginRequest(object sender, EventArgs e)
3878
private void Application_PreRequestHandlerExecute(object sender, EventArgs e)
3979
{
4080
AspNetTelemetryCorrelationEventSource.Log.TraceCallback("Application_PreRequestHandlerExecute");
41-
var context = ((HttpApplication)sender).Context;
42-
43-
var rootActivity = (Activity)context.Items[ActivityHelper.ActivityKey];
44-
if (Activity.Current == null && rootActivity != null)
45-
{
46-
ActivityHelper.RestoreCurrentActivity(rootActivity);
47-
}
81+
ActivityHelper.RestoreActivityIfNeeded(((HttpApplication)sender).Context.Items);
4882
}
4983

5084
private void Application_EndRequest(object sender, EventArgs e)
@@ -59,16 +93,25 @@ private void Application_EndRequest(object sender, EventArgs e)
5993
{
6094
// Activity has never been started
6195
var activity = ActivityHelper.CreateRootActivity(context);
62-
ActivityHelper.StopAspNetActivity(activity, context);
96+
ActivityHelper.StopAspNetActivity(activity, context.Items);
6397
}
6498
else
6599
{
66100
var activity = (Activity)context.Items[ActivityHelper.ActivityKey];
67101

68102
// try to stop activity if it's in the Current stack
69-
if (!ActivityHelper.StopAspNetActivity(activity, context))
103+
// stop all running Activities on the way
104+
if (!ActivityHelper.StopAspNetActivity(activity, context.Items))
70105
{
71-
// Activity we created was lost, let's report it
106+
// perhaps we attempted to restore the Activity before
107+
var restoredActivity = (Activity)context.Items[ActivityHelper.RestoredActivityKey];
108+
if (restoredActivity != null)
109+
{
110+
// if so, report it
111+
ActivityHelper.StopRestoredActivity(restoredActivity, context);
112+
}
113+
114+
// Activity we created was lost let's report it
72115
if (activity != null)
73116
{
74117
ActivityHelper.StopLostActivity(activity, context);

0 commit comments

Comments
 (0)