现在的位置: 首页 > 综合 > 正文

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

2011年03月26日 ⁄ 综合 ⁄ 共 10596字 ⁄ 字号 评论关闭

Effective C# 原则36:利用.Net运行时诊断
Item 36: Leverage .NET Runtime Diagnostics

当有问题发生时,它们往往并不是在实验的时候发生的,机器有轻松调试的工具。在很多实际情况中,你不好修正的问题总是发生在用户的机器上,那里没有调试环境,也没有好的方法计算出问题的情况。在实际情况中,有经验的开发人员会创建一个方法,让系统在运行时捕获尽可能多的信息。.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已经完成了核心的功能。尽可能完全的使用它们,然后在满足特殊要求时扩展它们。这样,即使是在产品发布的环境中也可以捕获所有的问题。
==================
   

Item 36: Leverage .NET Runtime Diagnostics
Problems happen. They don't always happen in the lab, on machines you can easily debug. The problems you can't fix always seem to occur on one user's machine in the field, with no debugging environment and no way to figure out the cause. Experienced developers have learned to build in the capability to capture as much information as possible from systems running in the field. The .NET Framework includes a set of classes that you can use to generate diagnostics. These are configurable at runtime or compile time. If you leverage them, you can more quickly find problems that occur only in the field. Using code already in the framework, you can send diagnostic messages to a file, to the system logger, or to a debugging terminal. In addition, you can specify the level of debugging output that your program produces. You should use these features early in your development and make sure that you can produce the output you need to fix unanticipated problems in the field. Don't write your own diagnostic library until you understand what's already provided.

The System.Diagnostics.Debug, System.Diagnostics.Trace, and System.Diagnostics.EventLog classes provide all the tools you need to create diagnostic information from a running program. The first two classes have almost identical capabilities. The difference is that the trace class methods are controlled by the TRACE preprocessor symbol, and the Debug class methods are controlled by the DEBUG preprocessor symbol. When you create a project with VS .NET, the trACE symbol is defined for both release and debug builds, while the DEBUG symbol is defined only for debug builds. You create all your release build diagnostics using the TRace class. The EventLog class provides entry points so that your application can write to the system event log. The EventLog class does not support runtime configuration, but you can wrap it to conform to the same interface illustrated shortly.

You can also control the diagnostic output at runtime. The .NET Framework uses an application-configuration file to control a variety of runtime settings. This file is an XML document, located in the same directory as the main executable. The file shares the same name as the executable, with .config appended. For example, MyApplication.exe would be controlled by the MyApplication.exe.config XML document. All the configuration information is contained in a configuration node:

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

</configuration>

 

The .NET Framework uses predefined keys to control the behavior of framework classes. In addition, you can define your own configuration keys and values.

You combine the TRace.WriteLineIf() method and traceSwitches to control the granularity of the output that your application generates. You turn off output by default so that you get the most performance possible out of your application. When you find problems, you can ratchet up the output to diagnose and correct any problems you find in the field. WriteLineIf() generates output only when an expression evaluates to true:

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

 

You create traceSwitches to control the level of output. A traceSwitch is a variable set using the application-configuration file to one of five states: Off, Error, Warning, Info, and Verbose. These states are part of an enumeration and have values from 0 to 4. You can create a switch for each subsystem to control its messages. To create the switch, declare a variable of the traceSwitch class and construct it:

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

 

The first parameter is the display name for the switch; the second parameter is the description. You set the value of the switch at runtime in the application configuration file. The following snippet sets the librarySwitch to Info:

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

 

If you edit the config file's value of the switch, you modify the output generated by all statements controlled by that switch.

One more task: You need to configure where your trace output goes. By default, one listener is connected to the TRace class: a DefaultTraceListener object. The DefaultTraceListener sends messages to the debugger, and its Fail method (called when asserts fail) prints a diagnostic messages and terminates the program. In a production environment, you won't see any of the messages. You can configure a different listener in a production environment; you add listeners in the application configuration file. The following snippet adds a TextWriterTraceListener to your application:

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

 

This TextWriterTraceListener prints all diagnostic information to the MyListener.log file. The name attribute specifies the name for the listener. The type specifies the type of object to create as a listener; it must be derived from System.Diagnostics.TraceListener. On those rare occasions when the standard listener classes in the .NET Framework are not enough for you, create your own listener class. The initializeData value is a string that gets passed to the object's constructor. TextWriterTraceListeners use this value for the filename.

You can extend these basics a bit to make it easier to create diagnostics for each assembly you distribute in your application. For each assembly you create, add a class to track the diagnostics generated by that assembly:

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.
}

 

The MyAssemblyDiagnostices class creates diagnostic messages for the assembly, depending on a switch for that assembly. To generate a message, call either of the overloaded Msg routines:

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." );
}

 

You can also combine the assembly-specific switch with a global switch to control the entire application's output:

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" );
}

This enables you to control application-wide diagnostics and more finely control an individual library's output. You can set the application-level diagnostics to the Error level to find errors anywhere in the application. When you have isolated the problem, you can raise the level of that one library's output to a higher level and find the exact source of the problem.

Diagnostic libraries are necessary to diagnose and maintain programs that have been distributed to the field. Don't write your own diagnostic library: The .NET FCL already has the core features you need. Use it to the fullest and then extend it for your own purposes, and you will capture all problems, even in production environments

抱歉!评论已关闭.