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

log4net 的一个简单封装

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (9投票s)

2011 年 5 月 12 日

CPOL

3分钟阅读

viewsIcon

69169

downloadIcon

1226

log4net 的一个封装,允许用一行代码完成日志记录操作。

引言

log4net 是一个适用于 .NET 的高度灵活的日志记录框架。 首先,我想说明的是,本文假设您对 log4net 框架有基本的了解,并非 log4net 的入门教程,也不是关于如何配置和使用 log4net 的指南。

如果您不熟悉 log4net,可以在这里找到相关文档,或者在 CodeProject 上查看这篇文章

问题

我对 log4net 的主要批评之一是,我发现它的语法有些笨拙。例如,添加一行简单的调试日志的代码如下

// C#
ILog log = LogManager.GetLogger("MyLog");
if (log.IsDebugEnabled)
{
    using (ThreadContext.Stacks["MyProperty"].Push("MyValue"))
    {
        using(ThreadContext.Stacks["MyOtherProperty"].Push("MyOtherValue"))
        {
            log.Debug("This is a debugging message.");
        }
    }
}
' Visual Basic
Dim log As ILog = LogManager.GetLogger("MyLog")
If log.IsDebugEnabled Then
    Using ThreadContext.Stacks("MyProperty").Push("MyValue")
        Using ThreadContext.Stacks("MyOtherProperty").Push("MyOtherValue")
            log.Debug("This is a debugging message.")
        End Using
    End Using
End If

正如您所看到的,执行一个简单的日志记录操作需要大约六行代码。 显然,即使对于最小的应用程序,仅出于日志记录目的,将代码中布满像上面所示的语句,也绝对是不希望看到的,并且随着日志代码开始淹没逻辑代码,很快就会变得难以阅读。

需要的是一种可以通过一行代码完成日志记录操作的解决方案。

解决方案

我的解决方案是一个名为 Logger 的静态类,它有两个方法(加上重载):Initialize() 用于初始化 log4net,Log() 实际执行日志记录操作

首先,让我们看看 Initialize() 方法。 它有两个重载:一个接受单个参数,指定 log4net 使用的配置文件。 无参数重载指定 log4net 应从 *web.config* 或 *app.config* 中提取其配置

// C#
private static bool isInitialized;

public static void Initialize()
{
    Initialize(null);
}

public static void Initialize(string configFile)
{
    if (!isInitialized)
    {
        if (!String.IsNullOrEmpty(configFile))
            XmlConfigurator.ConfigureAndWatch(new FileInfo(configFile));
        else
            XmlConfigurator.Configure();
        isInitialized = true;
    }
    else
        throw new LoggingInitializationException(
              "Logging has already been initialized.");
}
' Visual Basic
Private _isInitialized As Boolean

Public Sub Initialize()
    Initialize(Nothing)
End Sub

Public Sub Initialize(ByVal configFile As String)
    If Not _isInitialized Then
        If Not String.IsNullOrEmpty(configFile) Then
            XmlConfigurator.ConfigureAndWatch(New FileInfo(configFile))
        Else
            XmlConfigurator.Configure()
        End If
        _isInitialized = True
    Else
        Throw New LoggingInitializationException("Logging has already been initialized.")
    End If
End Sub

接下来,我们将看看 Log() 方法及其重载。 这些方法接受以下参数的各种组合

参数 描述
logName 要将事件写入的日志的名称。 如果省略,则事件将写入所有日志。
loggingLevel 事件的级别(调试、信息、警告、错误或致命)。
message 消息。
loggingProperties 其属性映射到 log4net PatternLayout 属性的对象(例如:%property{Foo},其中 Foo 是属性的名称)。
exception 要记录的异常(如果有)。

所有 Log() 方法重载最终都会调用 LogBase(),这是实际调用 log4net 的地方。 此方法的代码如下

// C#
private static void LogBase(ILog log, LoggingLevel loggingLevel, 
        string message, object loggingProperties, Exception exception)
{
    if (ShouldLog(log, loggingLevel))
    {
        PushLoggingProperties(loggingProperties);
        switch (loggingLevel)
        {
            case LoggingLevel.Debug: log.Debug(message, exception); break;
            case LoggingLevel.Info: log.Info(message, exception); break;
            case LoggingLevel.Warning: log.Warn(message, exception); break;
            case LoggingLevel.Error: log.Error(message, exception); break;
            case LoggingLevel.Fatal: log.Fatal(message, exception); break;
        }
        PopLoggingProperties(loggingProperties);
    }
}
' Visual Basic
Private Sub LogBase(ByVal log As ILog, ByVal loggingLevel As LoggingLevel, _
        ByVal message As String, ByVal loggingProperties _
        As Object, ByVal exception As Exception)
    If ShouldLog(log, loggingLevel) Then
        PushLoggingProperties(loggingProperties)
        Select Case loggingLevel
            Case VisualBasic.LoggingLevel.Debug
                log.Debug(message, exception)
            Case VisualBasic.LoggingLevel.Info
                log.Info(message, exception)
            Case VisualBasic.LoggingLevel.Warning
                log.Warn(message, exception)
            Case VisualBasic.LoggingLevel.Error
                log.Error(message, exception)
            Case VisualBasic.LoggingLevel.Fatal
                log.Fatal(message, exception)
        End Select
        PopLoggingProperties(loggingProperties)
    End If
