Friday, December 12, 2008

Logging to Database With Enterprise Library Part IV - Tieing it all together

In my last post we created all the plumbing that we needed to configure our logger. The final step is to create the actual logger. As a side note I want to say that tried to use TDD development for this piece; however, I have learned that TDD is not the greatest choice when your building plugins. I might elaborate on this in the future but anyways.

To create a custom trace listner for the Enterprise Library you need to derive from CustomTraceListener. You will then need to implement the TraceData function.

    [ConfigurationElementType(typeof(CustomTraceListenerData))]

    public class DBAuditLoggerTraceListener :

        Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.CustomTraceListener

    {

 

        public override void Write(string message)

        {

            throw new NotImplementedException("This method is not supported");

        }

 

        public override void WriteLine(string message)

        {

            throw new NotImplementedException("This method is not supported");

        }

 

        public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)

        {

 

            AuditLogEntry ale = data as AuditLogEntry;

            if (ale == null)

            {

                //Not an audit log entry

                return;

            }

            IAuditLoggerExpressionGroup logGroup;

            if (!AuditLoggerExpressionCache.Instance.TryGetValue(ale.AuditCategory, out logGroup))

            {

                //Category not registered

                return;

            }

 

            using (StoredProcedure sp = new StoredProcedure(logGroup.Procedure))

            {

                foreach (string key in logGroup.Keys)

                {

                    sp.AddParameter(key, logGroup.GetValue(key, ale));

                }

 

                sp.Execute();

            }

 

 

        }

 



A couple of things to note, The Stored Procedure class is my own class which I am unable to share; however, all it does is wrap up all the ADO.Net Goodness. It also handles my connection strings which is why I am not passing the connection string in. You should be able to quickly whip up your own ADO.Net code. If you run into issues let me know and I will be glad to help out.

Basically all we are doing is making sure we have an AuditLogEntry object, then we check if we have created an ExpressionGroup for this AuditCategory. If we have we simply loop through each of the keys in the ExpressionGroup, calling the GetValue method.

GetValue was needed to ensure that this class didn't have to know about the type parameter in the Expression Group. I'm hopping C#4 with co/contra variance will remove this requirement. Finally we execute the stored procedure.

The next step is to configure this. If your using an ASP.Net app I'd recommend that you configure your expression Groups in the Global.asax. Also remember to call the Cache() method. If you forget this you will not save the expression group you have created.

The final step it to add your logger to the EntLib configuration for example:

<loggingConfiguration name="Logging Application Block" tracingEnabled="true" defaultCategory="Error" logWarningsWhenNoCategoriesMatch="true">

    <listeners>

        <add  listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.CustomTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging,  Version=3.1.0.0"

              traceOutputOptions="None" type="DBAuditLoggerTraceListener, YourAssembly" name="AuditLogger" initializeData="" formatter="Text Formatter"/>

    </listeners>

    <formatters>

        <add template="Timestamp: {timestamp}&#xA;"

            type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging"

            name="Text Formatter"/>

    </formatters>

    <categorySources>

        <add switchValue="All" name="Audit">               

            <listeners>

                <add name="AuditLogger"/>                   

            </listeners>

        </add>   

    </categorySources>

</loggingConfiguration>



I just noticed I had left the text formatter in there which I have not used. I am not sure if this is required or not.

So after all of this what do we have? A logger that will take any AuditLogEntry and execute a stored procedure, with any number of parameters. I hope you find this useful. Please feel free to provide any feedback positive or negative.

Enjoy!

Update

I have extracted the source and posted online. Please note that this is not tested, so if you run into any issues let me know. When I get caught back up with my day job I will try and finish rounding this out.

Let me know if this link doesn't work. First time I've used skydrive so I'm not positive I have it all setup right.

Logging to Database With Enterprise Library Part III - Expression Cache

In my previous post we started to explore a solution for logging any arbitrary data to the database using the Enterprise Library.

Next on our list of things we need to create our Expressions. Not wanting to create the expressions each time we need to log the data, and not wanting to tie our method for extracting data from the log entry to the log entry, I opted to create what I am calling the AuditLoggerExpressionCache or Expression Cache for short. The expression cache uses the singleton pattern to ensure we only have one cache for our process.

The cache doesn't actually store the expressions directly but it stores a group of expressions, keyed off the AuditCategory.

public class AuditLoggerExpressionCache

{

    private Dictionary<string, IAuditLoggerExpressionGroup> _expressionGroups =

        new Dictionary<string, IAuditLoggerExpressionGroup>();

    private AuditLoggerExpressionCache() { }

 

    public void Add(IAuditLoggerExpressionGroup group)

    {

        this._expressionGroups.Add(group.Name, group);

    }

 

    public IAuditLoggerExpressionGroup this[string key]

    {

        get

        {

            return this._expressionGroups[key];

        }

    }

    #region Singleton Pattern

    public static AuditLoggerExpressionCache Instance

    {

        get

        {

            return singleton.Instance;

        }

    }

    private class singleton

    {

        internal static readonly AuditLoggerExpressionCache Instance = new AuditLoggerExpressionCache();

    }

    #endregion

 

    public AuditLoggerExpressionGroup<T> CreateGroup<T>(string category, string connection, string procedure)

    {

        return new AuditLoggerExpressionGroup<T>(category, connection, procedure);

    }

 

    public bool TryGetValue(string category, out IAuditLoggerExpressionGroup logGroup)

    {

        return this._expressionGroups.TryGetValue(category, out logGroup);

    }

}



