Boy, was this painful. But there's not much too it when it works. There are several bits of config, 3 classes and some gotchas involved, as follows.
Config Sections
<system.serviceModel>
<extensions>
<behaviorExtensions>
<!-- Gotcha: this element looks so easy but is a pain in the backside.
The type element has to *exactly* match the undocumented-and-fussier-than-anywhere-else-in-config required format
If it has to change, the best way to do it is to delete the line and then use MS Service Configuration Tool to lookup the assembly and classname
See e.g. http://stackoverflow.com/questions/1163996/adding-a-custom-wcf-behavior-extension-causes-a-configurationerrorsexception
-->
<add name="EnterpriseLibraryClientMessageBodyLogging"
type="MyNamespace.MyAppName.Implementation.LoggingServiceClientDecorators.EnterpriseLibraryMessageBodyLoggingClientBehaviorExtension, MyNamespace.MyAppName.Implementation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="LogAllMessageBodies">
<EnterpriseLibraryClientMessageBodyLogging />
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<!-- Add atrribute
behaviorConfiguration="LogAllMessageBodies"
to the endpoint element to enable WCF message in-and-out logging
e.g.:
-->
<endpoint address="http://10.1.2.3/CalledServiceName/CalledServiceMethod.asmx"
binding="basicHttpBinding" bindingConfiguration="UnsecuredBindingConfiguration"
contract="CalledServiceMethod.CalledServiceMethodSoap"
behaviorConfiguration="LogAllMessageBodies"
name="CalledServiceMethodSoap"/>
... rest of your client section .....
</client
... rest of your system.serviceModel section ...
</system.serviceModel>
<loggingConfiguration name="" tracingEnabled="true" defaultCategory="General">
<listeners>
<add name="FullFlatFileTraceListener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.RollingFlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging"
rollSizeKB="5000" rollFileExistsBehavior="Overwrite" timeStampPattern="yyyy-MM-dd"
listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.RollingFlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging"
fileName="FullLog.log" formatter="FullTextFormatter" />
</listeners>
<formatters>
<add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging"
template="---------------------------------{newline}Timestamp: {timestamp}{newline}Message: {message}{newline}{newline}Extended Properties: {newline}{dictionary( {key}: {value}{newline})}"
name="FullTextFormatter" />
</formatters>
<categorySources/>
<specialSources>
<allEvents switchValue="All" name="All Events">
<listeners>
<add name="FullFlatFileTraceListener"/>
</listeners>
</allEvents>
<notProcessed switchValue="Off" name="Unprocessed Category" />
<errors switchValue="Off" name="Logging Errors & Warnings" />
</specialSources>
</loggingConfiguration>
Code
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using Logger=namespaceWhichDefinesYourLoggerInterfaceIfAny;
namespace blah.LoggingServiceClientDecorators
{
public class EnterpriseLibraryMessageBodyLoggingClientBehaviorExtension : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new EnterpriseLibraryMessageBodyLoggingClientBehavior();
}
public override Type BehaviorType
{
get { return typeof (EnterpriseLibraryMessageBodyLoggingClientBehavior); }
}
}
public class EnterpriseLibraryMessageBodyLoggingClientBehavior : IEndpointBehavior
{
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(
new MessageBodyLoggingClientMessageInspector(new EnterpriseLibraryExceptionHandlingLogger())
);
}
#region Irrelevant IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,EndpointDispatcher endpointDispatcher){}
public void Validate(ServiceEndpoint endpoint){}
#endregion
}
public class MessageBodyLoggingClientMessageInspector : IClientMessageInspector
{
private readonly ILogger logger;
public MessageBodyLoggingClientMessageInspector(ILogger logger)
{
this.logger = logger;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
logger.Info("<MessageRecord Direction=\"In\">{0}</MessageRecord>", reply.ToString());
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
logger.Info("<MessageRecord Direction=\"Out\" Endpoint=\"{1}\">{0}</MessageRecord>", request.ToString(), channel.RemoteAddress.Uri);
return null;
}
}
}
The Gotchas
- Note this is a completely different route to what you'll find if you search for 'WCF message logging' What you'll find there is how to switch on WCF logging. Which logs everything - and I mean everything - except the message bodies.
- The typename in the behaviorExtensions section above is hard to type exactly as required
- The typename in the behaviorExtensions must specify an exact version number which is a pain when it changes
- Note that you can't do anything more sophisticated with the messages in the IClientMessageInspector because by design Messages are Read Once. If you want to extract the innards of the message, you have to copy it to a buffer, then and set the ref parameter to a new copy of the message.