C#条件记录/跟踪

我想在我的C#应用​​程序中添加日志记录或跟踪,但是如果将日志详细级别设置得太低以至于不记录消息,我不希望记录格式化字符串或计算值的开销。

在C ++中,您可以使用预处理器来定义将阻止代码执行的宏,如下所示:

#define VLOG(level,expr) if (level >= g_log.verbosity) { g_log.output << expr; } 

像这样使用:

 VLOG(5,"Expensive function call returns " << ExpensiveFunctionCall()); 

你是怎么用C#做的?

我在这里阅读了解释Trace和Debug工具的Microsoft文档,他们声称使用#undef DEBUG和#undef TRACE从生成的可执行文件中删除所有跟踪和调试代码,但它是否真的删除了整个调用? 意思是,如果我写的话

 System.Diagnostics.Trace.WriteLineIf(g_log.verbosity>=5,ExpensiveFunctionCall()); 

如果我取消定义TRACE,它不会调用我昂贵的function吗? 或者是打电话, 然后决定它不会追踪任何东西?

无论如何,即使它确实删除它,这也不如C ++宏,因为我无法使那个大丑陋的调用看起来像我在C ++中简单的VLOG()调用,仍然避免评估参数,是吗? 我也不能像在C ++中那样在运行时定义详细程度来避免开销,对吧?

要回答您的一个问题,如果编译出Trace.WriteLine,则不会调用必须为了调用Trace.WriteLine(或其兄弟/兄弟)而调用的所有方法调用。 因此,继续将昂贵的方法调用直接作为Trace调用的参数,如果没有定义TRACE符号,它将在编译时被删除。

现在为您提供有关在运行时更改详细程度的其他问题。 这里的技巧是Trace.WriteLine和类似的方法采用’params object [] args’作为字符串格式化参数。 只有在实际发出字符串时(当详细程度设置得足够高时),该方法才会对这些对象调用ToString以从中获取字符串。 所以我经常玩的一个技巧是将对象而不是完全组装的字符串传递给这些方法,并将字符串创建保留在我传入的对象的ToString中。这样,运行时性能税只在日志实际发生时支付,它让您可以自由地更改详细程度,而无需重新编译您的应用程序。

对我有用的解决方案是使用单例类。 它可以公开您的日志记录function,您可以有效地控制其行为。 让我们调用类’AppLogger’。 她就是一个例子

 public class AppLogger { public void WriteLine(String format, params object[] args) { if ( LoggingEnabled ) { Console.WriteLine( format, args ); } } } 

注意,Singleton的东西不在上面的例子中。 管中有很多很好的例子。 有趣的是,如何支持multithreading。 我这样做了:(缩写为简洁,hahahaha)

 public static void WriteLine( String format, params object[] args ) { if ( TheInstance != null ) { TheInstance.TheCreatingThreadDispatcher.BeginInvoke( Instance.WriteLine_Signal, format, args ); } } 

这样,任何线程都可以记录,并在原始创建线程上处理消息。 或者您可以创建一个特殊的线程来处理日志输出。

ConditionalAttribute是你最好的朋友。 当#define未设置时,将完全删除呼叫(就像呼叫站点是#if’d)。

编辑:有人把它放在评论中(谢谢!),但值得注意的是在主答案体中:

Trace类的所有方法都使用Conditional(“TRACE”)进行修饰。 刚看到这个使用reflection器。

这意味着如果没有定义TRACE,Trace.Blah(……昂贵的……)会完全消失。

有关条件(跟踪)的所有信息都很好 – 但我认为您真正的问题是您希望将Trace调用留在生产代码中,但(通常)在运行时禁用它们,除非您遇到问题。

如果您正在使用TraceSource(我相信您应该使用它,而不是直接调用Trace,因为它可以在运行时为组件级别的跟踪提供更细粒度的控制),您可以执行以下操作:

 if (Component1TraceSource.ShouldTrace(TraceEventType.Verbose)) OutputExpensiveTraceInformation() 

这假设您能够在另一个函数中隔离跟踪参数(即它们主要依赖于当前类的成员,而不是对该代码所在的函数的参数进行昂贵的操作)。

这种方法的优点是因为JITer在需要时逐个函数地编译,如果“if”求值为false,则不仅不会调用函数 – 它甚至不会被JIT。 缺点是(a)你已经分离了这个调用和函数OutputExpensiveTraceInformation之间的跟踪级别的知识(例如,如果你将TraceEventType更改为TraceEventType.Information,那么它将无法工作,因为你永远不会甚至调用它,除非在本例中为TraceSource启用了详细级别跟踪)和(b)更多的代码要编写。

这种情况似乎是一个类似C的预处理器会有所帮助(因为它可以确保,例如,ShouldTrace的参数和最终的TraceEvent调用是相同的),但我理解为什么C#没有包括那个。

安德鲁建议在传递给TraceEvent的对象的.ToString方法中隔离昂贵的操作也是一个很好的建议; 在这种情况下,您可以开发一个对象,该对象仅用于Trace,您要传递要构建昂贵字符串表示的对象,并在跟踪对象的ToString方法中隔离该代码,而不是在TraceEvent调用的参数列表(即使在运行时未启用TraceLevel,也会导致它执行)。

希望这可以帮助。

你有没有像log4net( http://logging.apache.org/log4net/index.html )那样尝试过复杂的日志API?

其中两个答案(Andrew Arnott和Brian的答案)确实回答了我的部分问题。 如果TRACE或DEBUG是#undef’d,则应用于Trace和Debug类方法的ConditionalAttribute将导致对方法的所有调用都被删除,包括昂贵的参数评估。 谢谢!

对于第二部分,是否可以在运行时完全删除所有调用,而不是在编译时,我在log4net fac中找到了答案。 根据他们的说法,如果你在启动时设置一个只读属性,运行时将编译掉所有未通过测试的调用! 这不允许你在启动后更改它,但这很好,它比在编译时删除它们更好。

对于你的评论

“因为我无法使那个大丑陋的调用看起来像我在C ++中的简单VLOG()调用” – 你可以添加一个using语句作为例子如下。

使用System.Diagnostics;

 ....
 Trace.WriteLineIf(.....)

据我所知,如果你取消定义Trace符号,它将删除包含Trace的行。

我不确定,但你可以自己找出答案。

使它成为一个非常昂贵的function(如Thread.Sleep(10000) )并调用时间。 如果需要很长时间,那么无论如何它都在调用你的function。

(您可以使用#if TRACE#endif包装Trace.WriteLineIf()调用并再次测试它以进行基本比较。)

它将调用昂贵的调用,因为它可能具有所需的副作用。

你可以做的是用[条件(“TRACE”)]或[条件(“DEBUG”)]属性装饰你昂贵的方法。 如果未定义DEBUG或TRACE常量,则不会将该方法编译到最终的可执行文件中,也不会执行任何调用来执行昂贵的方法。