65.9K
CodeProject 正在变化。 阅读更多。
Home

使用log4net和NHibernate将日志信息保存在数据库中

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (11投票s)

2010 年 2 月 4 日

CPOL

5分钟阅读

viewsIcon

94138

downloadIcon

3872

使用log4net在NHibernate的帮助下将数据保存在数据库中

CodeProjectLogging.PNG

引言

我们经常需要进行程序日志记录。因此,应该包括使用不同数据存储(文件、数据库、事件日志等)的可能性,以及控制日志信息级别的能力。

log4net 库是众多有助于解决所有这些问题的实现之一。

背景

log4net

log4net 是一款帮助程序员将日志语句输出到各种输出目标的工具。在应用程序出现问题时,启用日志记录有助于定位问题。使用 log4net,可以在运行时启用日志记录,而无需修改应用程序二进制文件。log4net 包的设计使得日志语句可以保留在已发布的代码中,而不会产生高昂的性能成本。因此,日志记录(或者说不记录)的速度至关重要。

同时,日志输出可能非常庞大,以至于很快就会让人不知所措。log4net 的一个显著特点是分层日志记录器(hierarchical loggers)的概念。使用这些日志记录器,可以以任意粒度选择性地控制哪些日志语句被输出。

特点

  • 支持多种框架
  • 输出到多个日志目标
  • 分层日志架构
  • XML 配置
  • 动态配置
  • 日志上下文
  • 久经考验的架构
  • 模块化和可扩展的设计
  • 高性能与灵活性

配置

log4net 使用 XML 配置文件进行配置。配置信息可以嵌入到其他 XML 配置文件中(例如应用程序的 .config 文件)或单独的文件中。log4net 可以监视其配置文件中的更改,并动态应用配置器所做的更改。日志级别、Appender、Layout 以及几乎所有其他内容都可以在运行时进行调整。

或者,log4net 也可以通过编程方式进行配置。

另请参见:官方 log4net 网站

NHibernate

NHibernate 是 Microsoft .NET 平台的一个对象关系映射(ORM)解决方案:它提供了一个框架,用于将面向对象的领域模型映射到传统的关系数据库。其目的是减轻开发人员在关系数据持久化相关的编程任务中的大部分负担。

特点

  • 自然的编程模型 - NHibernate 支持自然的 OO 习惯用法;继承、多态、组合和 .NET 集合框架,包括泛型集合。
  • 原生 .NET - NHibernate API 使用 .NET 约定和习惯用法。
  • 支持细粒度的对象模型 - 为集合和依赖对象提供丰富的映射。
  • 无构建时字节码增强 - 您的构建过程中没有额外的代码生成或字节码处理步骤。
  • 查询选项 - NHibernate 解决了问题的两个方面;不仅是如何将对象存入数据库,还有如何将其取出来。
  • 自定义 SQL - 指定 NHibernate 应使用的确切 SQL 来持久化您的对象。Microsoft SQL Server 支持存储过程。
  • 支持“会话” - NHibernate 支持长寿命的持久化上下文,对象的分离/重连接,并自动处理乐观锁定。

另请参见:官方 NHibernate 网站

Using the Code

该库中使用 log4net.Appender.AdoNetAppender 类来记录数据库信息。在此类的设置中,会给定一个 connectionType 元素,其 value 属性必须引用继承自 IDbConnection 的类。

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

在我的一项目中,使用了 NHibernate ORM 解决方案,并决定利用其功能将日志信息插入数据库。但我没有找到(也许存在,但我没找到)NHibernate 的任何支持。因此,我为 NHibernate.ISession 创建了一个包装器,可以用于 log4net 库。

我需要实现以下接口

  • IDbConnection - 表示到数据源的已打开连接,并由访问关系数据库的 .NET Framework 数据提供程序实现。在我的实现中,我使用 NHibernate 而不是 ADO.NET 类。

    • public 构造函数中,我创建了 NHibernate.ISessionFactory 对象。
    • Open 方法中,我打开了 NHibernate 会话。
    • BeginTransaction() 方法中,我通过 Session.BeginTransaction 创建了我的 HDbTransaction 对象。
    • ConnectionState 属性从 NHibernate.ISession 对象返回连接状态。
    • CreateCommand() 创建了我的 HDbCommand 对象。
    • Dispose() 方法关闭已打开的连接并调用 HNibernate.ISession Dispose() 方法。
    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
        }

    从源代码可以看出,没有必要实现此接口的所有方法和属性。

  • IDbTransaction - 表示将在数据源上执行的事务。在这里,我只是使用了 NHibernate.ITransaction 接口的相关方法。

    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 - 表示连接到数据源时执行的 SQL 语句。在我实现的这些方法中,最重要的是 ExecuteNonQuery() 方法,它执行数据库查询。

    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 - 表示 IDbCommand 对象的参数。实现很简单:我只是创建了具有 get/set 访问器的 public 属性。

    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 - 收集与 IDbCommand 对象相关的所有参数。在这里,我创建了 HDbDataParameter 集合,并使用了 LinqList实现必要功能的几种方法

    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  
    }

    GetEnumerator() 方法中,我返回了 IList.Enumerator() 的实现。

总的来说,我不需要编写复杂的代码。有些方法我留空了(带 NotImplementedException)。

然后,您需要创建一个数据库及其表。log4net 将使用 NHibernate 插入日志信息。这是 Microsoft SQL Server 的脚本。

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
)

DateThreadLevelLoggerMessage 列将由 log4net 填充数据。

然后,类 HDbConnection 和参数化插入查询必须在应用程序配置文件中指定。参数的描述与 AdoNetAppender 中的相同。

<!-- 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>

注意:在 NHibernate 中,查询中的参数必须以 ':' 开头。

用法

配置 log4net 后,我们就可以使用它将一些信息记录到数据库中。

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");       
        }
    }
}

之后,数据库中将出现以下条目:

DataLogging.PNG

由于 log4net 可以输出到多个日志目标,我们可以添加其他 Appender 来捕获与数据库相关的错误。在此示例中,记录将被添加到文件和控制台。

...
<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>

注释

  • Level”列是多余的。
  • 在此示例中,我使用了直接查询数据库的 NHibernate。我认为,与其使用直接插入查询,不如使用映射类。为此,您需要添加此类并更改 HDbCommand.ExecuteNonQuery() 方法的实现。

历史

  • 2010 年 2 月 2 日:文章的第一个版本
  • 2010 年 2 月 3 日:更新文章
  • 2010 年 2 月 4 日:更新文章
© . All rights reserved.