Skip to content

Commit 8529387

Browse files
[2.0] Add modified WCF CallTarget instrumentation via opt-in environment variable (#2041)
* Port changes from #1992 into the release/2.0 branch. Additional changes include: - Move CreateScope method from ChannelHandlerIntegration.cs to WcfCommon.cs - Instead of breaking the CallTarget native structures (causing the original PR to be reverted for 1.30.1), use the new CallTarget ByRef instrumentation to instrument WCF methods which have out parameters (thanks Tony!) - Remove testing of CallSite codepaths in WcfTests
1 parent 5696ae1 commit 8529387

File tree

37 files changed

+2165
-225
lines changed

37 files changed

+2165
-225
lines changed

tracer/src/Datadog.Trace.ClrProfiler.Native/calltarget_tokens.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ HRESULT CallTargetTokens::ModifyLocalSigAndInitialize(void* rewriterWrapperPtr,
874874

875875
HRESULT CallTargetTokens::WriteBeginMethod(void* rewriterWrapperPtr, mdTypeRef integrationTypeRef,
876876
const TypeInfo* currentType,
877-
std::vector<FunctionMethodArgument>& methodArguments, ILInstr** instruction)
877+
const std::vector<FunctionMethodArgument>& methodArguments, ILInstr** instruction)
878878
{
879879
auto hr = EnsureBaseCalltargetTokens();
880880
if (FAILED(hr))

tracer/src/Datadog.Trace.ClrProfiler.Native/calltarget_tokens.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class CallTargetTokens
8787
mdToken* callTargetReturnToken, ILInstr** firstInstruction);
8888

8989
HRESULT WriteBeginMethod(void* rewriterWrapperPtr, mdTypeRef integrationTypeRef, const TypeInfo* currentType,
90-
std::vector<FunctionMethodArgument>& methodArguments, ILInstr** instruction);
90+
const std::vector<FunctionMethodArgument>& methodArguments, ILInstr** instruction);
9191