End Sub

正如您所看到的,此方法发出的第一个调用是 ShouldLog() 方法,该方法根据当前的日志记录级别确定是否应记录事件

// C#
private static bool ShouldLog(ILog log, LoggingLevel loggingLevel)
{
    switch (loggingLevel)
    {
        case LoggingLevel.Debug: return log.IsDebugEnabled;
        case LoggingLevel.Info: return log.IsInfoEnabled;
        case LoggingLevel.Warning: return log.IsWarnEnabled;
        case LoggingLevel.Error: return log.IsErrorEnabled;
        case LoggingLevel.Fatal: return log.IsFatalEnabled;
        default: return false;
    }
}
' Visual Basic
Private Function ShouldLog(ByVal log As ILog, _
        ByVal loggingLevel As LoggingLevel) As Boolean
    Select Case loggingLevel
        Case VisualBasic.LoggingLevel.Debug
            Return log.IsDebugEnabled
        Case VisualBasic.LoggingLevel.Info
            Return log.IsInfoEnabled
        Case VisualBasic.LoggingLevel.Warning
            Return log.IsWarnEnabled
        Case VisualBasic.LoggingLevel.Error
            Return log.IsErrorEnabled
        Case VisualBasic.LoggingLevel.Fatal
            Return log.IsFatalEnabled
        Case Else
            Return False
    End Select
End Function

如果此方法返回 true,则要调用的下一个方法是 PushLoggingProperties()。 这需要 loggingProperties 参数,并使用反射将每个属性的值推送到 log4net ThreadContext 堆栈上

// C#
private static void PushLoggingProperties(object loggingProperties)
{
    if (loggingProperties != null)
    {
        Type attrType = loggingProperties.GetType();
        PropertyInfo[] properties = attrType.GetProperties(
                       BindingFlags.Public | BindingFlags.Instance);
        for (int i = 0; i < properties.Length; i++)
        {
            object value = properties[i].GetValue(loggingProperties, null);
            if (value != null)
                ThreadContext.Stacks[properties[i].Name].Push(value.ToString());
        }
    }
}
' Visual Basic
Private Sub PushLoggingProperties(ByVal loggingProperties As Object)
    If Not loggingProperties Is Nothing Then
        Dim attrType As Type = loggingProperties.GetType()
        Dim properties() As PropertyInfo = attrType.GetProperties(
                         BindingFlags.Public Or BindingFlags.Instance)
        For i As Integer = 0 To properties.Length - 1 Step +1
            Dim value As Object = properties(i).GetValue(loggingProperties, Nothing)
            If Not value Is Nothing Then
                ThreadContext.Stacks(properties(i).Name).Push(value.ToString())
            End If
        Next
    End If
End Sub

接下来,代码基于当前的日志记录级别在 log4net ILog 对象上调用适当的日志记录方法。 接下来调用 PopLoggingProperties(),它只是执行 PushLoggingProperties() 的相反操作,并将属性从堆栈中弹出。

一些示例

以下是一些 Logger 类在实际操作中的示例。 在这些示例中,我们使用以下 PatternLayout

<layout type="log4net.Layout.PatternLayout">
    <conversionPattern 
     value="%date [%thread] %-5level %logger [%property{User}]: 
            %{property{Environment}  - %message%newline%exception" />
</layout>
// C#
// Initialise log4net
Logger.Initialize();

// Write a debugging event to all logs.
Logger.Log(LoggingLevel.Debug, "Calling method Foo().", 
           new { User = "Joe Bloggs", Environment = "UAT" });

// Write an exception to the log called "ErrorLog".
try
{
    int i = 25;
    int j = 0;
    int foo = i / j;
}
catch (DivideByZeroException ex)
{
    Logger.Log("ErrorLog", LoggingLevel.Error, "Attempted to divide by zero.",
    new { User = "Fred Bloggs", Environment = "Production" }, ex);
}
' Visual Basic
' Initialise log4net
Logger.Initialize()

' Write a debugging event to all logs.
Logger.Log(LoggingLevel.Debug, "Calling method Foo().", _
   New With {.User = "Joe Bloggs", .Environment = "UAT"})

' Write an exception to the log called "ErrorLog".
Try
    Dim i As Integer = 25
    Dim j As Integer = 0
    Dim foo As Integer = CInt(i / j)
Catch ex As DivideByZeroException
    Logger.Log("ErrorLog", LoggingLevel.Error, "Attempted to divide by zero.", _
    New With {.User = "Fred Bloggs", .Environment = "Production"}, ex)
End Try

摘要

通过一个简单的封装器,可以抽象出与 log4net 框架交互的代码,同时通过将日志记录操作减少到单行来提高代码的可读性。

您可以从这里下载完整的代码。

© . All rights reserved.