Skip to content

Commit 4afeee3

Browse files
committed
Move WCF client instrumentation down to a lower-level binding, rather than in a ClientMessageInspector
1 parent 5c6ac68 commit 4afeee3

29 files changed

Lines changed: 1761 additions & 189 deletions

src/OpenTelemetry.Instrumentation.Wcf/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
* Update OpenTelemetry SDK version to `1.5.1`.
66
([#1255](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1255))
7+
* Client instrumentation implementation moved to lower-level `BindingElement`.
8+
([#1247](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1247))
79

810
## 1.0.0-rc.10
911

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// <copyright file="AsyncResultWithTelemetryState.cs" company="OpenTelemetry Authors">
2+
// Copyright The OpenTelemetry Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
using System;
18+
using System.Threading;
19+
20+
namespace OpenTelemetry.Instrumentation.Wcf.Implementation;
21+
22+
internal sealed class AsyncResultWithTelemetryState : IAsyncResult
23+
{
24+
public AsyncResultWithTelemetryState(IAsyncResult inner, RequestTelemetryState telemetryState)
25+
{
26+
this.Inner = inner;
27+
this.TelemetryState = telemetryState;
28+
}
29+
30+
public IAsyncResult Inner { get; }
31+
32+
public RequestTelemetryState TelemetryState { get; }
33+
34+
object IAsyncResult.AsyncState => this.Inner.AsyncState;
35+
36+
WaitHandle IAsyncResult.AsyncWaitHandle => this.Inner.AsyncWaitHandle;
37+
38+
bool IAsyncResult.CompletedSynchronously => this.Inner.CompletedSynchronously;
39+
40+
bool IAsyncResult.IsCompleted => this.Inner.IsCompleted;
41+
42+
public static AsyncCallback GetAsyncCallback(AsyncCallback innerCallback, RequestTelemetryState telemetryState)
43+
{
44+
return (IAsyncResult ar) =>
45+
{
46+
innerCallback(new AsyncResultWithTelemetryState(ar, telemetryState));
47+
};
48+
}
49+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// <copyright file="ClientChannelInstrumentation.cs" company="OpenTelemetry Authors">
2+
// Copyright The OpenTelemetry Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
using System;
18+
using System.Diagnostics;
19+
using System.ServiceModel.Channels;
20+
using OpenTelemetry.Context.Propagation;
21+
using OpenTelemetry.Internal;
22+
using OpenTelemetry.Trace;
23+
24+
namespace OpenTelemetry.Instrumentation.Wcf.Implementation;
25+
26+
internal static class ClientChannelInstrumentation
27+
{
28+
public static RequestTelemetryState BeforeSendRequest(Message request, Uri remoteChannelAddress)
29+
{
30+
if (!ShouldInstrumentRequest(request))
31+
{
32+
return new RequestTelemetryState { SuppressionScope = SuppressDownstreamInstrumentation() };
33+
}
34+
35+
Activity activity = WcfInstrumentationActivitySource.ActivitySource.StartActivity(
36+
WcfInstrumentationActivitySource.OutgoingRequestActivityName,
37+
ActivityKind.Client);
38+
IDisposable suppressionScope = SuppressDownstreamInstrumentation();
39+
40+
if (activity != null)
41+
{
42+
var action = string.Empty;
43+
if (!string.IsNullOrEmpty(request.Headers.Action))
44+
{
45+
action = request.Headers.Action;
46+
activity.DisplayName = action;
47+
}
48+
49+
Propagators.DefaultTextMapPropagator.Inject(
50+
new PropagationContext(activity.Context, Baggage.Current),
51+
request,
52+
WcfInstrumentationActivitySource.MessageHeaderValueSetter);
53+
54+
if (activity.IsAllDataRequested)
55+
{
56+
activity.SetTag(WcfInstrumentationConstants.RpcSystemTag, WcfInstrumentationConstants.WcfSystemValue);
57+
58+
var actionMetadata = GetActionMetadata(request, action);
59+
activity.SetTag(WcfInstrumentationConstants.RpcServiceTag, actionMetadata.ContractName);
60+
activity.SetTag(WcfInstrumentationConstants.RpcMethodTag, actionMetadata.OperationName);
61+
62+
if (WcfInstrumentationActivitySource.Options.SetSoapMessageVersion)
63+
{
64+
activity.SetTag(WcfInstrumentationConstants.SoapMessageVersionTag, request.Version.ToString());
65+
}
66+
67+
var remoteAddressUri = request.Headers.To ?? remoteChannelAddress;
68+
if (remoteAddressUri != null)
69+
{
70+
activity.SetTag(WcfInstrumentationConstants.NetPeerNameTag, remoteAddressUri.Host);
71+
activity.SetTag(WcfInstrumentationConstants.NetPeerPortTag, remoteAddressUri.Port);
72+
activity.SetTag(WcfInstrumentationConstants.WcfChannelSchemeTag, remoteAddressUri.Scheme);
73+
activity.SetTag(WcfInstrumentationConstants.WcfChannelPathTag, remoteAddressUri.LocalPath);
74+
}
75+
76+
if (request.Properties.Via != null)
77+
{
78+
activity.SetTag(WcfInstrumentationConstants.SoapViaTag, request.Properties.Via.ToString());
79+
}
80+
81+
try
82+
{
83+
WcfInstrumentationActivitySource.Options.Enrich?.Invoke(activity, WcfEnrichEventNames.BeforeSendRequest, request);
84+
}
85+
catch (Exception ex)
86+
{
87+
WcfInstrumentationEventSource.Log.EnrichmentException(ex);
88+
}
89+
}
90+
}
91+
92+
return new RequestTelemetryState
93+
{
94+
SuppressionScope = suppressionScope,
95+
Activity = activity,
96+
};
97+
}
98+
99+
public static void AfterReceiveReply(Message reply, RequestTelemetryState state)
100+
{
101+
Guard.ThrowIfNull(state);
102+
103+
state.SuppressionScope?.Dispose();
104+
105+
if (state.Activity is Activity activity)
106+
{
107+
if (activity.IsAllDataRequested)
108+
{
109+
if (reply == null || reply.IsFault)
110+
{
111+
activity.SetStatus(Status.Error);
112+
}
113+
114+
if (reply != null)
115+
{
116+
activity.SetTag(WcfInstrumentationConstants.SoapReplyActionTag, reply.Headers.Action);
117+
try
118+
{
119+
WcfInstrumentationActivitySource.Options.Enrich?.Invoke(activity, WcfEnrichEventNames.AfterReceiveReply, reply);
120+
}
121+
catch (Exception ex)
122+
{
123+
WcfInstrumentationEventSource.Log.EnrichmentException(ex);
124+
}
125+
}
126+
}
127+
128+
activity.Stop();
129+
}
130+
}
131+
132+
private static IDisposable SuppressDownstreamInstrumentation()
133+
{
134+
return WcfInstrumentationActivitySource.Options?.SuppressDownstreamInstrumentation ?? false
135+
? SuppressInstrumentationScope.Begin()
136+
: null;
137+
}
138+
139+
private static ActionMetadata GetActionMetadata(Message request, string action)
140+
{
141+
ActionMetadata actionMetadata = null;
142+
if (request.Properties.TryGetValue(TelemetryContextMessageProperty.Name, out object telemetryContextProperty))
143+
{
144+
var actionMappings = (telemetryContextProperty as TelemetryContextMessageProperty)?.ActionMappings;
145+
if (actionMappings != null && actionMappings.TryGetValue(action, out ActionMetadata metadata))
146+
{
147+
actionMetadata = metadata;
148+
}
149+
}
150+
151+
return actionMetadata != null ? actionMetadata : new ActionMetadata
152+
{
153+
ContractName = null,
154+
OperationName = action,
155+
};
156+
}
157+
158+
private static bool ShouldInstrumentRequest(Message request)
159+
{
160+
try
161+
{
162+
if (WcfInstrumentationActivitySource.Options == null || WcfInstrumentationActivitySource.Options.OutgoingRequestFilter?.Invoke(request) == false)
163+
{
164+
WcfInstrumentationEventSource.Log.RequestIsFilteredOut();
165+
return false;
166+
}
167+
}
168+
catch (Exception ex)
169+
{
170+
WcfInstrumentationEventSource.Log.RequestFilterException(ex);
171+
return false;
172+
}
173+
174+
return true;
175+
}
176+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// <copyright file="InstrumentedChannel.cs" company="OpenTelemetry Authors">
2+
// Copyright The OpenTelemetry Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
using System.ServiceModel.Channels;
18+
19+
namespace OpenTelemetry.Instrumentation.Wcf.Implementation;
20+
21+
internal class InstrumentedChannel : InstrumentedCommunicationObject, IChannel
22+
{
23+
public InstrumentedChannel(IChannel inner)
24+
: base(inner)
25+
{
26+
}
27+
28+
protected new IChannel Inner { get => (IChannel)base.Inner; }
29+
30+
T IChannel.GetProperty<T>()
31+
where T : class
32+
{
33+
return this.Inner.GetProperty<T>();
34+
}
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// <copyright file="InstrumentedChannelFactory.cs" company="OpenTelemetry Authors">
2+
// Copyright The OpenTelemetry Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
using System.ServiceModel.Channels;
18+
19+
namespace OpenTelemetry.Instrumentation.Wcf.Implementation;
20+
21+
internal class InstrumentedChannelFactory : InstrumentedCommunicationObject, IChannelFactory
22+
{
23+
public InstrumentedChannelFactory(IChannelFactory inner)
24+
: base(inner)
25+
{
26+
}
27+
28+
protected new IChannelFactory Inner { get => (IChannelFactory)base.Inner; }
29+
30+
T IChannelFactory.GetProperty<T>()
31+
where T : class
32+
{
33+
return this.Inner.GetProperty<T>();
34+
}
35+
}

0 commit comments

Comments
 (0)