9292
HRESULT WriteEndVoidReturnMemberRef(void* rewriterWrapperPtr, mdTypeRef integrationTypeRef,
9393
const TypeInfo* currentType, ILInstr** instruction);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// <copyright file="AsyncMethodInvoker_InvokeBegin_Integration.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#if NETFRAMEWORK
7+
using System;
8+
using System.ComponentModel;
9+
using System.Reflection;
10+
using Datadog.Trace.ClrProfiler.CallTarget;
11+
using Datadog.Trace.ClrProfiler.Emit;
12+
using Datadog.Trace.Configuration;
13+
14+
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Wcf
15+
{
16+
/// <summary>
17+
/// System.ServiceModel.Dispatcher.AsyncMethodInvoker calltarget instrumentation
18+
/// </summary>
19+
[InstrumentMethod(
20+
AssemblyName = "System.ServiceModel",
21+
TypeName = "System.ServiceModel.Dispatcher.AsyncMethodInvoker",
22+
MethodName = "InvokeBegin",
23+
ReturnTypeName = ClrNames.IAsyncResult,
24+
ParameterTypeNames = new[] { ClrNames.Object, "System.Object[]", ClrNames.AsyncCallback, ClrNames.Object },
25+
MinimumVersion = "4.0.0",
26+
MaximumVersion = "4.*.*",
27+
IntegrationName = WcfCommon.IntegrationName)]
28+
[Browsable(false)]
29+
[EditorBrowsable(EditorBrowsableState.Never)]
30+
public class AsyncMethodInvoker_InvokeBegin_Integration
31+
{
32+
/// <summary>
33+
/// OnMethodBegin callback
34+
/// </summary>
35+
/// <typeparam name="TTarget">Type of the target</typeparam>
36+
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param>
37+
/// <param name="instanceArg">RequestContext instance</param>
38+
/// <param name="inputs">Input arguments</param>
39+
/// <param name="callback">Callback argument</param>
40+
/// <param name="state">State argument</param>
41+
/// <returns>Calltarget state value</returns>
42+
public static CallTargetState OnMethodBegin<TTarget>(TTarget instance, object instanceArg, object[] inputs, AsyncCallback callback, object state)
43+
{
44+
// TODO Just use the OperationContext.Current object to get the span information
45+
// context.IncomingMessageHeaders contains:
46+
// - Action
47+
// - To
48+
//
49+
// context.IncomingMessageProperties contains:
50+
// - ["httpRequest"] key to find distributed tracing headers
51+
if (!Tracer.Instance.Settings.IsIntegrationEnabled(WcfCommon.IntegrationId) || !Tracer.Instance.Settings.DelayWcfInstrumentationEnabled || WcfCommon.GetCurrentOperationContext is null)
52+
{
53+
return CallTargetState.GetDefault();
54+
}
55+
56+
var requestContext = WcfCommon.GetCurrentOperationContext()?.GetProperty<object>("RequestContext").GetValueOrDefault();
57+
return new CallTargetState(WcfCommon.CreateScope(requestContext));
58+
}
59+
}
60+
}
61+
#endif
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// <copyright file="AsyncMethodInvoker_InvokeEnd_Integration.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#if NETFRAMEWORK
7+
using System;
8+
using System.ComponentModel;
9+
using System.Reflection;
10+
using Datadog.Trace.ClrProfiler.CallTarget;
11+
using Datadog.Trace.Configuration;
12+
13+
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Wcf
14+
{
15+
/// <summary>
16+
/// System.ServiceModel.Dispatcher.AsyncMethodInvoker calltarget instrumentation
17+
/// </summary>
18+
[InstrumentMethod(
19+
AssemblyName = "System.ServiceModel",
20+
TypeName = "System.ServiceModel.Dispatcher.AsyncMethodInvoker",
21+
MethodName = "InvokeEnd",
22+
ReturnTypeName = ClrNames.Object,
23+
ParameterTypeNames = new[] { ClrNames.Object, "System.Object[]&", ClrNames.IAsyncResult },
24+
MinimumVersion = "4.0.0",
25+
MaximumVersion = "4.*.*",
26+
IntegrationName = WcfCommon.IntegrationName)]
27+
[Browsable(false)]
28+
[EditorBrowsable(EditorBrowsableState.Never)]
29+
public class AsyncMethodInvoker_InvokeEnd_Integration
30+
{
31+
/// <summary>
32+
/// OnMethodEnd callback
33+
/// </summary>
34+
/// <typeparam name="TTarget">Type of the target</typeparam>
35+
/// <typeparam name="TReturn">Type of the response</typeparam>
36+
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param>
37+
/// <param name="returnValue">Return value</param>
38+
/// <param name="exception">Exception instance in case the original code threw an exception.</param>
39+
/// <param name="state">Calltarget state value</param>
40+
/// <returns>A response value, in an async scenario will be T of Task of T</returns>
41+
public static CallTargetReturn<TReturn> OnMethodEnd<TTarget, TReturn>(TTarget instance, TReturn returnValue, Exception exception, CallTargetState state)
42+
{
43+
var scope = Tracer.Instance.ActiveScope;
44+
scope.DisposeWithException(exception);
45+
return new CallTargetReturn<TReturn>(returnValue);
46+
}
47+
}
48+
}
49+
#endif

tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Wcf/ChannelHandlerIntegration.cs

Lines changed: 6 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,6 @@ namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Wcf
3535
public class ChannelHandlerIntegration
3636
{
3737
private const string IntegrationName = nameof(Configuration.IntegrationId.Wcf);
38-
private const string ChannelHandlerTypeName = "System.ServiceModel.Dispatcher.ChannelHandler";
39-
private const string HttpRequestMessagePropertyTypeName = "System.ServiceModel.Channels.HttpRequestMessageProperty";
40-
41-
private const IntegrationId IntegrationId = Configuration.IntegrationId.Wcf;
42-
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(ChannelHandlerIntegration));
4338

