5
5
using System . Web ;
6
6
using System . Web . Routing ;
7
7
using Datadog . Trace . ExtensionMethods ;
8
+ using Datadog . Trace . Logging ;
8
9
9
10
namespace Datadog . Trace . ClrProfiler . Integrations
10
11
{
11
12
/// <summary>
12
13
/// The ASP.NET MVC integration.
13
14
/// </summary>
14
- public sealed class AspNetMvcIntegration : IDisposable
15
+ public static class AspNetMvcIntegration
15
16
{
16
17
internal const string OperationName = "aspnet-mvc.request" ;
17
18
private const string HttpContextKey = "__Datadog.Trace.ClrProfiler.Integrations.AspNetMvcIntegration" ;
18
19
19
20
private static readonly Type ControllerContextType = Type . GetType ( "System.Web.Mvc.ControllerContext, System.Web.Mvc" , throwOnError : false ) ;
20
21
private static readonly Type RouteCollectionRouteType = Type . GetType ( "System.Web.Mvc.Routing.RouteCollectionRoute, System.Web.Mvc" , throwOnError : false ) ;
21
-
22
- private readonly HttpContextBase _httpContext ;
23
- private readonly Scope _scope ;
22
+ private static readonly ILog Log = LogProvider . GetLogger ( typeof ( AspNetMvcIntegration ) ) ;
24
23
25
24
/// <summary>
26
- /// Initializes a new instance of the <see cref="AspNetMvcIntegration"/> class .
25
+ /// Creates a scope used to instrument an MVC action and populates some common details .
27
26
/// </summary>
28
- /// <param name="controllerContextObj">The System.Web.Mvc.ControllerContext that was passed as an argument to the instrumented method.</param>
29
- public AspNetMvcIntegration ( object controllerContextObj )
27
+ /// <param name="controllerContext">The System.Web.Mvc.ControllerContext that was passed as an argument to the instrumented method.</param>
28
+ /// <returns>A new scope used to instrument an MVC action.</returns>
29
+ public static Scope CreateScope ( dynamic controllerContext )
30
30
{
31
- if ( controllerContextObj == null || ControllerContextType == null )
31
+ if ( ControllerContextType == null ||
32
+ controllerContext == null ||
33
+ ( ( object ) controllerContext ) ? . GetType ( ) != ControllerContextType )
32
34
{
33
35
// bail out early
34
- return ;
36
+ return null ;
35
37
}
36
38
39
+ Scope scope = null ;
40
+
37
41
try
38
42
{
39
- if ( controllerContextObj . GetType ( ) != ControllerContextType )
40
- {
41
- return ;
42
- }
43
-
44
- // access the controller context without referencing System.Web.Mvc directly
45
- dynamic controllerContext = controllerContextObj ;
43
+ var httpContext = controllerContext . HttpContext as HttpContextBase ;
46
44
47
- _httpContext = controllerContext . HttpContext ;
48
-
49
- if ( _httpContext == null )
45
+ if ( httpContext == null )
50
46
{
51
- return ;
47
+ return null ;
52
48
}
53
49
54
- string host = _httpContext . Request . Headers . Get ( "Host" ) ;
55
- string httpMethod = _httpContext . Request . HttpMethod . ToUpperInvariant ( ) ;
56
- string url = _httpContext . Request . RawUrl . ToLowerInvariant ( ) ;
50
+ string host = httpContext . Request . Headers . Get ( "Host" ) ;
51
+ string httpMethod = httpContext . Request . HttpMethod . ToUpperInvariant ( ) ;
52
+ string url = httpContext . Request . RawUrl . ToLowerInvariant ( ) ;
57
53
58
54
RouteData routeData = controllerContext . RouteData as RouteData ;
59
55
Route route = routeData ? . Route as Route ;
@@ -76,8 +72,11 @@ public AspNetMvcIntegration(object controllerContextObj)
76
72
string actionName = ( routeValues ? . GetValueOrDefault ( "action" ) as string ) ? . ToLowerInvariant ( ) ;
77
73
string resourceName = $ "{ httpMethod } { controllerName } .{ actionName } ";
78
74
79
- _scope = Tracer . Instance . StartActive ( OperationName ) ;
80
- Span span = _scope . Span ;
75
+ // extract distributed tracing values
76
+ var spanContext = httpContext . Request . Headers . Extract ( ) ;
77
+
78
+ scope = Tracer . Instance . StartActive ( OperationName , spanContext ) ;
79
+ Span span = scope . Span ;
81
80
span . Type = SpanTypes . Web ;
82
81
span . ResourceName = resourceName ;
83
82
span . SetTag ( Tags . HttpRequestHeadersHost , host ) ;
@@ -87,16 +86,18 @@ public AspNetMvcIntegration(object controllerContextObj)
87
86
span . SetTag ( Tags . AspNetController , controllerName ) ;
88
87
span . SetTag ( Tags . AspNetAction , actionName ) ;
89
88
}
90
- catch
89
+ catch ( Exception ex )
91
90
{
92
- // TODO: logging
91
+ Log . ErrorException ( "Error creating or populating scope." , ex ) ;
93
92
}
93
+
94
+ return scope ;
94
95
}
95
96
96
97
/// <summary>
97
- /// Wrapper method used to instrument System.Web.Mvc.Async.AsyncControllerActionInvoker .BeginInvokeAction().
98
+ /// Wrapper method used to instrument System.Web.Mvc.Async.IAsyncActionInvoker .BeginInvokeAction().
98
99
/// </summary>
99
- /// <param name="asyncControllerActionInvoker">The AsyncControllerActionInvoker instance.</param>
100
+ /// <param name="asyncControllerActionInvoker">The IAsyncActionInvoker instance.</param>
100
101
/// <param name="controllerContext">The ControllerContext for the current request.</param>
101
102
/// <param name="actionName">The name of the controller action.</param>
102
103
/// <param name="callback">An <see cref="AsyncCallback"/> delegate.</param>
@@ -113,37 +114,37 @@ public static object BeginInvokeAction(
113
114
dynamic callback ,
114
115
dynamic state )
115
116
{
116
- AspNetMvcIntegration integration = null ;
117
+ Scope scope = null ;
117
118
118
119
try
119
120
{
120
121
if ( HttpContext . Current != null )
121
122
{
122
- integration = new AspNetMvcIntegration ( ( object ) controllerContext ) ;
123
- HttpContext . Current . Items [ HttpContextKey ] = integration ;
123
+ scope = CreateScope ( controllerContext ) ;
124
+ HttpContext . Current . Items [ HttpContextKey ] = scope ;
124
125
}
125
126
}
126
- catch
127
+ catch ( Exception ex )
127
128
{
128
- // TODO: log this as an instrumentation error, but continue calling instrumented method
129
+ Log . ErrorException ( "Error instrumenting method {0}" , ex , "System.Web.Mvc.Async.IAsyncActionInvoker.BeginInvokeAction()" ) ;
129
130
}
130
131
131
132
try
132
133
{
133
- // call the original method, catching and rethrowing any unhandled exceptions
134
+ // call the original method, inspecting (but not catching) any unhandled exceptions
134
135
return asyncControllerActionInvoker . BeginInvokeAction ( controllerContext , actionName , callback , state ) ;
135
136
}
136
- catch ( Exception ex )
137
+ catch ( Exception ex ) when ( scope ? . Span . SetExceptionForFilter ( ex ) ?? false )
137
138
{
138
- integration ? . SetException ( ex ) ;
139
+ // unreachable code
139
140
throw ;
140
141
}
141
142
}
142
143
143
144
/// <summary>
144
- /// Wrapper method used to instrument System.Web.Mvc.Async.AsyncControllerActionInvoker .EndInvokeAction().
145
+ /// Wrapper method used to instrument System.Web.Mvc.Async.IAsyncActionInvoker .EndInvokeAction().
145
146
/// </summary>
146
- /// <param name="asyncControllerActionInvoker">The AsyncControllerActionInvoker instance.</param>
147
+ /// <param name="asyncControllerActionInvoker">The IAsyncActionInvoker instance.</param>
147
148
/// <param name="asyncResult">The <see cref="IAsyncResult"/> returned by <see cref="BeginInvokeAction"/>.</param>
148
149
/// <returns>Returns the <see cref="bool"/> returned by the original EndInvokeAction().</returns>
149
150
[ InterceptMethod (
@@ -152,66 +153,31 @@ public static object BeginInvokeAction(
152
153
TargetType = "System.Web.Mvc.Async.IAsyncActionInvoker" ) ]
153
154
public static bool EndInvokeAction ( dynamic asyncControllerActionInvoker , dynamic asyncResult )
154
155
{
155
- AspNetMvcIntegration integration = null ;
156
+ Scope scope = null ;
157
+ var httpContext = HttpContext . Current ;
156
158
157
159
try
158
160
{
159
- if ( HttpContext . Current != null )
160
- {
161
- integration = HttpContext . Current ? . Items [ HttpContextKey ] as AspNetMvcIntegration ;
162
- }
161
+ scope = httpContext ? . Items [ HttpContextKey ] as Scope ;
163
162
}
164
- catch
163
+ catch ( Exception ex )
165
164
{
166
- // TODO: log this as an instrumentation error, but continue calling instrumented method
165
+ Log . ErrorException ( "Error instrumenting method {0}" , ex , "System.Web.Mvc.Async.IAsyncActionInvoker.EndInvokeAction()" ) ;
167
166
}
168
167
169
168
try
170
169
{
171
- // call the original method, catching and rethrowing any unhandled exceptions
172
- return asyncControllerActionInvoker . EndInvokeAction ( asyncResult ) ;
170
+ // call the original method, inspecting (but not catching) any unhandled exceptions
171
+ return ( bool ) asyncControllerActionInvoker . EndInvokeAction ( asyncResult ) ;
173
172
}
174
- catch ( Exception ex )
173
+ catch ( Exception ex ) when ( scope ? . Span . SetExceptionForFilter ( ex ) ?? false )
175
174
{
176
- integration ? . SetException ( ex ) ;
175
+ // unreachable code
177
176
throw ;
178
177
}
179
178
finally
180
179
{
181
- integration ? . Dispose ( ) ;
182
- }
183
- }
184
-
185
- /// <summary>
186
- /// Tags the current span as an error. Called when an unhandled exception is thrown in the instrumented method.
187
- /// </summary>
188
- /// <param name="ex">The exception that was thrown and not handled in the instrumented method.</param>
189
- public void SetException ( Exception ex )
190
- {
191
- _scope ? . Span ? . SetException ( ex ) ;
192
- }
193
-
194
- /// <summary>
195
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
196
- /// </summary>
197
- public void Dispose ( )
198
- {
199
- try
200
- {
201
- // sometimes, if an exception was unhandled in user code, status code is set to 500 later in the pipeline,
202
- // so it is still 200 here. if there was an unhandled exception, always set status code to 500.
203
- if ( _scope ? . Span ? . Error == true )
204
- {
205
- _scope ? . Span ? . SetTag ( Tags . HttpStatusCode , "500" ) ;
206
- }
207
- else if ( _httpContext != null )
208
- {
209
- _scope ? . Span ? . SetTag ( Tags . HttpStatusCode , _httpContext . Response . StatusCode . ToString ( ) ) ;
210
- }
211
- }
212
- finally
213
- {
214
- _scope ? . Dispose ( ) ;
180
+ scope ? . Dispose ( ) ;
215
181
}
216
182
}
217
183
}
0 commit comments