Effective C# 原则36:利用.Net运行时诊断

JerryXia 发表于 , 阅读 (1,773)

当有问题发生时,它们往往并不是在实验的时候发生的,机器有轻松调试的工具。在很多实际情况中,你不好修正的问题总是发生在用户的机器上,那里没有调试环境,也没有好的方法计算出问题的情况。在实际情况中,有经验的开发人员会创建一个方法,让系统在运行时捕获尽可能多的信息。.Net框架已经包含一些类集合,利用这些集合,你可以做一些通用的调试。而且这些类可以在运行时或者编译时进行配置。如果你利用它们,你就可以轻松的发现在实际运行时的问题。使用框架里已经存在的代码,你可以发送一条诊断信息到一个文件,或者到调试终端。另外,你还可以为你的产品指定特殊的调试输出级别。你应该尽快的在你的开发环境中使用这些功能,以确保你可以利用这些输出信息来修正在实际运行中没有预料到的一些问题。不要自己写诊断库除非你已经明白框架已经提供了哪些。

System.Diagnostics.Debug,
System.Diagnostics.Trace和System.Diagnostics.EventLog类提供了你在运行程序时要创建诊断信息的所有工具。前面两个类功能是基本上是一样的。不同之外是Trace类是由预处理符TRACE控制的,而Debug类则是由DEBUG预处理符控制的。当你用VS.net开发一个项目时,TRACE符号是同时在调试版和发布版中定义的。你可以为所有的发布版使用Trace类来创建诊断信息。EventLog类提供了一个入口,通过这个入口,你的程序可以写一些系统日志。EventLog类不支持运行时配置,但你可以把它封装到一个统一的简单接口中。

你可以在运行时控制诊断输出,.Net框架使用一个应用程序配置文件来控制变化多样的运行时设置。这个是一个XML文件,在主应用程序运行时的目录中。这个文件与应用程序同名,但添加了一个.config后缀。务更制块例如MyApplication.exe
可能会有一个MyApplication.exe.config的XML文件来控制它。所所有的配置信息包含在一个configuration节点中:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

</configuration>

.Net框架使用预定义的关键字来控制框架中一些类的行为。另外,你可以定义你自己的配置关键字和值。

你可以组合输出开关和Trace.WriteLineIf()方法来控制应用程序的输出。你可以在应用程序外以默认的方式关闭这个输出,以便应用程序得到最好的性能。当发现问题时,你可以打开这个输出用于诊断和修正在实际中遇到的问题。WriteLineIf()只有在表达式为真时才输出:

bool _printDiagnostics = true;
Trace.WriteLineIf(_printDiagnostics, "Printing Diagnostics Today", "MySubSystem");

你所创建的输出开关用于控制输出的级别,一个输出开关可以是由应用程序配置文件定义的变量,可以是五种状态之一:关闭(Off),错误(Error),警告(Warning),信息(Info)和详细(Verbose)。这些状态是环境的一部份,而且它们的值可以是从0到4。这样你就可能为所有的子系统信息创建一个控制。
定义一个输出开关类然后初始化它就可以创建一个开关了:

static private TraceSwitch librarySwitch = new
  TraceSwitch("MyAssembly", "The switch for this assembly");

第一个参数是开关显示的名字,第二个参数是描述。这样,在运行时可以在应用程序配置文件中配置它们的值。下面就把librarySwitch设置成Info:

<system.diagnostics>
  <switches>
    <add name="MyAssembly" value="3" />
  </switches>
</system.diagnostics>

如果你编辑了这个配置文件中开关的值,那么就修改了所有由那个开关控制的输出语句。

另一个任务:你须要配置你的输出到什么地方去。
默认是一个链接到Trace类上的监听者:一个DefaultTraceListener对象。DefaultTraceListener发送信息到调试器,而且在它的失败方法(断言失败时调用)会打印一些诊断信息然后终止程序。在产品发布环境中,你不可能看到这样的信息。但你可是以配置不同的监听对象到产品发布环境中:那就是在应用程序的配置文件中添加监听者。下面就添加了一个TextWriterTraceListener
到应用程序中:

<system.diagnostics>
  <trace autoflush="true" indentsize="0">
    <listeners>
      <add name="MyListener"
        type="System.Diagnostics.TextWriterTraceListener"
        initializeData="MyListener.log"/>
    </listeners>
  </trace>
</system.diagnostics>

TextWriterTraceListener把所有的诊断信息到打印到一个MyListener.log文件中。名字属性指定了监听者的名字,类型指定了作者监听对象的类型,它必须是从System.Diagnostics.TraceListener派生下来的。只有在极少数情况下你才创建自己的监听类,那就是你觉得.Net框架的监听类不够用。initializeData的值是一个字符串,用于传给对象的构造函数。而TextWriterTraceListeners把它用于文件名。

你可以小做一个扩展,让它可以在应用中每个部署的程序集上都可以简单的使用。对于每个程序集,添加一个类来跟踪程序集创建的诊断:

internal class MyAssemblyDiagnostics
{
  static private TraceSwitch myAssemblySwitch =
    new TraceSwitch("MyAssembly", "The switch for this assembly");

  internal static void Msg( TraceLevel l, object o )
  {
    Trace.WriteLineIf(myAssemblySwitch.Level >= l, o, "MyAssembly");
  }

  internal static void Msg( TraceLevel l, string s )
  {
    Trace.WriteLineIf(myAssemblySwitch.Level >= l, s, "MyAssembly");
  }

  // Add additional output methods to suit.
}

MyAssemblyDiagnostices类根据一个开关来为这个程序集创建诊断信息。为了创建信息,调用按常规调用重载的Msg的任何一个就行了:

public void Method1( )
{
  MyAssemblyDiagnostics.Msg(TraceLevel.Info, "Entering Method1.");
  bool rVal = DoMoreWork( );
  if( rVal == false )
  {
    MyAssemblyDiagnostics.Msg(TraceLevel.Warning, "DoMoreWork Failed in Method1");
  }
  MyAssemblyDiagnostics.Msg(TraceLevel.Info, "Exiting Method1.");
}

利用一个全局的开关,你还可以组件特殊的程序集开关,来控制整个应用程序的输出:

internal static void Msg( TraceLevel l, object o )
{
  Trace.WriteLineIf(librarySwitch.Level >= l || globalSwitch.Level >= l, o, "MyLibrary");
}

internal static void Msg( TraceLevel l, string s )
{
  Trace.WriteLineIf( librarySwitch.Level >= l || globalSwitch.Level >= l, s, "MyLibrary");
}

这样,你就可以在应用程序上诊断信息,而且更友好的控制个别库文件的输出。在应用程序的任何地方,你都可以设置应用程序级的诊断到错误级,从而发现错误。当你有一个独立的问题时,你可以通过提高这个库的输出级别,从而精确的发现问题的源头。

在实际环境中,对于已经布署的应用程序,诊断库对于程序诊断和维护是必须的。但你自己不必写这些诊断库:.Net
FCL已经完成了核心的功能。尽可能完全的使用它们,然后在满足特殊要求时扩展它们。这样,即使是在产品发布的环境中也可以捕获所有的问题。

添加新评论