Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Save Log Information in Database with log4net and NHibernate

4.50/5 (11 votes)
4 Feb 2010CPOL5 min read 93.1K   3.8K  
Using log4net to save data in database with help of NHibernate
CodeProjectLogging.PNG

Introduction

Very often we have a necessity for program logging. Thus should include the possibility of using different data storage (files, databases, event logs, etc.) and the ability to control the level of information logging.

The library log4net is one of many implementations, which help to solve all these problems.

Background

log4net

log4net is a tool to help the programmer output log statements to a variety of output targets. In case of problems with an application, it is helpful to enable logging so that the problem can be located. With log4net it is possible to enable logging at runtime without modifying the application binary. The log4net package is designed so that log statements can remain in shipped code without incurring a high performance cost. It follows that the speed of logging (or rather not logging) is crucial.

At the same time, log output can be so voluminous that it quickly becomes overwhelming. One of the distinctive features of log4net is the notion of hierarchical loggers. Using these loggers, it is possible to selectively control which log statements are output at arbitrary granularity.

Features

  • Support for multiple frameworks
  • Output to multiple logging targets
  • Hierarchical logging architecture
  • XML configuration
  • Dynamic configuration
  • Logging context
  • Proven architecture
  • Modular and extensible design
  • High performance with flexibility

Configuration

