IEndpointBehavior生命周期/日志记录服务调用

本文关键字:记录 服务 调用 日志 生命 周期 IEndpointBehavior | 更新日期: 2023-09-27 17:59:05

我正在尝试记录所有发送到服务引用的出站请求,包括完整的请求和响应主体。我以为我有一个使用behaviorExtensions的解决方案,但在部署后,很明显,扩展是在多个请求之间共享的。

这是我当前的代码:

public class LoggingBehaviorExtender : BehaviorExtensionElement
{
    public override Type BehaviorType => typeof(LoggingRequestExtender);
    protected override object CreateBehavior() { return new LoggingRequestExtender(); }
}
public class LoggingRequestExtender : IClientMessageInspector, IEndpointBehavior
{
    public string Request { get; private set; }
    public string Response { get; private set; }
    #region IClientMessageInspector
    public virtual object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
    {
        Request = request.ToString();
        Response = null;
        return null;
    }
    public virtual void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
    {
        Response = reply.ToString();
    }
    #endregion
    #region IEndpointBehavior
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(this);
    }
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
    public void Validate(ServiceEndpoint endpoint) { }
    #endregion
}

然后,当我到达要记录的点时,我提取行为。。。

var lre = client.Endpoint.Behaviors.OfType<LoggingRequestExtender>().FirstOrDefault();
var req = lre?.Request;
var resp = lre?.Response;

将调试日志添加到LoggingRequestExtender中,我发现它只为多个请求实例化了一次。

有没有一种方法可以确保这个行为类对于每个线程都是新实例化的?或者,在进行服务调用时,有更好的方法来获取完整的请求/响应主体吗?

编辑/部分答案:

自从写这篇文章以来,我发现BeforeSendRequest返回的值作为correlationState传递到AfterReceiveReply中,因此我可以使用guid:连接请求和响应

public virtual object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
    var guid = Guid.NewGuid();
    WebServiceLog.LogCallStart(guid, channel.RemoteAddress.ToString(), request.ToString());
    return guid;
}
public virtual void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
    Guid guid = (Guid)correlationState;
    WebServiceLog.LogCallEnd(guid, reply.ToString());
}

我认为这种方法有两个缺陷。一个适合使用的方法是,这需要一个日志插入,然后更新,而不是一个插入。

第二个问题更像是一个问题:在出现异常(例如超时)的情况下,我们从未命中AfterReciveSupply,因此日志不知道发生了什么。我可以单独记录异常。。。

try
{
    response = client.SomeFunction(request);
}
catch (Exception ex)
{
    AppLog.Error("Some function failed", ex);
}

但是我看不到访问BeforeSendRequest/AfterReceiveReply之外的guid的方法,所以我没有什么可以将异常日志与服务请求日志联系起来的。

IEndpointBehavior生命周期/日志记录服务调用

有几种方法可以做到这一点。

1、你所描述的必须单独记录通话的情况不一定是这样的。如果WCF服务位于非负载平衡服务器中,只需使用Guid作为键将请求添加到MemoryCache。当请求到来时,完成请求并一次性登录。要捕获超时调用,您可以在线程上运行一个进程,该进程将每隔x分钟检查一次MemoryCache以提取并记录(使用足够的锁来确保线程安全)。

如果WCF服务处于负载平衡环境中,那么您所做的一切都与上面相同,只是存储到非sql类型的数据存储中。

2、进行外呼的代码是否在您的更改范围内?如果是这样,您可以放弃创建行为扩展,而是创建一个定制的消息记录器。使用实现IDisposable的类,您可以编写这样的漂亮代码。。

RequestMessage request = new RequestMessage();  
ResponseMessage response = null;
using (_messageLogger.LogMessage(request, () => response, CallContextHelper.GetContextId(), enabled))
{
  response = _outboundService.DoSomething(request);  
}

这样就不需要另一个进程来捕获将在dispose方法中处理的任何超时线程。

如果你需要更多的清晰度,请告诉我,希望这能帮助你。。。