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 ;
45using System . Diagnostics ;
56using 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}
0 commit comments