log4net is configured using an XML configuration file. The configuration information can be embedded within other XML configuration files (such as the application's .config file) or in a separate file. log4net can monitor its configuration file for changes and dynamically apply changes made by the configurator. The logging levels, appenders, layouts, and just about everything else can be adjusted at runtime.

Alternatively log4net can be configured programmatically.

See also: official log4net site.

NHibernate

NHibernate is an Object-relational mapping (ORM) solution for the Microsoft .NET platform: it provides a framework for mapping an object-oriented domain model to a traditional relational database. Its purpose is to relieve the developer from a significant portion of relational data persistence-related programming tasks.

Features

  • Natural programming model - NHibernate supports natural OO idiom; inheritance, polymorphism, composition and the .NET collections framework, including generic collections
  • Native .NET - NHibernate API uses .NET conventions and idioms
  • Support for fine-grained object models - a rich variety of mappings for collections and dependent objects
  • No build-time bytecode enhancement - there's no extra code generation or bytecode processing steps in your build procedure
  • The query options - NHibernate addresses both sides of the problem; not only how to get objects into the database, but also how to get them out again
  • Custom SQL - specify the exact SQL that NHibernate should use to persist your objects. Stored procedures are supported on Microsoft SQL Server
  • Support for "conversations" - NHibernate supports long-lived persistence contexts, detach/reattach of objects, and takes care of optimistic locking automatically.

See also: official NHibernate site.

Using the Code

The log4net.Appender.AdoNetAppender class is used in that library for logging in the database. In the settings for this class, an element connectionType is given, which value attribute must refer to the class, inherited from IDbConnection:

XML
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
    <bufferSize value="100" />
    <connectionType value="System.Data.SqlClient.SqlConnection, System.Data/>
    ...
</appender> 

In one of my projects NHibernate ORM-solution was used, and I decided to use its capabilities to insert log information into the database. But I did not find (maybe it exists, but I simply didn't find) any support for this possibility for NHibernate. So I made a wrapper for NHibernate.ISession, which could be used in log4net library.

I needed to implement the following interfaces:

  • IDbConnection - represents an open connection to a data source, and is implemented by .NET Framework data providers that access relational databases. In my implementation, I use NHibernate instead of the ADO.NET classes:

    • In public constructor, I create NHibernate.ISessionFactory object
    • In Open method, I open NHibernate session
    • In BeginTransaction() method, I create my HDbTransaction object with the help of Session.BeginTransaction
    • ConnectionState property returns connection state from NHibernate.ISession object
    • CreateCommand() creates my HDbCommand object
    • Dispose() method closes open connection and calls HNibernate.ISession Dispose() method
    C#
    public class HDbConnection : System.Data.IDbConnection
        {
    
            private ISessionFactory _sessionFactory;
            private ISession Session { get; set; }
    
            public HDbConnection()
            {
                //Configure NHibernate
                Configuration config = new Configuration();
                config.Configure();
                config.AddAssembly(System.Reflection.Assembly.GetExecutingAssembly());
    
                _sessionFactory = config.BuildSessionFactory();
            }
    
            #region IDbConnection Members
    
            public System.Data.IDbTransaction BeginTransaction
    				(System.Data.IsolationLevel il)
            {
                //begin NHibernate transaction
                return new HDbTransaction(Session.BeginTransaction(), il);
            }
    
            public void Open()
            {
                //open NHibernate session
                Session = _sessionFactory.OpenSession();
            }
    
            public System.Data.ConnectionState State
            {
                get
                {
                    //return connection state
                    return Session.Connection.State;
                }
            }
    
            public void Close()
            {
                //Close session
                Session.Close();
            }
            public string ConnectionString { get; set; }
    
            public System.Data.IDbCommand CreateCommand()
            {
                //return our command
                return new HDbCommand(Session);
            }
    
           ...
            #endregion
    
            #region IDisposable Members
            public void Dispose()
            {
                //dispose session
                if(Session.IsOpen) Session.Close();
                Session.Dispose();
            }
            #endregion
        }

    As it's seen from the source, there was no necessity in the implementation of all methods and properties of this interface.

  • IDbTransaction - represents a transaction to be performed at a data source. Here I simply use the relevant methods of NHibernate.ITransaction interface:

    C#
    public class HDbTransaction : IDbTransaction
        {
            private ITransaction _transaction;
            private IsolationLevel _isolationLevel;
    
            public HDbTransaction(ITransaction transaction)
            {
                _transaction = transaction;
            }
            ...
            #region IDbTransaction Members
    
            public void Commit()
            {
                _transaction.Commit();
            }
    
            public void Rollback()
            {
                _transaction.Rollback();
            }
            ...
    
            #endregion
    
            #region IDisposable Members
    
            public void Dispose()
            {
                _transaction.Dispose();
            }
            #endregion
        }
  • IDbCommand - represents a SQL statement that is executed while connected to a data source. In my implementation of the most important thing is the method ExecuteNonQuery(), which does a database query:

    C#
    public class HDbCommand: IDbCommand
        {
            private ISession _session;
            public HDbCommand(ISession session)
            {
                //init NHibirnate session variable
                _session = session;
            }
            ...
    
            public int ExecuteNonQuery()
            {
                //get iquery object
                IQuery query = _session.CreateSQLQuery(CommandText);
    
                //add all parameters
                foreach (HDbDataParameter p in Parameters)
                    query = query.SetParameter(p.ParameterName, p.Value);
    
                //execute
                return query.List().Count;
            }
    }
  • IDbDataParameter - represents a parameter to an IDbCommand object. The implementation is trivial: I just created public properties with get/set accessors.

    C#
    public class HDbDataParameter : IDbDataParameter
        {
            #region IDbDataParameter Members
    
            public byte Precision { get; set; }
            public byte Scale { get; set; }
            public int Size { get; set; }
            public DbType DbType { get; set; }
            public ParameterDirection Direction { get; set; }
            public string ParameterName { get; set; }
            public string SourceColumn { get; set; }
            public DataRowVersion SourceVersion { get; set; }
            public object Value { get; set; }
            ...
            #endregion
        }
  • IDataParameterCollection - collects all parameters relevant to an IDbCommand object. Here I create HDbDataParameter collection and use Linq with List<hdbdataparameter /><hdbdataparameter> methods to realize the necessary functionality:

    C#
    public class HDbParameterCollection : IDataParameterCollection 
        {
            IList<HDbDataParameter> parameters = new List<HDbDataParameter>();
    
            public object this[string parameterName]
            {
                get
                {
                    return (from p in parameters
                           where p.ParameterName == parameterName
                           select p).SingleOrDefault();
                }
                set{throw new NotImplementedException(); }
            }
    ...
            #region IEnumerable Members
            public System.Collections.IEnumerator GetEnumerator()
            {
                return parameters.GetEnumerator();
            }
           #endregion  
    }

    In the GetEnumerator() method, I am returning IList.Enumerator() realization.

Generally, I do not need to write some complex code. Some of the methods I left empty (with NotImplementedException).

Then you need to create a database with a table. log4net will insert log information using NHibernate. Here is a script for Microsoft SQL Server.

SQL
CREATE TABLE [dbo].[Log] (
    [Id] [int] IDENTITY (1, 1) NOT NULL,
    [Date] [datetime] NOT NULL,
    [Thread] [varchar] (255) NOT NULL,
    [Level] [varchar] (50) NOT NULL,
    [Logger] [varchar] (255) NOT NULL,
    [Message] [varchar] (4000) NOT NULL
)

Columns Date, Thread, Level, Logger and Message will be filled with data by log4net.

Then the class HDbConnection and parameterized insert query must be specified in the application configuration file. The description of parameters are the same as in AdoNetAppender:

XML
<!-- Using standard AdoNet Appender -->
    <appender name="HibernateAdoNetAppender" type="log4net.Appender.AdoNetAppender">
      <!-- Use our connection class -->
      <connectionType value="HLogger.HDbConnection, HLoggerLibrary" />
      <!-- Sql insert query with parameters-->
      <commandText value="INSERT INTO Log (Date,Thread,Level,Logger,Message) 
		VALUES (:log_date, :thread, :log_level, :logger, :message)" />
      <!-- Parameters-->
      <parameter>
        <parameterName value="log_date" />
        <dbType value="DateTime" />
        <layout type="log4net.Layout.PatternLayout" 
		value="%date{yyyy'-'MM'-'dd HH':'mm':'ss'.'fff}" />
      </parameter>
      <parameter>
        <parameterName value="thread" />
        <dbType value="String" />
        <size value="255" />
        <layout type="log4net.Layout.PatternLayout" value="%thread" />
      </parameter>
      <parameter>
...
   </appender>

Note: In NHibernate, parameters in the query must begin with ':'

Usage

When log4net is configured, we can use it to log some information to the database:

C#
using log4net;

namespace HLoggerConsole
{
    class Program
    {
        static void Main(string[] args)
        {
	    //get our logger
            ILog log = log4net.LogManager.GetLogger("NHibernateLogging");
	
	    //log
            log.Error("Error!", new Exception("Exception"));
            log.Warn("Warning");       
        }
    }
}

After this, the following entries will appear in the database:

DataLogging.PNG

Because log4net can use output to multiple logging targets, we can add other Appender to catch errors associated with the database. In this example, the records will be added in a file and console:

XML
...
<appender name="ConsoleAppender"
              type="log4net.Appender.ConsoleAppender" >
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern"
           value="%d [%t] %-5p %c [%x]  [%X{auth}] - %m%n" />
      </layout>
    </appender>
...
 <appender name="LogFileAppender"
             type="log4net.Appender.FileAppender" >
      <param name="File" value="log-file.txt" />
      <param name="AppendToFile" value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <param name="Header" value="[Header]"><param name="Footer" value="[Footer]">
	<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] [%X{auth}] - %m%n">
      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMin" value="DEBUG" />
      </filter>
    </appender>

Notes

  • The column 'Level' is redundant.
  • In this example, I use a direct query to database with NHibernate. I think instead of using the direct insert query, it is better to use mapped class. To do this, you need to add this class and change the implementation of the method HDbCommand.ExecuteNonQuery().

History

  • 2nd February, 2010: First version of article
  • 3rd February, 2010: Updated article
  • 4th February, 2010: Updated article

License

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