Using LogFactory warp up log4net, entlib and other logging service

JP provides a great solution to warp log4net, textwritter and other logging service, the basic idea is to create a common interface and a common Log class used by application directly.

In Application:

Log.For(this).Info(…)

And Log class initialize different logging service might be passed in.


public class Log
{
private static ILogFactory logFactory;

public static void InitializeLogFactory(ILogFactory logFactory)
{
Log.logFactory = logFactory;
}

public static ILog For(object itemThatRequiresLoggingServices)
{
return For(itemThatRequiresLoggingServices.GetType());
}

public static ILog For(Type type){

// This is IOC style, then we should not use logFactory field which needs to be passed in when using log.
//            return DependencyResolver.GetImplementationOf().CreateFor(type);

return logFactory.CreateFor(type); ;
}
}

If it’s a simple console logging service, the implementation will be like this:


    public class ConsoleLogFactory : ILogFactory
    {
        public ILog CreateFor(Type type)
        {
            return new ConsoleLogger();
        }

        private class ConsoleLogger : ILog
        {
            public void Informational(string message)
            {
                System.Console.Out.WriteLine(message);
            }

            public void Informational(string messageFormat, params object[] args)
            {
                System.Console.Out.WriteLine(messageFormat, args);
            }

And the log4net factory will be this, in which the WireUpConfiguration() method is using singleton pattern.

    public class Log4NetLogFactory : ILogFactory
    {
        private ILog4NetInitialization initialization;
        private bool initialized;

        public Log4NetLogFactory(): this(new Log4NetInitialization())
        {
        }

        public Log4NetLogFactory(string filename): this(new Log4NetInitialization(filename))
        {
        }

        // using ILog4NetInitialization can enable logFactory read from different app config file.
        public Log4NetLogFactory(ILog4NetInitialization initialization)
        {
            this.initialization = initialization;
        }

        public ILog CreateFor(Type typeThatRequiresLoggingServices)
        {
            WireUpConfiguration();
            return new Log4NetLog(LogManager.GetLogger(typeThatRequiresLoggingServices));
        }

        private void WireUpConfiguration()
        {
            if (initialized) return;

            initialization.Execute();
            initialized = true;
        }
    }

Finally, the log4netINitialization is this: (I don’t think ILog4NetInitialization is necessary, in fact the whole Log4NetInitialization can also be moved into log4net Factory class even.)

    public class Log4NetInitialization : ILog4NetInitialization
    {
        private readonly XmlElement configuration;
        public Log4NetInitialization(){}

        public Log4NetInitialization(string filename)
        {
            XmlDocument document = new XmlDocument();
            document.Load(filename);
            configuration = document.DocumentElement;

        }
        public Log4NetInitialization(XmlElement configuration)
        {
            this.configuration = configuration;
        }

        public void Execute()
        {
            if (configuration == null) XmlConfigurator.Configure();
            else XmlConfigurator.Configure(configuration);
        }
    }

The benefit of all those work is, the front end is same based on different logging service implementation,

Log.For(this).Info(…)

Log.For(this).Debug(…)

Log.For(this).Fatal(…)

Log.For(this).Warn(…)

Log.For(this).Error(…)

Update at Mar. 25, 2011
Yesterday I was trying to explain this static gateway pattern to my coworkers, I realized I need a drawing to describe what I was thinking. Here it is, by comparing the same implementation done by IglooCoder Commons, in which he rename LogFactory to Logger.

Advertisements

Use WCF FaultException LogBook in CSLA

Juval Lowy, the author of Programming WCF, has a very good logbook sample application in the attached source code, which can log all the WCF FaultException into a database table.

To make it work with CSLA, I figured out there is a minor change needed. (The version I am working on in 3.0) The reason is WcfPortal actually doesn’t throw exception, instead, it casts the exception to an object returning to WcfProxy. This means, It’s WcfProxy at the client side throwing the exception. Apparently, Juval’s logbook can only monitor exceptions on Wcf Host, not at the client. I had to add an

throw ex;

into WcfPortal’s catching block to make CSLA framework can use this handy logBook tools.

WcfPortal:
    public WcfResponse Fetch(FetchRequest request)
    {
      Csla.Server.DataPortal portal = new Csla.Server.DataPortal();
      object result;
      try
      {
        result = portal.Fetch(request.ObjectType, request.Criteria, request.Context);
      }
      catch (Exception ex)
      {
        result = ex;
      }
      return new WcfResponse(result);
    }

WcfProxy:
    public DataPortalResult Fetch(Type objectType, object criteria, DataPortalContext context)
    {
      ChannelFactory cf = new ChannelFactory(_endPoint);
      IWcfPortal svr = cf.CreateChannel();
      WcfResponse response =
        svr.Fetch(new FetchRequest(objectType, criteria, context));
      cf.Close();

      object result = response.Result;
      if (result is Exception)
        throw (Exception)result;
      return (DataPortalResult)result;
    }

Thanks to Juval, this is almost exactly what our boss wants, the only thing missing is UserName and Client Machine information, and exception stack?

Most important, what’s the reason WcfPortal doesn’t throw exception? The explanation back from Rocky is, he wants to display the full stack of the error/exception so the exception on the server side is caught but not thrown out, instead this exception is passed to client as an parameter to the client side. Another ward, no exception will be thrown from CSLA at all! There is no way to use LogBook, unless we change CSLA framework.

Here is Andy’s solution from CLSA forum:

That’s what I do.. override OnDataPortalException.  You can do this in something like MyCompanyBusinessBase, and have all your classes inherit that subclass.  Rocky recommends creating such a subclass even if initially it doesn’t add any behavior at all, because someday you will.

One thing to watch is that if you’re doign different logging on server vs. client you need to check ExecutionLocation.  But it works great.

My exception handling actually builds up a message, drilling down to all InnerExceptions and emails the whole thing to me.  Very helpful to finding out where the exception is occuring.  If the email fails to send for some reason, my fallback is to log a message to the Windows Event Log.  This can be tricky to get going at first, because if you’re remoting you’ll need to manually add the Application Name to the registry so that your application can use that name (and you’ll want to do this to distingish between your remoting application and other asp.net errors).

HTH
Andy

TypeMock Repository object in CSLA BO

CSLAEx DAL’s basic idea is adding an middle method in DataPortal_XXX():

CSLA BO Get -> DataPortal_Fetch -> DataAccess.ReadDataMethod (return SafeDataReader)

I added another repository in between to introduce stong type domain object instead of DataReader (I think DataReader is not much difference than DataSet in UI)

CSLA BO Get -> DataPortal_Fetch ->Repository.DataProssingMethod() (return DTO)-> DataAccess.ReadDataMethod (return SafeDataReader)

Repository can be mocked, and DataAccess can be tested (based on peeking into db then writing assert expection.)

The next problem is, how to test / mock the top level static Get method? I tried using adapter pattern by adding a injectable public constructor to BO, then the test case code like this:

[Test]
public void GetProductOnBOTestUsingMock()
{
MockRepository mocks = new MockRepository();
IProductRepository mockRepository = (IProductRepository)mocks.CreateMock<IProductRepository>();
Product p = new Product(mockRepository);
Guid id = Guid.NewGuid();
using (mocks.Record())
{
ProductDTO fakeProduct = null;
Expect.Call(mockRepository.GetById(id)).Return(fakeProduct);

}// Replay and validate interaction
Product obj = null;
using (mocks.Playback())
{
obj = p.GetProductAdaptor(id);

}// Post-interaction assertions
Assert.IsNull(obj);

Assert.AreEqual(obj.ProductName, "");
}

This test case failed, because in CSLA’s calling sequence:

Client.DataPortal -> LocalProxy -> Server.DataPortal -> 1. ServicedDataPortal TransactionalTypes.EnterpriseServices: 2. TransactionalTypes TransactionalTypes.TransactionScope 3. SimpleDataPortal default

The last step will run this piece of code:

namespace Csla.Server
{

public class SimpleDataPortal : IDataPortalServer
{
public DataPortalResult Fetch(Type objectType, object criteria, DataPortalContext context)
{
object obj = null;
try
{
// create an instance of the business object.
obj = Activator.CreateInstance(objectType, true);
...

My attempt to adding constuctor injection won’t suceed. I had to back to TypeMock then:

[Test]
public void TestGetUsingTypeMock()
{
// Setup Stud Data
ProductDTO stubProdDTO = new ProductDTO();
stubProdDTO.ProductName = "TypeMocked prod name";

// Everything inside recorder block will be mocked.
// expect the last line settng return value.
using (RecordExpectations recorder = RecorderManager.StartRecording())
{
ProductRepository mockRepository = new ProductRepository();
mockRepository.GetById(Guid.Empty);
recorder.Return(stubProdDTO);
}

Product p = Product.GetProduct(Guid.Empty);
Assert.IsNotNull(p);
Assert.AreEqual("TypeMocked prod name", p.ProductName);
}

It’s green

Another benefit to add a repository between BO and DataAccess object might be, it will be easier to use Castle’s IIntercepter to log those repository calls. As Castle warned:

If you are registering a component without an interface (the service) then only virtual methods can be intercepted.

Miguel seems doesn’t like code against interface in his dataaccess class, neither does Rocky in his CSLA BO. But we can add interface in our repository object. I am going to post this part of code once I finish it.

About repository class, JP treats those classes as DataAccess objects which makes sense, the return type of his repository class is Domain object, so he convert those domain object to DTO in his task layer using mapper class (Martin Fowler initially uses XXXAssembly class to do the conversion between domain object and DTO) Our CSLA BO is too rich which can not be returned from repository, so my repository just returns DTO instead. I think this solution can eliminate the usage of task and mapper object in my project.

Why AOP?

AOP, Aspect Oriented Programing, we need it because we want to do logging, security, and all those crosscutting concerns in the right way.

Business Objects, or domain object should not involved those because they should be clean and simple. Those crosscutting concerns can be injected into, by introducing AOP, through Castle Windsor Container, creating an object implementing IInterceptor interface, then inject it into the target BO by changing configuration file. Example can be found at Matthew’s old blog, and new blog.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <components>
    <component id="repository"
               service="WindsorInterceptor.IRepository`1, WindsorInterceptor"
               type="WindsorInterceptor.Repository`1, WindsorInterceptor">
      <interceptors>
        <interceptor>${logInterceptor}</interceptor>
      </interceptors>
    </component>
    <component id="logInterceptor"
               service="WindsorInterceptor.ConsoleLogInterceptor, WindsorInterceptor"
               type="WindsorInterceptor.ConsoleLogInterceptor, WindsorInterceptor"
               lifestyle="transient">
    </component>
  </components>
</configuration>