I have defined an IAuditLoggerExpressionGroup interface which allows us to access the groups without having to know the generic parameter type. I am using the Generics to improve the syntax since you won't have to cast the AuditLogEntry in your lambda.

public interface IAuditLoggerExpressionGroup

{

string Name { get; set; }

string Connection { get; set; }

string Procedure { get; set; }

Delegate this[string key] { get; }

IList<string> Keys { get; }

object GetValue(string key, object param);

}



A couple of key properties. The name here is the audit category, the Connection is intended to be used to store the connection string, the Procedure is the name of the stored procedure to execute. Keys is the keys to the expression dictionary itself. I am using the keys also as the stored procedure parameter names.

Let's look at the ExpressionGroup:

public class AuditLoggerExpressionGroup<T> : IAuditLoggerExpressionGroup

{

 

    private Dictionary<string, Func<T, object>> _expressions =

        new Dictionary<string, Func<T, object>>();

 

    public AuditLoggerExpressionGroup(string name, string connection, string procedure)

    {

        this.Name = name;

        this.Connection = connection;

        this.Procedure = procedure;

    }

 

    #region IAuditLoggerExpressionGroup Members

 

    public string Name { get; set; }

    public string Connection { get; set; }

    public string Procedure { get; set; }

 

    public Func<T, object> this[string key]

    {

        get

        {

            return this._expressions[key];

        }

        set

        {

            this._expressions[key] = value;

        }

    }

    public IList<string> Keys

    {

        get

        {

            return this._expressions.Keys.ToList();

        }

    }

 

    public AuditLoggerExpressionGroup<T> Add(string key, Func<T, object> expression)

    {

        this._expressions.Add(key, expression);

        return this;

    }

 

    public AuditLoggerExpressionGroup<T> Cache()

    {

        Check.Require(!string.IsNullOrEmpty(this.Name), "You cannot cache something without a name");

        AuditLoggerExpressionCache.Instance.Add(this);

        return this;

    }

    public AuditLoggerExpressionGroup<T> Cache(string name)

    {

        this.Name = name;

        return this.Cache();

    }

 

    public object GetValue(string key, object param)

    {

        return this[key]((T)param);

    }

    Delegate IAuditLoggerExpressionGroup.this[string key]

    {

        get

        {

            return this[key] as Delegate;

        }

    }

    #endregion

 

}



One thing to note I am using the Design By Contract class. This is however optional but if your not using it, be sure to go have a look its a great additional to your programming toolbox. The expression group as you see in the Add method takes a key, and a Func. T is your specific AuditLogEntry class, and we are returning an object.

At this point your unit test should work, and the vast majority of the work has been done. The only thing left is to define and configure the actual trace listener. I will cover this in Part IV of this series.

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.

Wednesday, December 10, 2008

Microsoft Bizspark

Startups are by the very nature of them cash strapped. I cannot speak for all startups but from my personal experience, we were forced to scrap together to buy the software we needed to start our business. For example, we were using trial versions of visual studio to build our prototype, and when it ran out were able to get a professional license as a door prize. And latter we upgraded to a single MSDN Team Suite DB Edition license.

I've also mumbled to myself that Microsoft should help early stage startups, it trully is in their best interest, since if our company succeddes, we will be buying lots of their products. The MSDN licenses can run anywhere from 2k-5k dollars.

It turns Microsoft has been addressing this need, by essentially giving away their best development tools. The Microsoft Bizspark program gives an unlimited number of MSDN Team Suite licenses for a cost of $100.00 (payable when you exit the program). There are many other benefits in the program, but this one was great. Pitty we can't return our earlier MSDN subscribtion, but this will save us thousands of dollars as we add more developers and grow over the next couple of years.

You can also read more about the program here: http://blogs.msdn.com/somasegar/archive/2008/11/05/bizspark-software-for-startups.aspx

Friday, December 5, 2008

Using Self Signed Certs on Vista

I needed to enable certificates for my WCF service, I first went and added the following behavior to my service:


<behavior name="MyServiceBehavior">
<servicecredentials>
<servicecertificate findvalue="MyLocalHost" x509findtype="FindBySubjectName" storelocation="LocalMachine" storename="My">
</servicecredentials>
</behavior>

Then following the steps here: http://msdn.microsoft.com/en-us/library/ms733813.aspx I created a self signed certificate to add to the root and certificate to use for my service.

I then tried to bring up the services help page and I get this error:

The certificate 'CN=SignedByLocalHost' must have a private key that is capable of key exchange. The process must have access rights for the private key.

Doing some searching I found that you need to use winhttpCertCfg to give permissions to the process account. I also found that this tool is deprecated in Vista. It may or may not work but I wanted to figure out how to get this to work.

The suggested method was to use the MMC snap in to manage Private keys. You need to right click on the certificate and there should be a "Manage Private Keys" option under All Tasks, but it was there for me.

After some more diging I found you need to create the certificate for exchange. The following command worked:

makecert -sk SignedByCA -iv c:\OutCert.pvk -n "CN=MyLocalHost" -ic c:\OutCert.cer -sr LocalMachine -ss My -sky exchange -pe

The thing I left off was -sky exchange

The next error I ran into was:

The X.509 certificate CN=MyLocalHost chain building failed. The certificate that was used has a trust chain that cannot be verified. Replace the certificate or change the certificateValidationMode. The revocation function was unable to check revocation for the certificate.

To resolve this you need to do what it says (of course the trick is finding where to change this setting at). Add the following behavior to your client code



<behaviors>
<endpointBehaviors>
<behavior name="ssl">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="None"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>