代码之家  ›  专栏  ›  技术社区  ›  Carlos A. Ibarra

C#条件记录/跟踪

  •  8
  • Carlos A. Ibarra  · 技术社区  · 15 年前

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

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

    这样使用:

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

    你在C#中是怎么做到的?

    我已经阅读了Microsoft文档,其中解释了跟踪和调试功能 here ,他们声称使用#undef DEBUG和#undef TRACE会从生成的可执行文件中删除所有跟踪和调试代码,但它真的会删除整个调用吗?意思是,如果我写

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

    如果我取消定义跟踪,它不会调用昂贵的函数?还是打了电话, 确定它不会追踪任何东西?

    无论如何,即使它确实删除它,这也不如C++宏,因为我不能让C++中的那个大丑陋的调用看起来像我的简单的VoLoE()调用,并且仍然避免评估参数,我能吗?我也不能通过定义运行时的冗长来避免开销,就像我在C++中所能做到的那样,对吗?

    9 回复  |  直到 15 年前
        1
  •  9
  •   Andrew Arnott    15 年前

    为了回答您的一个问题,如果编译出Trace.WriteLine,则所有必须求值才能调用Trace.WriteLine(或其同级/同级)的方法调用都不会被调用。因此,请继续,直接将昂贵的方法调用作为跟踪调用的参数,如果不定义跟踪符号,它将在编译时被删除。

    现在是关于在运行时更改详细信息的另一个问题。这里的技巧是Trace.WriteLine和类似的方法将“params object[]args”作为字符串格式参数。只有当字符串实际发出时(当详细度设置得足够高时),该方法才会调用这些对象上的ToString以从中获取字符串。所以我经常玩的一个技巧是将对象而不是完全组装的字符串传递给这些方法,并将字符串创建留在我传递的对象的ToString中。这样,运行时性能税只在实际发生日志记录时支付,并且它允许您在不重新编译应用程序的情况下更改详细信息。

        2
  •  2
  •   Foredecker    15 年前

    一个对我有效的解决方案是使用 singleton 班它可以公开您的日志功能,您可以有效地控制其行为。让我们称这个类为“apploger”。她就是一个例子

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

    注意,上面的例子中没有提到单例。有很多很好的例子。现在有趣的是如何支持多线程。我是这样做的:(简称为简洁,哈哈哈)

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

        3
  •  2
  •   Brian    15 年前

    ConditionalAttribute 她是你最好的朋友。当未设置#define时,调用将被完全删除(就像调用站点被#if'd)。

    编辑:有人在评论中写了这句话(谢谢!),但在主要答案正文中值得注意:

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

        4
  •  2
  •   Mike Kelly    14 年前

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

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

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

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

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

        5
  •  1
  •   Tobias Langner Tobias Langner    15 年前

    您是否尝试过像log4net这样复杂的日志api( http://logging.apache.org/log4net/index.html

        6
  •  1
  •   Carlos A. Ibarra    15 年前

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

        7
  •  0
  •   shahkalpesh    15 年前

    “因为我不能让那个大丑的调用看起来像我的C++中简单的VLoW()调用——你可以在下面添加一个使用语句。

    using System.Diagnostics;
    
    ....
    Trace.WriteLineIf(.....)
    

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

        8
  •  0
  •   lc.    15 年前

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

    让它成为一个非常昂贵的功能(比如 Thread.Sleep(10000)

    (你可以把包裹起来 Trace.WriteLineIf() 打电话给 #if TRACE #endif

        9
  •  0
  •   Judah Gabriel Himango    15 年前

    它将调用昂贵的调用,因为它可能会产生预期的副作用。

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