4439
/// <summary>
4540
/// OnMethodBegin callback
@@ -53,7 +48,12 @@ public class ChannelHandlerIntegration
5348
/// <returns>Calltarget state value</returns>
5449
public static CallTargetState OnMethodBegin<TTarget, TRequestContext, TOperationContext>(TTarget instance, TRequestContext request, TOperationContext currentOperationContext)
5550
{
56-
return new CallTargetState(CreateScope(request));
51+
if (Tracer.Instance.Settings.DelayWcfInstrumentationEnabled)
52+
{
53+
return CallTargetState.GetDefault();
54+
}
55+
56+
return new CallTargetState(WcfCommon.CreateScope(request));
5757
}
5858

5959
/// <summary>
@@ -71,85 +71,6 @@ public static CallTargetReturn<TReturn> OnMethodEnd<TTarget, TReturn>(TTarget in
7171
state.Scope.DisposeWithException(exception);
7272
return new CallTargetReturn<TReturn>(returnValue);
7373
}
74-
75-
private static Scope CreateScope(object requestContext)
76-
{
77-
var requestMessage = requestContext.GetProperty<object>("RequestMessage").GetValueOrDefault();
78-
79-
if (requestMessage == null)
80-
{
81-
return null;
82-
}
83-
84-
var tracer = Tracer.Instance;
85-
86-
if (!tracer.Settings.IsIntegrationEnabled(IntegrationId))
87-
{
88-
// integration disabled, don't create a scope, skip this trace
89-
return null;
90-
}
91-
92-
Scope scope = null;
93-
94-
try
95-
{
96-
SpanContext propagatedContext = null;
97-
var tagsFromHeaders = Enumerable.Empty<KeyValuePair<string, string>>();
98-
string host = null;
99-
string httpMethod = null;
100-
101-
IDictionary<string, object> requestProperties = requestMessage.GetProperty<IDictionary<string, object>>("Properties").GetValueOrDefault();
102-
if (requestProperties.TryGetValue("httpRequest", out object httpRequestProperty) &&
103-
httpRequestProperty.GetType().FullName.Equals(HttpRequestMessagePropertyTypeName, StringComparison.OrdinalIgnoreCase))
104-
{
105-
var webHeaderCollection = httpRequestProperty.GetProperty<WebHeaderCollection>("Headers").GetValueOrDefault();
106-
107-
// we're using an http transport
108-
host = webHeaderCollection[HttpRequestHeader.Host];
109-
httpMethod = httpRequestProperty.GetProperty<string>("Method").GetValueOrDefault()?.ToUpperInvariant();
110-
111-
// try to extract propagated context values from http headers
112-
if (tracer.ActiveScope == null)
113-
{
114-
try
115-
{
116-
var headers = webHeaderCollection.Wrap();
117-
propagatedContext = SpanContextPropagator.Instance.Extract(headers);
118-
tagsFromHeaders = SpanContextPropagator.Instance.ExtractHeaderTags(headers, tracer.Settings.HeaderTags, SpanContextPropagator.HttpRequestHeadersTagPrefix);
119-
}
120-
catch (Exception ex)
121-
{
122-
Log.Error(ex, "Error extracting propagated HTTP headers.");
123-
}
124-
}
125-
}
126-
127-
var tags = new WebTags();
128-
scope = tracer.StartActiveWithTags("wcf.request", propagatedContext, tags: tags);
129-
var span = scope.Span;
130-
131-
object requestHeaders = requestMessage.GetProperty<object>("Headers").GetValueOrDefault();
132-
string action = requestHeaders.GetProperty<string>("Action").GetValueOrDefault();
133-
Uri requestHeadersTo = requestHeaders.GetProperty<Uri>("To").GetValueOrDefault();
134-
135-
span.DecorateWebServerSpan(
136-
resourceName: action ?? requestHeadersTo?.LocalPath,
137-
httpMethod,
138-
host,
139-
httpUrl: requestHeadersTo?.AbsoluteUri,
140-
tags,
141-
tagsFromHeaders);
142-
143-
tags.SetAnalyticsSampleRate(IntegrationId, tracer.Settings, enabledWithGlobalSetting: true);
144-
}
145-
catch (Exception ex)
146-
{
147-
Log.Error(ex, "Error creating or populating scope.");
148-
}
149-
150-
// always returns the scope, even if it's null
151-
return scope;
152-
}
15374
}
15475
}
15576
#endif
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// <copyright file="SyncMethodInvokerIntegration.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#if NETFRAMEWORK
7+
using System;
8+
using System.ComponentModel;
9+
using System.Reflection;
10+
using Datadog.Trace.ClrProfiler.CallTarget;
11+
using Datadog.Trace.ClrProfiler.Emit;
12+
using Datadog.Trace.Configuration;
13+
14+
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Wcf
15+
{
16+
/// <summary>
17+
/// System.ServiceModel.Dispatcher.SyncMethodInvoker calltarget instrumentation
18+
/// </summary>
19+
[InstrumentMethod(
20+
AssemblyName = "System.ServiceModel",
21+
TypeName = "System.ServiceModel.Dispatcher.SyncMethodInvoker",
22+
MethodName = "Invoke",
23+
ReturnTypeName = ClrNames.Object,
24+
ParameterTypeNames = new[] { ClrNames.Object, "System.Object[]", "System.Object[]&" },
25+
MinimumVersion = "4.0.0",
26+
MaximumVersion = "4.*.*",
27+
IntegrationName = WcfCommon.IntegrationName)]
28+
[Browsable(false)]
29+
[EditorBrowsable(EditorBrowsableState.Never)]
30+
public class SyncMethodInvokerIntegration
31+
{
32+
/// <summary>
33+
/// OnMethodBegin callback
34+
/// </summary>
35+
/// <typeparam name="TTarget">Type of the target</typeparam>
36+
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param>
37+
/// <param name="instanceArg">RequestContext instance</param>
38+
/// <param name="inputs">Input arguments</param>
39+
/// <param name="outputs">Output arguments</param>
40+
/// <returns>Calltarget state value</returns>
41+
public static CallTargetState OnMethodBegin<TTarget>(TTarget instance, object instanceArg, object[] inputs, ref object[] outputs)
42+
{
43+
// TODO Just use the OperationContext.Current object to get the span information
44+
// context.IncomingMessageHeaders contains:
45+
// - Action
46+
// - To
47+
//
48+
// context.IncomingMessageProperties contains:
49+
// - ["httpRequest"] key to find distributed tracing headers
50+
if (!Tracer.Instance.Settings.IsIntegrationEnabled(WcfCommon.IntegrationId) || !Tracer.Instance.Settings.DelayWcfInstrumentationEnabled || WcfCommon.GetCurrentOperationContext is null)
51+
{
52+
return CallTargetState.GetDefault();
53+
}
54+
55+
var requestContext = WcfCommon.GetCurrentOperationContext()?.GetProperty<object>("RequestContext").GetValueOrDefault();
56+
return new CallTargetState(WcfCommon.CreateScope(requestContext));
57+
}
58+
59+
/// <summary>
60+
/// OnMethodEnd callback
61+
/// </summary>
62+
/// <typeparam name="TTarget">Type of the target</typeparam>
63+
/// <typeparam name="TReturn">Type of the response</typeparam>
64+
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param>
65+
/// <param name="returnValue">Return value</param>
66+
/// <param name="exception">Exception instance in case the original code threw an exception.</param>
67+
/// <param name="state">Calltarget state value</param>
68+
/// <returns>A response value, in an async scenario will be T of Task of T</returns>
69+
public static CallTargetReturn<TReturn> OnMethodEnd<TTarget, TReturn>(TTarget instance, TReturn returnValue, Exception exception, CallTargetState state)
70+
{
71+
state.Scope.DisposeWithException(exception);
72+
return new CallTargetReturn<TReturn>(returnValue);
73+
}
74+
}
75+
}
76+
#endif
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// <copyright file="TaskMethodInvokerIntegration.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#if NETFRAMEWORK
7+
using System;
8+
using System.ComponentModel;
9+
using System.Reflection;
10+
using Datadog.Trace.ClrProfiler.CallTarget;
11+
using Datadog.Trace.ClrProfiler.Emit;
12+
using Datadog.Trace.Configuration;
13+
14+
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Wcf
15+
{
16+
/// <summary>
17+
/// System.ServiceModel.Dispatcher.TaskMethodInvoker calltarget instrumentation
18+
/// </summary>
19+
[InstrumentMethod(
20+
AssemblyName = "System.ServiceModel",
21+
TypeName = "System.ServiceModel.Dispatcher.TaskMethodInvoker",
22+
MethodName = "InvokeAsync",
23+
ReturnTypeName = "System.Threading.Tasks.Task`1<System.Tuple`2<System.Object, System.Object[]>>",
24+
ParameterTypeNames = new[] { ClrNames.Object, "System.Object[]" },
25+
MinimumVersion = "4.0.0",
26+
MaximumVersion = "4.*.*",
27+
IntegrationName = WcfCommon.IntegrationName)]
28+
[Browsable(false)]
29+
[EditorBrowsable(EditorBrowsableState.Never)]
30+
public class TaskMethodInvokerIntegration
31+
{
32+
/// <summary>
33+
/// OnMethodBegin callback
34+
/// </summary>
35+
/// <typeparam name="TTarget">Type of the target</typeparam>
36+
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param>
37+
/// <param name="instanceArg">RequestContext instance</param>
38+
/// <param name="inputs">Input arguments</param>
39+
/// <returns>Calltarget state value</returns>
40+
public static CallTargetState OnMethodBegin<TTarget>(TTarget instance, object instanceArg, object[] inputs)
41+
{
42+
// TODO Just use the OperationContext.Current object to get the span information
43+
// context.IncomingMessageHeaders contains:
44+
// - Action
45+
// - To
46+
//
47+
// context.IncomingMessageProperties contains:
48+
// - ["httpRequest"] key to find distributed tracing headers
49+
if (!Tracer.Instance.Settings.IsIntegrationEnabled(WcfCommon.IntegrationId) || !Tracer.Instance.Settings.DelayWcfInstrumentationEnabled || WcfCommon.GetCurrentOperationContext is null)
50+
{
51+
return CallTargetState.GetDefault();
52+
}
53+
54+
var requestContext = WcfCommon.GetCurrentOperationContext()?.GetProperty<object>("RequestContext").GetValueOrDefault();
55+
return new CallTargetState(WcfCommon.CreateScope(requestContext));
56+
}
57+
58+
/// <summary>
59+
/// OnAsyncMethodEnd callback
60+
/// </summary>
61+
/// <typeparam name="TTarget">Type of the target</typeparam>
62+
/// <typeparam name="TResponse">Type of the response, in an async scenario will be T of Task of T</typeparam>
63+
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param>
64+
/// <param name="returnValue">Return value</param>
65+
/// <param name="exception">Exception instance in case the original code threw an exception.</param>
66+
/// <param name="state">Calltarget state value</param>
67+
/// <returns>A response value, in an async scenario will be T of Task of T</returns>
68+
public static TResponse OnAsyncMethodEnd<TTarget, TResponse>(TTarget instance, TResponse returnValue, Exception exception, CallTargetState state)
69+
{
70+
state.Scope.DisposeWithException(exception);
71+
return returnValue;
72+
}
73+
}
74+
}
75+
#endif

0 commit comments

Comments
 (0)