Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

NLog Log and Audit Advanced Target

5.00/5 (3 votes)
26 May 2010CPOL6 min read 44K   751  
A way to audit your business objects using NLog.

This a completely refactored version of this API. A lot has changed since the last post, and I'll put some extra effort explaining the main aim of this tool. The demo project was greatly improved too. Just configure the connection string on the NLog.config file to point to a valid SQL Server database and you're ready to go.

Introduction

This is a custom target for NLog that allows you to keep track of changes made on objects. With this API, you will be able to respond to the following questions:

  • Who changed this object?
  • When was this object changed?
  • What was it changed? (Historical list or on a specific change.)
  • How was my object on a certain point in time?

The usage is pretty basic, just pass the object to NLog and it will store any changes made to it since the last time.

It is highly advisable that you do a small test using nothing but NLog before you start using this custom target.

This code was tested under NLog version 1.0 Refresh. Everything is done on VS2010 but I didn't use any .net 4.0 new functionality, so you can compile it nicelly on 2.0. Please download NLog from: http://nlog-project.org/download.html.

Background

While developing my own core modules meant to be a base start point for all my applications, I thought that it would be a good idea to have a way to log certain actions within those applications. Furthermore, I also wanted to know who changed an object when and what actually have been changed. For this, we usually develop a History table that is nothing more than a replica of the original table, and every time a change is made to that original table, we insert the same row on the History table. This way, we're able to know the history of an object... nice! :) But... what was actually changed? If a single column was changed or all of them, we really don't know immediately. Another problem is the size... why am I duplicating all the columns if I only changed one field? Plus, if I add a property to the object, I need to bother about updating the replication code between the original and the History table.

How does it do it?

Using Reflection, I can enumerate the object properties and match with what is already stored on the database.

I may not want to customize every property of my object. Can I customize what's logged?

Sure! This is a major requirement. You have two ways of doing it:

  • Applying attributes to the object class and properties.
  • Adding the expected types and their properties to be logged on the target configuration.

Where does it save the changes?

This version only supports SQL Server, although it is developed applying a Factory pattern that permits the development of other database providers.

Query the data

All this isn't of any use if you can't get the history of logs.

So for now, you can:

  • Get the list of modifications
  • Get the object's last version
  • Get object version at a given date

Using the code

To log a Customer class, you just have to do as follows:

C#
// here 'customer' is the business object I want to log.
NLog.Logger logger = LogManager.GetCurrentClassLogger();
logger.Trace(customer);

If you want to add who did the change, you also need to pass an OHAuditInfo with that extra information:

C#
// here 'customer' is the business object I want to log.
NLog.Logger logger = LogManager.GetCurrentClassLogger();
logger.Info(string.Empty, customer, new OHAuditInfo() { UserName = "Admin" });

I'll be adding mode fields to this OHAuditInfo object.

Currently, I intend to add:

  • ActionType: where you can specify the kind of change (Insert, Update, Delete, ...)
  • ExtraInfo: free text field where you will be able to add anything you want (text, XML, ...)

Basically what is shown above is an instance of the Customer class with some dummy data being logged. As you can see, you just need to pass the customer to NLog.

Configuration

NLog and this custom target know nothing about your classes, so we must instruct them what to do if they receive a Customer class type to be logged.

This configuration can be done either directly on the object's code or on the target XML configuration. If an object has both configurations defined, the XML will prevail.

Code attributes configuration

On code, you need to tell NLog that the class is to be logged and which properties are to be logged. For that, you have the following attributes:

  • [Log]
    • Apply this attribute to the class you want to be logged.
  • [LogKey]
    • Every "loggable" object needs to have a property that uniquely identifies it.
    • This attribute specifies that property.
  • [LogProperty]
    • Applied to a property marks it to be considered in the logging process.
    • Only properties that have this attribute are saved to the logging database.
    • Attributes can be added or removed anytime you see fit.

XML Configuration

  • ConnectionString (Mandatory)
    • Specify the connection string to be used.
  • DbProviderInvariantName (Mandatory)
    • This is how this target identifies what DbClient to use.
    • Use it the same way you use it in the ADO.NET Factory.
  • DBTablesPrefix
    • Use this if you want to add a prefix to the table names.
    • This is useful either to avoid naming collisions with existing tables on the database and to keep all logging tables together.
    • By default, there isn't any prefix assigned.
  • AutoCreateDatabase
    • Use this if you don't want to care about if the database already has the necessary tables or not.
    • Setting this option to true will make the target to validate the database schema and update it if necessary.
    • I advise to set this option off. Only turn it on at first run, or when performing a version update.
    • By default, this option is true.
  • LogTypes
    • This is where we tell this NLog target what types it should be expecting.
    • Configuring a type here will override any configuration made directly on the class code using the [LogKey] and [LogProperty] attributes.
    • Configuring expected types here must go with the following rules:
      • Each type configuration is separated by a |
      • Each configuration argument is separated by a ;
      • An argument is defined as: Name:Value
      • Properties argument specify the properties of the type to consider; each property name must be separated by a ,
    • Configuration arguments
      • name: Fully qualified name of the type, e.g.: App.Logger.WebTests.BusinessObjects.Supplier
      • key: Name of the property that represents the unique identifier of the object
      • properties: properties to be logged

Here's the NLog.config example used in the demo.

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <extensions>
      <add assembly="App.Logger"/>
    </extensions>

    <targets>
      <target name="OHTarget" type="OHTarget"
              connectionString="Server=.;Database=MyDatabase;UID=sa;PWD=MyP@ssw0rd;"
              DBTablesPrefix=""
              DbProviderInvariantName="System.Data.SqlClient"
              AutoCreateDatabase="True"
              LogTypes="name:App.Logger.WebTests.BusinessObjects.
                Supplier;key:SupplierID;properties:CompanyName,InvoicingAddress,Balance;"
              />
    </targets>
    <rules>
      <logger name="*" minlevel="Trace" writeTo="OHTarget" />
    </rules>

  </nlog>

</configuration>

Points of interest

This started as a test project to dig into NLog. After this, I did a couple of side developments that you may find interesting:

  • NLog custom target creation.
    • Custom target configuration on the NLog.Config file.
  • Implement auditing on applications without much effort!
  • Use of Reflection to get the loggable properties and their values.
  • Database automatic update and script versioning so you don't have to worry about the storage schema creation or update.

History

  • [2010-05-26] - Deep refactoring
    • Improved Factory model for multiple database engine support
    • No longer needs to receive an OHInfo object
      • Now we can just pass the object to be logged
    • Added optional parameter OHAuditInfo to add audit info
    • Added support for XML configuration
      • Now the loggable properties can be configured on the target configuration XML
    • Minor bugs and fixes
  • [2010-05-11] - Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)