WCF客户端异常时AfterReceiveReply未调用 日志扩展点咨询
WCF客户端全场景操作记录/链路追踪实现方案
IClientMessageInspector的设计定位是处理存在实际消息内容的发送、接收流程,遇到无响应的传输层异常(包括EndpointNotFoundException、ServerTooBusyException、TimeoutException等)时,由于没有实际回复消息生成,AfterReceiveReply方法不会被触发,这是WCF的原生设计逻辑,不是实现bug。
要在不侵入业务调用代码、无需手动在外层写try-catch的前提下,可靠记录每一次操作的开始与结束,可以使用WCF内置的自定义通道绑定元素扩展点,该扩展点位于WCF客户端调用栈的最外层,无论调用是正常返回、返回SOAP错误,还是抛出任意类型的调用异常,都能保证结束逻辑被执行,非常适合分布式链路追踪的Activity启停场景。
具体实现步骤
- 实现自定义
BindingElement,负责构建包装后的通道工厂 - 实现包装后的
IRequestChannel/IRequestSessionChannel,在通道的Request方法外层用try-finally包裹调用逻辑,finally块中统一处理操作结束逻辑 - 将自定义绑定元素插入到目标绑定的元素集合最上层(传输绑定元素之前),配合端点行为自动应用到所有客户端调用
核心实现代码
using System; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; // 自定义追踪绑定元素 public class TracingBindingElement : BindingElement { public override BindingElement Clone() { return new TracingBindingElement(); } public override T GetProperty<T>(BindingContext context) { return context.GetInnerProperty<T>(); } public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context) { var innerFactory = context.BuildInnerChannelFactory<TChannel>(); if (typeof(TChannel) == typeof(IRequestChannel)) { return (IChannelFactory<TChannel>)new TracingRequestChannelFactory(innerFactory); } // 按需实现会话通道、双工通道的包装即可 return innerFactory; } public override bool CanBuildChannelFactory<TChannel>(BindingContext context) { return context.CanBuildInnerChannelFactory<TChannel>(); } } // 包装请求通道工厂 internal class TracingRequestChannelFactory : ChannelFactoryBase<IRequestChannel> { private readonly IChannelFactory<IRequestChannel> _innerFactory; public TracingRequestChannelFactory(IChannelFactory<IRequestChannel> innerFactory) { _innerFactory = innerFactory; } protected override IRequestChannel OnCreateChannel(EndpointAddress address, Uri via) { var innerChannel = _innerFactory.CreateChannel(address, via); return new TracingRequestChannel(innerChannel); } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { return _innerFactory.BeginOpen(timeout, callback, state); } protected override void OnEndOpen(IAsyncResult result) { _innerFactory.EndOpen(result); } protected override void OnOpen(TimeSpan timeout) { _innerFactory.Open(timeout); } // 其余Open/Close/Abort相关方法直接转发给_innerFactory即可 } // 包装请求通道 internal class TracingRequestChannel : ChannelBase, IRequestChannel { private readonly IRequestChannel _innerChannel; public TracingRequestChannel(IRequestChannel innerChannel) : base(innerChannel) { _innerChannel = innerChannel; } public EndpointAddress RemoteAddress => _innerChannel.RemoteAddress; public Uri Via => _innerChannel.Via; public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state) { return _innerChannel.BeginRequest(message, callback, state); } public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state) { return _innerChannel.BeginRequest(message, timeout, callback, state); } public Message EndRequest(IAsyncResult result) { return _innerChannel.EndRequest(result); } public Message Request(Message message) { return Request(message, DefaultSendTimeout); } public Message Request(Message message, TimeSpan timeout) { // 操作开始逻辑,对应原BeforeSendRequest的执行时机 Console.WriteLine("Operation Start"); try { return _innerChannel.Request(message, timeout); } finally { // 无论正常返回还是抛出异常,都会走到这里,对应操作结束逻辑 Console.WriteLine("Operation End"); // 在这里调用Activity.Stop()即可保证链路追踪活动被正确关闭 } } // 其余通道方法、Open/Close/Abort相关方法直接转发给_innerChannel即可 } // 自定义端点行为,用来自动注入绑定元素 public class TracingEndpointBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { // 避免重复注入绑定元素 foreach (var binding in endpoint.Binding.CreateBindingElements()) { if (binding is TracingBindingElement) return; } // 插入到绑定集合最顶层,保证包裹整个调用流程 endpoint.Binding.Elements.Insert(0, new TracingBindingElement()); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } }
使用方式
只需要在创建ChannelFactory的时候,给端点添加上面实现的TracingEndpointBehavior即可,业务代码完全不需要修改,所有调用都会自动被包裹:
factory.Endpoint.EndpointBehaviors.Add(new TracingEndpointBehavior());
方案说明
- 该扩展点位于WCF通道层,是客户端调用流程的最外层,覆盖所有异常场景,不会出现结束逻辑漏执行的问题
- 完全基于WCF原生扩展点实现,不需要使用RealProxy等非官方包装方式,兼容性和稳定性与WCF原生管道一致
- 如果需要支持双工通道、会话通道,只需要对应实现
IDuplexSessionChannel等对应通道的包装逻辑即可 - 如果使用异步调用方式,需要对应实现异步的
Request方法包装,保证异步场景下finally块也能正确执行
内容的提问来源于stack exchange,提问作者vyrp
相关产品推荐
相关产品推荐

