Wcf Service Attribute to Log Method Calls and Exceptions

WCF service attribute to log method calls and exceptions

Yes, it is possible to encapsulate this kind of logging, using the extensibility points built into WCF. There are actually multiple possible approaches. The one I'm describing here adds an IServiceBehavior, which uses a custom IOperationInvoker, and does not require any web.config modifications.

There are three parts to this.

  1. Create an implementation of IOperationInvoker, which wraps the method invocation in the required logging and error-handling.
  2. Create an implementation of IOperationBehavior that applies the invoker from step 1.
  3. Create an IServiceBehavior, which inherits from Attribute, and applies the behavior from step 2.

Step 1 - IOperationInvoker

The crux of IOperationInvoker is the Invoke method. My class wraps the base invoker in a try-catch block:

public class LoggingOperationInvoker : IOperationInvoker
{
IOperationInvoker _baseInvoker;
string _operationName;

public LoggingOperationInvoker(IOperationInvoker baseInvoker, DispatchOperation operation)
{
_baseInvoker = baseInvoker;
_operationName = operation.Name;
}

// (TODO stub implementations)

public object Invoke(object instance, object[] inputs, out object[] outputs)
{
MyInfrastructure.LogStart(_operationName, inputs);
try
{
return _baseInvoker.Invoke(instance, inputs, out outputs);
}
catch (Exception ex)
{
MyInfrastructure.LogError(_operationName, inputs, ex);
return null;
}
MyInfrastructure.LogEnd("Add", parameters);
}
}

Step 2 - IOperationBehavior

The implementation of IOperationBehavior simply applies the custom dispatcher to the operation.

public class LoggingOperationBehavior : IOperationBehavior
{
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new LoggingOperationInvoker(dispatchOperation.Invoker, dispatchOperation);
}

// (TODO stub implementations)
}

Step 3 - IServiceBehavior

This implementation of IServiceBehavior applies the operation behavior to the service; it should inherit from Attribute so that it can be applied as an attribute to the WCF service class. The implementation for this is standard.

public class ServiceLoggingBehavior : Attribute, IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ServiceEndpoint endpoint in serviceDescription.Endpoints)
{
foreach (OperationDescription operation in endpoint.Contract.Operations)
{
IOperationBehavior behavior = new LoggingOperationBehavior();
operation.Behaviors.Add(behavior);
}
}
}
}

Best way to log errors in WCF

I would implement custom IErrorHandler and use log4net

[AttributeUsage (AttributeTargets.Interface)]
public class ErrorPolicyBehaviorAttribute : Attribute, IContractBehavior, IErrorHandler
{
private ILog m_logger;

#region IErrorHandler

public void ProvideFault (Exception error, MessageVersion version, ref Message fault)
{
return;
}

public bool HandleError (Exception error)
{
m_logger.Error (error.Message, error);
return true;
}

#endregion

#region IContractBehavior

public void ApplyDispatchBehavior (ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
...init logger
......Add this class to a list of dispatchRuntime.ChannelDispatcher.ErrorHandlers...
}

#endregion
}

This class also implements IContractBehavior, so you can use it as Attribute on your service contracts.

[ErrorPolicyBehavior]
public interface IYourServiceContract
{ }

log4net is quite flexible, so you can log what you need and when you need.

Log WCF Service Calls with Parameter information

If you enable message tracing, you should get all the details of both the call (including the XML representation of your message sent out) as well as the answer:

<system.diagnostics >
<sources>
<source
name="System.ServiceModel.MessageLogging"
switchValue="Information, ActivityTracing" >
<listeners>
<add name="yourTrace"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="C:\Logs\YourMessageLog.svclog">
<filter type="" />
</add>
</listeners>
</source>
</sources>
<trace autoflush="true" />
</system.diagnostics>
<system.serviceModel>
<diagnostics>
<messageLogging
logMessagesAtTransportLevel="true"
logMessagesAtServiceLevel="false"
logMalformedMessages="true"
logEntireMessage="true"
maxSizeOfMessageToLog="65535000" maxMessagesToLog="500" />
</diagnostics>
</system.serviceModel>

This should create a file called "YourMessageLog.svclog" in a directory "C:\Logs" (which must exist beforehand!) and which you can view with the WCF Service Trace Viewer.

What you'll see here is the XML representation of the message going out and the response coming back in - your parameters will have been wrapped into your XML structure here. Is that what you're looking for?

Exception Logging for WCF Services using ELMAH

The solution from my blog post (referenced in the OP) was based on an existing solution we were/are using to alter HTTP Response Codes during an error state.

So, for us it was a one-line change to pass the Exception to ELMAH. If there's a better solution, I'd love to know about it too.

For Posterity/Reference, and potential improvement - here's the code from the current solution.

HttpErrorHandler and ServiceErrorBehaviourAttribute Classes

