C# 不调试时的Log4Net消息构造成本

C# 不调试时的Log4Net消息构造成本,c#,log4net,C#,Log4net,根据log4net的文章,您应该在任何Log.debug语句之前检查是否启用了调试,以减少语句的构造成本。在任何日志语句之前,是否有更好的替代方法来检查(Log.IsDebugEnabled) Log4Net示例: if (log.IsDebugEnabled) { log.Debug("This is entry number: " + i ); } 我不想支付语句构造的开销,但也不想在每个日志语句之前进行检查 可以使用lambda表达式。比如: log.Debug(() =>

根据log4net的文章,您应该在任何Log.debug语句之前检查是否启用了调试,以减少语句的构造成本。在任何日志语句之前,是否有更好的替代方法来检查(Log.IsDebugEnabled)

Log4Net示例:

if (log.IsDebugEnabled)
{ 
    log.Debug("This is entry number: " + i );
}

我不想支付语句构造的开销,但也不想在每个日志语句之前进行检查

可以使用lambda表达式。比如:

log.Debug(() => "This is entry number:" + i);
这样,lambda只在.IsDebugEnabled调用之后计算

我们定义了一个扩展类(取自),它具有如下扩展方法:

public static class Log4NetExtensionMethods
{
    public static void Debug( this ILog log, Func<string> formattingCallback )
    {
        if( log.IsDebugEnabled )
        {
            log.Debug( formattingCallback() );
        }
    }
    // .. other methods as required...
}
公共静态类Log4NetExtensionMethods
{
公共静态无效调试(此ILog日志,Func formattingCallback)
{
如果(log.IsDebugEnabled)
{
Debug(formattingCallback());
}
}
//…需要的其他方法。。。
}

我不确定log4net是否在最近的版本中添加了lamda类型的支持,但这对我来说很有效。

我会看看预处理器(预编译?)指令

#if DEBUG
{your logging code here}
#endif
类似的东西应该可以为您完成,然后代码只能在调试模式下编译

您还可以在如下方法上使用
条件
属性:
[系统.诊断.条件(“调试”)]
private void YourMethodName此处(YourMethodSignatureHere)

看看这个老问题,了解更多关于何时/为什么/如何使用它们的信息

http://stackoverflow.com/questions/3788605/if-debug-vs-conditionaldebug

但也不想在每次日志语句之前检查

当你发现自己一遍又一遍地重复同一个代码时,听起来一个普通的抽象可能是正确的。例如,在这种情况下,您可以为Log4Net创建一个自定义包装器。简单到:

public static class Logger
{
    private static ILog _log;

    static Logger()
    {
        log4net.Config.XmlConfigurator.Configure();
        _log = log4net.LogManager.GetLogger("Log4Net");
    }

    public static void Debug(string message)
    {
        if (_log.IsDebugEnabled)
            _log.Debug(message);
    }

    public static void Info(string message)
    {
        _log.Info(message);
    }

    public static void Warn(string message)
    {
        _log.Warn(message);
    }

    public static void Error(string message)
    {
        _log.Error(message);
    }

    public static void Error(string message, Exception ex)
    {
        _log.Error(message, ex);
    }

    public static void Fatal(string message)
    {
        _log.Fatal(message);
    }

    public static void Fatal(string message, Exception ex)
    {
        _log.Fatal(message, ex);
    }
}
在本例中,我将记录器实例设置为静态。我不能百分之百肯定这会像预期的那样奏效。通常我在依赖项注入框架后面使用它,并将记录器依赖项配置为单例,由框架处理。您可以改为使用实例方法将其作为实例类,并将其放在静态工厂类之后。根据需要进行测试和调整

