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






4.50/5 (11投票s)
使用log4net在NHibernate的帮助下将数据保存在数据库中

引言
我们经常需要进行程序日志记录。因此,应该包括使用不同数据存储(文件、数据库、事件日志等)的可能性,以及控制日志信息级别的能力。
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
集合,并使用了Linq
和List
。实现必要功能的几种方法 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
)
Date
、Thread
、Level
、Logger
和 Message
列将由 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");
}
}
}
之后,数据库中将出现以下条目:

由于 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 日:更新文章