using System;
using System.ServiceModel;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Collections.ObjectModel;
using System.Net;
using System.Web;
using Elmah;
namespace YourApplication
{
/// <summary>
/// Your handler to actually tell ELMAH about the problem.
/// </summary>
public class HttpErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return false;
}

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error != null ) // Notify ELMAH of the exception.
{
if (System.Web.HttpContext.Current == null)
return;
Elmah.ErrorSignal.FromCurrentContext().Raise(error);
}
}
}
/// <summary>
/// So we can decorate Services with the [ServiceErrorBehaviour(typeof(HttpErrorHandler))]
/// ...and errors reported to ELMAH
/// </summary>
public class ServiceErrorBehaviourAttribute : Attribute, IServiceBehavior
{
Type errorHandlerType;

public ServiceErrorBehaviourAttribute(Type errorHandlerType)
{
this.errorHandlerType = errorHandlerType;
}

public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
{
}

public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
{
}

public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler;
errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(errorHandler);
}
}
}
}

Usage Example

Decorate your WCF Services with the ServiceErrorBehaviour Attribute:

[ServiceContract(Namespace = "http://example.com/api/v1.0/")]
[ServiceErrorBehaviour(typeof(HttpErrorHandler))]
public class MyServiceService
{
// ...
}

How to log method calls on targets marked with an attribute?

This can be done using aspect-oriented programming (AOP). Have a look at PostSharp. See the sample for tracing here:

Non-Invasive Tracing & Logging

Exception Logging for WCF Services using ELMAH

The solution from my blog post (referenced in the OP) was based on an existing solution we were/are using to alter HTTP Response Codes during an error state.

So, for us it was a one-line change to pass the Exception to ELMAH. If there's a better solution, I'd love to know about it too.

For Posterity/Reference, and potential improvement - here's the code from the current solution.

HttpErrorHandler and ServiceErrorBehaviourAttribute Classes

using System;
using System.ServiceModel;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Collections.ObjectModel;
using System.Net;
using System.Web;
using Elmah;
namespace YourApplication
{
/// <summary>
/// Your handler to actually tell ELMAH about the problem.
/// </summary>
public class HttpErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return false;
}

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error != null ) // Notify ELMAH of the exception.
{
if (System.Web.HttpContext.Current == null)
return;
Elmah.ErrorSignal.FromCurrentContext().Raise(error);
}
}
}
/// <summary>
/// So we can decorate Services with the [ServiceErrorBehaviour(typeof(HttpErrorHandler))]
/// ...and errors reported to ELMAH
/// </summary>
public class ServiceErrorBehaviourAttribute : Attribute, IServiceBehavior
{
Type errorHandlerType;

public ServiceErrorBehaviourAttribute(Type errorHandlerType)
{
this.errorHandlerType = errorHandlerType;
}

public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
{
}

public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
{
}

public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler;
errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(errorHandler);
}
}
}
}

Usage Example

Decorate your WCF Services with the ServiceErrorBehaviour Attribute:

[ServiceContract(Namespace = "http://example.com/api/v1.0/")]
[ServiceErrorBehaviour(typeof(HttpErrorHandler))]
public class MyServiceService
{
// ...
}

MVC Error Handling with WCF Service

As you know how you can handle WCF exceptions but in my opinion this is better to observe these:

1-This not good idea to show user exactly exception message, this is better to show very understandable message for example "Operation get failed there is may problem with back-end service, try again or notify admin"

2- It's is boring end user to redirect to public error page.

3- This is better show the public prompt to user which tell user that the operation get failed exactly where the user do action not redirect it to another page.

4- At the end If you want to do what you want try these:

try
{
//Call your wcf
}
catch(Exception exp)
{
//Logging.Log(LoggingMode.Error, "You message , EXP:{0}...", exp.ToString());
Response.Redirect("~/ErrorPages/Oops.aspx?Error=WCfOperationFailed", false);
}

in your error page page_load:

            switch (Request.QueryString["Error"].ToString())
{
case "WCfOperationFailed":
litError.Text = string.Format("<h2>Error!.</h2><br/><p>{0}.</p>",GetError());
break;
default:
break;
}

public string GetError()
{
Exception lastError = Server.GetLastError();
return lastError.ToString();
}

or you can redirect error message as a QueryString to error page and show it to user in Page_load like:

//in catch block
Response.Redirect("~/ErrorPages/Oops.aspx?Error="+exp.Message, false);

in error page Page_load :

txtError.Text = Request.QueryString["Error"].ToString();

However, you can trap errors that occur anywhere in your application by adding code to the Application_Error handler in the Global.asax file:

 void Application_Error(object sender, EventArgs e)
{
Exception exc = Server.GetLastError();

if (exc is HttpUnhandledException)
{
// Pass the error on to the error page.
Server.Transfer("ErrorPage.aspx?Error="+exc.Message, true);
}
}

This link can be helpful there are some examples
Error Handling



Related Topics



Leave a reply



Submit