C# 日志记录的性能技巧

Posted

技术标签:

【中文标题】C# 日志记录的性能技巧【英文标题】:Performance Tricks for C# Logging 【发布时间】:2011-02-06 20:34:50 【问题描述】:

我正在研究 C# 日志记录,如果消息低于日志记录阈值,我不希望我的日志消息花费任何时间进行处理。我能看到 log4net 所做的最好的事情是在评估日志参数之后进行阈值检查。

例子:

_logger.Debug( "My complicated log message " + thisFunctionTakesALongTime() + " will take a long time" )

即使阈值高于 Debug,仍然会评估 thisFunctionTakesALongTime。

在 log4net 中你应该使用 _logger.isDebugEnabled 所以你最终会得到 ​​p>

if( _logger.isDebugEnabled )
    _logger.Debug( "Much faster" )

我想知道是否有更好的 .net 日志记录解决方案,无需在每次我想记录时都进行检查。

在 C++ 中我可以这样做

LOG_DEBUG( "My complicated log message " + thisFunctionTakesALongTime() + " will take no time" )

因为我的 LOG_DEBUG 宏会自行检查日志级别。这让我可以在整个应用程序中使用 1 行日志消息,这是我非常喜欢的。有人知道在 C# 中复制这种行为的方法吗?

【问题讨论】:

【参考方案1】:

如果您可以面向 .NET 3.5 (C# 3.0),则可以使用 extension methods 包装 if 语句。

所以你可以做等效的“宏”:

logger.Log_Debug("Much faster");

logger.Log_Debug(() =>  "My complicated log message " + thisFunctionTakesALongTime() + " will take no time" );

通过在这个方法中包装检查:

public class Log4NetExtensionMethods 
    // simple string wrapper
    public void Log_Debug(this log4net.ILog logger, string logMessage) 
        if(logger.isDebugEnabled) 
             logger.Debug(logMessage);
        
    

    // this takes a delegate so you can delay execution
    // of a function call until you've determined it's necessary
    public void Log_Debug(this log4net.ILog logger, Func<string> logMessageDelegate) 
        if(logger.isDebugEnabled) 
             logger.Debug(logMessageDelegate());
        
    

【讨论】:

但是如果我正在评估函数以获取日志消息,这不会同样慢吗?如果日志级别低于我的阈值,我想避免所有函数评估。 您是正确的,您需要延迟函数评估。查看我使用匿名委托的更新。 D'oh,这也是我的回答。我应该警告你,如果你正在寻找速度,你不想使用代表。如果if (logger.isDebugEnabled) 围绕任何昂贵的东西,您应该接受打击并使用。没有既高性能又优雅的解决方案。 @bobbymcr:性能是相对的。委托调用与任何接口函数调用处于同一级别,thisFunctionTakesALongTime() 可以运行需要数天的蒙特卡罗模拟。 @Jeff:我指的是使用委托进行条件评估与简单的布尔检查之间的性能。请参阅下面的答案——在未启用日志记录时使用 lambda 会降低 20% 以上的性能。简单的规则是,如果性能至关重要,则不要使用后期绑定。【参考方案2】:

17.4.2 条件属性

Conditional 属性启用条件方法的定义。 Conditional 属性通过测试条件编译符号来指示条件。根据在调用点是否定义了此符号,是否包含或省略对条件方法的调用。如果定义了符号,则包含调用;否则,调用(包括调用参数的评估)将被省略。

[ Conditional("DEBUG") ]
public static void LogLine(string msg,string detail)

    Console.WriteLine("Log: 0 = 1",msg,detail);


public static void Main(string[] args)

    int Total = 0;
    for(int Lp = 1; Lp < 10; Lp++)
    
        LogLine("Total",Total.ToString());
        Total = Total + Lp;
    

【讨论】:

这很有趣,我会做更多的研究。 MSDN:msdn.microsoft.com/en-us/library/aa664622%28VS.71%29.aspx 我认为更好的日志库已经使用了这种技术。 仅仅因为您正在调用“logger.debug”方法并不意味着您实际上是在调试模式下运行。名称“调试”实际上只是应用程序开发人员定义的任意阈值的方法名称。如果 Log4Net、EnterpriseLibrary 等...实际上使用任何条件属性,我会感到惊讶,因为该属性依赖于编译符号的存在。 创建两个版本的 log 方法 - 有和没有条件编译,即 LogLine() 和 LogLineConditional(),因此您可以选择使用哪一个,例如在性能敏感区域使用 LogLineConditional()。使用 not "DEBUG" 的条件字符串也很有用,因此可以与 DEBUG 构建分开控制。【参考方案3】:

这里的问题是所有方法参数都必须在方法被调用之前进行评估。鉴于您使用的语法,没有办法解决这个问题。由于 C# 没有真正的预处理器或宏,因此您无法执行“LOG_DEBUG”之类的操作。你能做的最好的就是按照建议使用if (logger.isDebugEnable)

我唯一能想到的可能是使用 lambda 表达式之类的东西来延迟评估。但我要警告你,这几乎肯定最终会对性能产生更大影响

internal class Sample

    private static void Main(string[] args)
    
        DelayedEvaluationLogger.Debug(logger, () => "This is " + Expensive() + " to log.");
    

    private static string Expensive()
    
        // ...
    


internal static class DelayedEvaluationLogger

    public static void Debug(ILog logger, Func<string> logString)
    
        if (logger.isDebugEnabled)
        
            logger.Debug(logString());
        
    

【讨论】:

lambda 表达式有那么贵吗? @Charles:就后期绑定而言,lambda 相对便宜,但比布尔检查和方法调用贵得多。在我超过 10 亿次迭代的简单测试中(此处的代码:pastebin.com/aRtMtsxz),当未启用日志记录时,我测量了 lambda 与布尔检查的大约 25% 开销。使用缓存的 lambda(将 lambda 保存到一个字段中,这样它就不会每次都重新生成),开销略少(低于 20%)。【参考方案4】:

没有预处理器,您就是 SOL。当然,在将代码提供给 C# 编译器之前,没有什么可以阻止您使用它。

【讨论】:

匿名代表和额外的课程 - 哇,打字量很大。

以上是关于C# 日志记录的性能技巧的主要内容,如果未能解决你的问题,请参考以下文章

C#日志记录工具 MicroLog

C#日志记录工具 MicroLog

Laravel技巧之记录多日志

C# 记录错误日志

C# 使用ILogger接口编写日志

C# 记录错误日志