如何从 .NET Core 上的 WCF SOAP 客户端记录 http 级流?
How to log http level stream from WCF SOAP client on .NET Core?
背景:我正在维护一个从各种不可靠的 API 中提取数据的集成平台。其中一些操作会产生潜在的高成本,因此出于诊断目的,每条传出和传入的消息都会记录到磁盘的单独文件中。对于类似 REST 的 API,我在网络流上使用了一个简单的包装器,它也将数据保存到一个文件中。对于 .NET Classic SOAP 客户端,我有一个帮助程序动态包装 SoapHttpClientProtocol
以使用相同的网络流日志记录机制。
使用 .NET Standard 2.0 和 .NET Core,唯一支持的编写 SOAP 客户端的方式是 WCF。我如何以编程方式配置 WCF SOAP 客户端以记录 HTTP incoming/outgoing 流以分隔文件,最好使用可配置的名称?
我当前的示例客户端代码:
public abstract class ServiceCommunicatorBase<T>
where T : IClientChannel
{
private const int Timeout = 20000;
private static readonly ChannelFactory<T> ChannelFactory = new ChannelFactory<T>(
new BasicHttpBinding(),
new EndpointAddress(new Uri("http://target/endpoint")));
protected T1 ExecuteWithTimeoutBudget<T1>(
Func<T, Task<T1>> serviceCall,
[CallerMemberName] string callerName = "")
{
// TODO: fixme, setup logging
Console.WriteLine(callerName);
using (var service = this.CreateService(Timeout))
{
// this uses 2 threads and is less than ideal, but legacy app can't handle async yet
return Task.Run(() => serviceCall(service)).GetAwaiter().GetResult();
}
}
private T CreateService(int timeout)
{
var clientChannel = ChannelFactory.CreateChannel();
clientChannel.OperationTimeout = TimeSpan.FromMilliseconds(timeout);
return clientChannel;
}
}
public class ConcreteCommunicator
: ServiceCommunicatorBase<IWCFRemoteInterface>
{
public Response SomeRemoteAction(Request request)
{
return this.ExecuteWithTimeoutBudget(
s => s.SomeRemoteAction(request));
}
}
我已经设法使用通过可配置行为附加的 IClientMessageInspector
来记录消息。 一些 Message Inspectors on MSDN 的文档,但它仍然含糊不清(而且有些过时,netstandard-2.0
API 表面略有不同。
我不得不从使用 ChannelFactory<T>
转向使用实际生成的代理 class。工作代码(为简洁起见删减):
var clientChannel = new GeneratedProxyClient(
new BasicHttpBinding { SendTimeout = TimeSpan.FromMilliseconds(timeout) },
new EndpointAddress(new Uri("http://actual-service-address"));
clientChannel.Endpoint.EndpointBehaviors.Add(new LoggingBehaviour());
服务class是:
// needed to bind the inspector to the client channel
// other methods are empty
internal class LoggingBehaviour : IEndpointBehavior
{
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new LoggingClientMessageInspector());
}
}
internal class LoggingClientMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
var correlationId = Guid.NewGuid();
this.SaveLog(ref request, correlationId, "RQ");
return correlationId;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
var correlationId = (Guid)correlationState;
this.SaveLog(ref reply, correlationId, "RS");
}
private void SaveLog(ref Message request, Guid correlationId, string suffix)
{
var outputPath = GetSavePath(suffix, correlationId, someOtherData);
using (var buffer = request.CreateBufferedCopy(int.MaxValue))
{
var directoryName = Path.GetDirectoryName(outputPath);
if (directoryName != null)
{
Directory.CreateDirectory(directoryName);
}
using (var stream = File.OpenWrite(outputPath))
{
using (var message = buffer.CreateMessage())
{
using (var writer = XmlWriter.Create(stream))
{
message.WriteMessage(writer);
}
}
}
request = buffer.CreateMessage();
}
}
}
背景:我正在维护一个从各种不可靠的 API 中提取数据的集成平台。其中一些操作会产生潜在的高成本,因此出于诊断目的,每条传出和传入的消息都会记录到磁盘的单独文件中。对于类似 REST 的 API,我在网络流上使用了一个简单的包装器,它也将数据保存到一个文件中。对于 .NET Classic SOAP 客户端,我有一个帮助程序动态包装 SoapHttpClientProtocol
以使用相同的网络流日志记录机制。
使用 .NET Standard 2.0 和 .NET Core,唯一支持的编写 SOAP 客户端的方式是 WCF。我如何以编程方式配置 WCF SOAP 客户端以记录 HTTP incoming/outgoing 流以分隔文件,最好使用可配置的名称?
我当前的示例客户端代码:
public abstract class ServiceCommunicatorBase<T>
where T : IClientChannel
{
private const int Timeout = 20000;
private static readonly ChannelFactory<T> ChannelFactory = new ChannelFactory<T>(
new BasicHttpBinding(),
new EndpointAddress(new Uri("http://target/endpoint")));
protected T1 ExecuteWithTimeoutBudget<T1>(
Func<T, Task<T1>> serviceCall,
[CallerMemberName] string callerName = "")
{
// TODO: fixme, setup logging
Console.WriteLine(callerName);
using (var service = this.CreateService(Timeout))
{
// this uses 2 threads and is less than ideal, but legacy app can't handle async yet
return Task.Run(() => serviceCall(service)).GetAwaiter().GetResult();
}
}
private T CreateService(int timeout)
{
var clientChannel = ChannelFactory.CreateChannel();
clientChannel.OperationTimeout = TimeSpan.FromMilliseconds(timeout);
return clientChannel;
}
}
public class ConcreteCommunicator
: ServiceCommunicatorBase<IWCFRemoteInterface>
{
public Response SomeRemoteAction(Request request)
{
return this.ExecuteWithTimeoutBudget(
s => s.SomeRemoteAction(request));
}
}
我已经设法使用通过可配置行为附加的 IClientMessageInspector
来记录消息。 一些 Message Inspectors on MSDN 的文档,但它仍然含糊不清(而且有些过时,netstandard-2.0
API 表面略有不同。
我不得不从使用 ChannelFactory<T>
转向使用实际生成的代理 class。工作代码(为简洁起见删减):
var clientChannel = new GeneratedProxyClient(
new BasicHttpBinding { SendTimeout = TimeSpan.FromMilliseconds(timeout) },
new EndpointAddress(new Uri("http://actual-service-address"));
clientChannel.Endpoint.EndpointBehaviors.Add(new LoggingBehaviour());
服务class是:
// needed to bind the inspector to the client channel
// other methods are empty
internal class LoggingBehaviour : IEndpointBehavior
{
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new LoggingClientMessageInspector());
}
}
internal class LoggingClientMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
var correlationId = Guid.NewGuid();
this.SaveLog(ref request, correlationId, "RQ");
return correlationId;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
var correlationId = (Guid)correlationState;
this.SaveLog(ref reply, correlationId, "RS");
}
private void SaveLog(ref Message request, Guid correlationId, string suffix)
{
var outputPath = GetSavePath(suffix, correlationId, someOtherData);
using (var buffer = request.CreateBufferedCopy(int.MaxValue))
{
var directoryName = Path.GetDirectoryName(outputPath);
if (directoryName != null)
{
Directory.CreateDirectory(directoryName);
}
using (var stream = File.OpenWrite(outputPath))
{
using (var message = buffer.CreateMessage())
{
using (var writer = XmlWriter.Create(stream))
{
message.WriteMessage(writer);
}
}
}
request = buffer.CreateMessage();
}
}
}