这里还有几个额外的好处:

  • Log4Net中的依赖关系被隔离到单个类。因此,如果您想要使用不同的记录器,只需更改一个类,而不是整个项目中的所有内容
  • 您可以很容易地将其抽象到依赖项注入器后面
  • 您希望包含在所有日志语句中的任何其他通用功能都可以轻松地全局包含在此处
  • 我通常用于第三个好处的示例可能如下:

    private static string GetLocation()
    {
        var frame = new StackTrace(1).GetFrame(1);
        var method = frame.GetMethod();
        return string.Format("{0}:{1}.{2}({3})", Environment.MachineName, method.ReflectedType.FullName, method.Name, frame.GetFileLineNumber().ToString());
    }
    
    public void Error(string message, Exception ex)
    {
        _log.Error(string.Format("{0}:{1}", GetLocation(), message), ex);
    }
    
    public class MyClass
    {
      private static readonly ILog log = new MyLoggerWrapper(typeof(MyClass));
    
      public void DoSomething()
      {
        log.Info("Hello world!");
      }
    }
    
    这可以从运行时系统中获得更有意义的调试信息(尽管可能会影响性能,但对于大容量系统,它值得测试)。因此,我的传递错误日志函数可能如下所示:

    private static string GetLocation()
    {
        var frame = new StackTrace(1).GetFrame(1);
        var method = frame.GetMethod();
        return string.Format("{0}:{1}.{2}({3})", Environment.MachineName, method.ReflectedType.FullName, method.Name, frame.GetFileLineNumber().ToString());
    }
    
    public void Error(string message, Exception ex)
    {
        _log.Error(string.Format("{0}:{1}", GetLocation(), message), ex);
    }
    
    public class MyClass
    {
      private static readonly ILog log = new MyLoggerWrapper(typeof(MyClass));
    
      public void DoSomething()
      {
        log.Info("Hello world!");
      }
    }
    

    @格姆和大卫都有好主意,但我认为大卫的包装纸并没有想象中那么好。这样包装log4net。简单地在包装器上实现Debug、Info等,并将它们委托给log4net的Debug、Info等方法,就会破坏log4net记录调用站点信息的能力。如果以这种方式包装并告诉log4net记录调用站点信息,log4net将在包装器中写出调用站点,而不是实际代码中的调用站点,这正是您想要的

    我个人不喜欢使用单例记录器,因为您无法在程序的不同部分调整日志记录级别。如果您正在处理多个组件,您可能希望为一个组件启用信息级日志记录,但只为其他组件启用警告日志记录(或者根本不启用)。使用单例记录器,所有应用程序中的所有日志都将处于同一级别

    当您错误地包装log4net时,以及当您使用单个记录器覆盖整个应用程序时,您否认了log4net的许多内置(和强大)功能

    我在这里回答了一个类似的问题(关于维护呼叫站点信息):

    为了节省时间,我在这里包含了一个代码示例(未编译和未测试,但应该很接近)

    编写保留调用站点信息的log4net包装器的诀窍是使用Log方法并将包装器的类型作为第一个参数传递

    如果您打算编写一个包装器来实现您所要求的功能(延迟日志调用中任何昂贵代码的执行,而不显式检查所需日志级别是否已启用),那么您最好将该代码放入包装器中,而不是将其作为扩展方法来实现(这也将遭受我上面描述的呼叫站点丢失问题)


    祝你好运!

    最简单、最干净的方法可能是使用
    DebugFormat
    方法,该方法实际上检查调试级别是否已启用(请参阅)。

    如果包含名称空间log4net.Util,则可以在log4net ILog上调用以下扩展方法:

    public static void ErrorExt(this ILog logger, Func<object> callback)
    
    publicstaticvoiderrorext(此ILog记录器,Func回调)
    

    这将仅在启用日志错误级别时调用lambda函数。无需编写自己的扩展方法。它还可以通过将创建内容包装在try-catch方法中来防止在构造实际日志消息时创建错误。

    这些语句在哪里?它们是否一致使用(即,您正在将它们复制并粘贴到同一类型的位置)?这与检查启用了哪些日志记录级别无关(启用了调试日志记录!=#如果启用调试)我强烈建议不要这样做。使用Log4Net的全部意义在于,您可以启用、禁用或重新配置日志记录,而无需重新编译应用程序——这是行不通的