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.

18 comments:

Nitin R.K. said...

You should check out the Validation Application Block in the Enterprise Library. It's simply amazing!

Imagine being able to define validators with XML so you can change validations in your application without touching the code.

Josh Berke said...

I've never looked at it, but I think I will it sounds interesting

Nitin R.K. said...

There are 3 ways to go about defining validators - attributes, code or XML.

I whipped together a quick set of not null validations for a project I worked on earlier, but now that I've got some time I'm looking at the other validators and am blogging about them.

Perhaps you can check out my blog posts to get started:

Quick Start

http://knitinr.blogspot.com/2008/12/simple-ms-enterprise-library-validation.html


Types of Validators (1 of 2)

http://knitinr.blogspot.com/2008/12/enterprise-library-vab-validators-part.html


I'm coming up with another blog post on validators soon.


I didn't spend too much time with the Logging Application Block, but I'll make sure I catch up with all of your entries about it. So far the Enterprise Library Logging Application Block hasn't been my choice for logging - I prefer using Log4Net instead.

Josh Berke said...

I will have a look at your blog thanks. I've used log4net a bit (I'm using nHibernate so its sort of been forced into my environment). I started using EntLib back in v1 just for the logging block as I got tired of writting loggers, so I'm sort of used to how its configured and works.

My choice between the two is soley on famillarity. They both solve the same problem with great effectivness

Nitin R.K. said...

Hi Josh!

You haven't posted anything in a while. Are you away for the holiday?

-Nitin

Josh Berke said...

I'm here just extremly busy...and haven't come up with my next post yet. This is a really busy time of year for me.

Anonymous said...

Hi I followed this method. and getting an exception in override TraceData()

Unable to cast object of type 'CustomLogger.AuditLogEntry' to type 'CustomLogger.AuditLogEntry'.

Thanks for any help
J

Nitin R.K. said...

@J/Anonymous

Hi!

Can you post the source code & the config file, or send me your messenger (Yahoo/GTalk/MSN.. I rarely use Skype) ID?

-Nitin

Josh Berke said...

@Anon if your able to post your source somewhere that would be helpful. Which line is failing in the TraceData method? The code I have is doing a cast using the "as" operator, which will result in a null if it's unable to cast it.

Did you perhaps do AuditLogEntry ale = (AuditLogEntry)data;

That would result in the exception. If you need more help just give us more details, we'd love to help.

On a side note I am stoked that other people are actually looking at this and trying to get it working. Makes blogging worthwhile.

Nitin R.K. said...

Pastie.org has become really popular, mostly due to the Microsoft Zune source code that was posted on it :-)

The guys at Microsoft are probably upset over it - their source code (even though it's just a part) is online and available for all to see!

Josh Berke said...

Not to mention their source code which caused all zune's to hang...Examples like that remind me why I love developing server applications, a bug occurs, and I only have to go one place to fix it.

I'll look at pastie and if I can get some time I'll try and put all the source together out there. (I'll have to strip some propietery stuff so it's a little more involved then copy paste...)

Thanks Nitin

Miroslav Galajda said...

Hi, it would be great to publish source code for that. It looks great.
Thanks

Josh Berke said...

Thanks I'll see what I can do it's been a crazy time for me.

Miroslav Galajda said...

Hi, have you done some progress about publishing source code? :-)

Thanks

Mirec

Josh Berke said...

Actually I have made progress. I have extracted the source code from my closed source project. I have not been able to setup tests to make sure it is working. If you email me I'll send you what I have.

You can email by clicking the Email Author link at the end of this post (Above the comments)

Josh Berke said...

I'm not sure if the Email Author is working so you can email me joshua[dot]berke[at]gmail[dot]com.

Anonymous said...

Hey,
Great work Josh. You made my life a lot easier. Is there any way I can configure this trace listener in web/app.config "completely"? With stored procedures,connection and all that? How can I read them.
Thanks
Anil

Josh Berke said...

Configuring stored procedure and connection is very easy, we can create a custom config section.

It gets a little bit tricker if we wanted to store the lambda expressions. We'd need to compile then at runtime which I know is possible, but not sure how to do that.

Also once you do that you would loose the type checking, and refactoring functionality.