Friday, December 12, 2008

Logging to Database With Enterprise Library Part II

I've been surprised at how much traffic my little insight into the lack of functionality that the out of the box database log listener has in the Enterprise Library. So much that I figured it was time to share my first pass on creating a log listener that can dynamically pull attributes from a LogEntry and call any stored procedure.

To set expectations this is a first pass solution which took me a couple of hours to come up with. It is not complete by any means, and there is plenty of places that it could use work; however, it is 100% functional and solves the problem I was setting out to solve. I appreciate any and all feedback. If you take this code and improve upon it let me know I'd love to know!

I am currently using Enteprise Library 3.x, so this might have changed with their more recent 4.0 release.

When building this I had a critical to make which was how do we configure this? I wanted to be able to specify two values a parameter for the stored procedure, and a property / method to be invoked on the LogEvent object. I could have used reflection, LCG, code dom...there are lots and lots of options; however, I wanted to choose something that would be fast at runtime (Reflection is out), and that would be quick for me to code (LCG and Code DOM are out).

I opted for using Lambda Expression, which are very efficent at runtime, and which I could code to very very quickly. This is one area for improvement, right now the expressions can be defined in a configuration file; however, this is definetly something that is possible, and that I would love to add.

I built this so that defining the properties via configuration are optional. I've been using Fluent nHibernate lately and I've learned to enjoy the refactoring support that exists in defining your configuration in code. So the pieces that we need to implement are:
  1. A dictionary of Expressions that call properties or methods on a LogEntry object
  2. A trace listener that uses the dictionary to construct a stored procedure call
  3. A log entry object that can tie the trace listener to the expressions

So let's get started. First thing I did was created a unit test to see what kind of interface I needed for my Expression dictionary. This looked like :


[TestMethod]

public void InitalizeExpressionCache()

{

    var cache = AuditLoggerExpressionCache.Instance;

    string category = "UnitTest";

    string connection = "[CutterMain]";

    string procedure = "sp_InsertUnitTest";

    cache.CreateGroup<AuditLogEntryUnitTest>(category, connection, procedure)

        .Add("MyProperty", ale => ale.MyProperty)

        .Cache();

 

    AuditLoggerExpressionGroup<AuditLogEntryUnitTest> grp = cache["UnitTest"] as AuditLoggerExpressionGroup<AuditLogEntryUnitTest>;

    AuditLogEntryUnitTest entity = new AuditLogEntryUnitTest();

    Assert.AreEqual(entity.MyProperty, grp["MyProperty"](entity));

}



AuditLogEntryUnitTest is a simple fake class to test the framework. I suppose you could substitute your own Mock using your favorite mocking framework:

public class AuditLogEntryUnitTest : AuditLogEntry

{

    public AuditLogEntryUnitTest()

        : base("Unit test", "UnitTest")

    {

    }

    public string MyProperty { get { return "MyProperty Called"; } }

}



Next we are going to define an AuditLogEntry class. This should be the base class for all of your auditing log entries. This class is defined as:

public class AuditLogEntry : LogEntry

{

    public AuditLogEntry(string auditMsg, string auditCategory)

    {

        //if (MembershipService

        this.Categories.Add("Audit");

        this.Categories.Add(auditCategory);

        this.Message = auditMsg;

        this.AuditCategory = auditCategory;

    }

 

    public string AuditCategory { get; set; }

}



This class adds an AuditCategory property, which is also added to the LogEntries categories so you can do filtering on it. The AuditCategory is important as it is is used to tie our LogEntries to the Expression Dictionary.

The next post in the series will explore the Expression Cache.

No comments: