论文部分内容阅读
摘 要:微软公司随VISUAL STUDIO 2005 发布了.NET 2.0,该系统较.NET 1.1增加了大量实用方便的功能和新特性,跟踪系统部分同样大大增强了功能。如何用好这些功能,特别是定制这些功能,使之更好地为我们服务,成为我们的当前任务。本文将探讨一下这方面的问题。
关键词: TraceSource类 Switch类 StopWatch精确度 堆栈信息 线程 进程 远程过程调用
一、跟踪系统概念介绍
在.NET 2.0中,除了用来在程序中输出调试和跟踪信息的Debug类和Trace类外,还有TraceSource类。TraceSource类是.NET 2.0新添加的类,微软公司建议在新开发的程序中使用TraceSource类来产生调试跟踪信息。调试开关用来过滤信息的输出,跟踪侦听器(TraceListener)类用来向外界真正输出调试信息。所有类的参数都可通过程序配置文件进行设置,而不需要重新编译程序,修改程序配置信息即可改变该程序的调试跟踪信息输出的行为。
跟踪侦听器用来收集、存储和路由跟踪消息,是真正对外输出日志信息的类对象。系统提供的跟踪侦听器往往不能满足我们在实际应用程序中的需要。例如,在无人职守环境下,服务程序的执行日志需要输出到能够自动切换的具有固定大小的日志文件中,而以上系统提供的跟踪侦听器均无法完成该任务。此时需要制定自己的跟踪侦听器。在使用Debug、Trace和TraceSource对象时,系统可根据配置信息为以上对象生成跟踪侦听器。如果没有配置信息,系统将会为Debug和Trace生成缺省的跟踪侦听器(DefoultTraceListener),该侦听器将消息发送到调试器的消息窗口(Visual studio的debug窗口)。系统还提供了EventLogTraceListener、ConsoleTraceListener、TextWriteTraceListener、DelimitedListTraceListener和XmlWriterTraceListener跟踪侦听器。Debug、Trace和TraceSource所使用的跟踪侦听器被放在各自的Listeners属性中,可通过编程来访问和维护该侦听器列表。
所有的跟踪侦听器均派生自TraceListener类,该类是抽象类,在用户制定的派生类中,需要重载和消息输出有关的函数包括:TraceData(),TraceEvent(),Write(),WriteLine(),Fail()。这些函数负责接受其它程序部件调用,输出跟踪消息。
TraceListener类还实现了IDisposable接口,该接口中的Dispose()和Finalize()被用来关闭和释放资源。
完成自定义跟踪侦听器后,可将该侦听器放在单独的动态链接库中,方便其它程序共享该组件。实例化该侦听器,可通过配置文件来完成,也可通过编程,手工实例化侦听器对象,再将侦听器添加到TraceSource或Trace的Listeners属性中。
二、定义自己的跟踪开关类
跟踪开关用于启用、禁用和筛选跟踪输出。它们是存在于代码中的对象,可以通过.config文件从外部进行配置。.NET Framework中提供了三种类型的跟踪开关:BooleanSwitch类、TraceSwitch类和SourceSwitch类。BooleanSwitch类用作切换开关,可启用或禁用各种跟踪语句。TraceSwitch和SourceSwitch类用于为特定的跟踪级别启用跟踪开关,以便显示为该级别及其下的所有级别指定的Trace或TraceSource消息。如果禁用此开关,则不会显示跟踪消息。
跟踪开关作用于TraceListener类,通过跟踪侦听器的Filter属性,可访问该侦听器的跟踪开关对象。当系统提供的跟踪开关类不能满足程序要求时,需要开发用户自己的跟踪开关。所有的跟踪开关对象都派生自Switch类,派生类中必须实现SwitchSetting方法和OnValueChanged方法。Switch类的Value属性用来设置或返回用来过滤消息的值的字符串。对Value属性的赋值将会调用OnValueChanged方法,在该方法中,需要将字符串的Value值转换成int型的值,用来设置SwitchSetting属性。
同用户自定义跟踪侦听器一样,建议将用户自定义跟踪开关放到单独的动态链接库中,方便不同的应用程序共享该组件。同样,可通过配置文件,为跟踪侦听器对象生成对应的跟踪开关对象。
三、用StopWatch来精确测量代码执行的时间
通常,我们根据经验,测量一段代码的执行时间,是在代码的开始位置记录当前的系统时间,在代码结束时,也记录一次系统当前时间,再比较这两个时间,获得这两个时间之间的差别,精确到毫秒。
但本方法存在以下问题:首先是精度不够。当代码执行时间在毫秒以下时,无法测量时间。其次,系统的当前时间的更新间隔并不能达到毫秒级,而是1/18秒。这样,以上方法的实际测量精度是1/18秒,精度太低。
为解决以上问题,.NET Framework 2.0中提供了Stopwatch类。该类提供了一组属性和方法,能够精确测量代码的执行时间。Stopwatch实例可以测量一个时间间隔的运行时间,也可以测量多个时间间隔的总运行时间。在典型的Stopwatch方案中,先调用Start方法,然后调用Stop方法,最后使用Elapsed属性检查运行时间。
Stopwatch类的Frequency属性是以每秒刻度数表示的计时器频率,ElapsedTicks属性是获取当前实例测量得出的总运行时间(用计时器刻度表示)。其中计时器刻度是指Frequency分之一秒的时间间隔。当然,也可通过Elapsed方法获得TimeStamp对象,来获得100毫微秒为单位的计时器刻度(Ticks)时间。
四、利用StackTrace获取函数调用堆栈信息
在设计应用程序的日志系统时,打印函数的进入和退出记录,是经常需要使用的功能。此时获取当前函数的名称以及所在类的名称,甚至函数调用堆栈信息,是必须作为日志信息输出的内容。如何实现以上功能呢?
.NET Framework的StackTrace类,属性以逆向时间顺序列出了方法调用,即描述了最近的方法调用,还为堆栈上的每个方法调用都列出一行堆栈跟踪信息。如果在编译时生成了调试符号,调试符号包含在构造StackFrame和StackTrace对象时使用的文件、方法名、行号和列信息这些数据中的大部分。但是,由于优化期间发生的代码转换,StackTrace属性报告的方法调用可能没有预期的多。
StackTrace是一个包含StackFrame对象的堆栈,堆栈最上面的是最近发生的函数调用信息。StakFrame对象中包含函数名称,文件名称,行号,函数参数等信息。应用程序可在需要的地方生成StackTrace对象,然后遍历该堆栈,以输出程序执行路径相关的跟踪消息。
五、利用CorrelationManager对象在不同线程和进程间保持调用线索和相关数据
在分布式应用程序中,一个方法的执行,会延续到另外进程的方法中,如何关联不同进程中的方法执行线索,成为日志系统设计时的一个棘手问题。
.NET framework为我们提供了一个方便的解决方案——CorrelationManager对象。该对象用来关联同属于某个逻辑事务的多个跟踪,可以使用唯一操作的标识对通过单个逻辑操作生成的跟踪进行标记,以将其与通过其他逻辑操作生成的跟踪区分开来。例如,在基于报文处理的应用程序中,可利用此功能,通过以不同的报文号对操作进行标记,以区分和关联不同报文在不同进程和函数中产生的日志信息。
逻辑操作也可以嵌套。LogicalOperationStack属性公开嵌套的逻辑操作标识的堆栈。对StartLogicalOperation方法的每个调用都会将一个新的逻辑操作标识压入堆栈。对StopLogicalOperation方法的每个调用都会从堆栈中弹出一个逻辑操作标识。逻辑操作标识是对象。
六、截获和记录远程过程调用
在进行远程过程调用时,函数调用方和函数被调用方都需要对该过程进行记录,该功能是日志系统应该具备的基本功能。要实现自动记录该过程,显然需要截获所有的远程过程调用。
在客户端,有两种方法能够截获对远程对象的方法的调用。第一种方法,使用用户制定的RealProxy类。在创建远程对象的本地代理类时,直接创建用户制定的RealProxy对象,再用RemotingServices对象的Connect方法创建一个系统提供的RealProxy对象。当用户调用远程对象的方法时,调用请求被转给了用户定制RealProxy的Invoke方法,在该方法中,可加入跟踪消息输出代码,输出方法调用的详细信息,如:远程对象名称、方法名称、参数值等。在用户制定的RealProxy中,使用函数调用的消息数据,调用实际的RealProxy对象的对应函数,再将返回的值封装成ReturnMessage对象,返回给实际调用函数。在退出Invoke方法前,还可向日志系统输出函数调用返回值,或函数调用出错的详细信息,远程过程调用执行时间等信息。第二种方法是制定ClientChannelSink类和ClientChannelSinkProvider类。在生成新的客户端通道时,由用户制定的ClientChannelSinkProvider类对象来生成ClientChannelSink类,并将其挂接到客户端的Remote.NET体系中的客户端的信道接收链的插接点上。这样在客户制定的ClientChannelSink的类对象的ProcessMessage方法中,可添加代码,检查每次函数调用的详细数据,并输出到日志系统中。同样,也可在函数调用返回时,打印函数返回结果的详细信息到日志系统中。
在服务端,也可采用同客户端类似的处理方式。生成用户定制的ServerChannelSink类和ServerChannelSinkProvider类,在ServerChannelSink类的ProcessMessage方法中,输出日志信息。
七、捕获未被处理的异常
在程序执行过程中,需要经常检查代码执行的结果,特别是要注意捕捉适当的异常。但总还是有未被捕捉到的异常被抛出,使程序被非正常终止。将所有的异常都捕捉到,并详细记录这些异常的相关数据,成为了日志系统应该具有的功能。
在应用程序所在域中,有一个UnhandledException事件,该事件是在应用程序当前域中出现了未被捕获并处理的异常时被触发。我们可以在应用程序所在的域对象上,通过处理UnhandledException事件,打印未处理异常的详细信息到日志系统中,为最终处理这些异常提供方便。
参考文献:
[1]Andrew Haigh著.贾爱霞译.面向对象的分析与设计.北京:机械工业出版社,2003.
[2]黄忠成.Framework的设计与应用:基于Windows Forms的应用开发实践.电子工业出版社,2006.
[3]Chappell,D著.荣耀译..NET大局观.电子工业出版社,2006.
关键词: TraceSource类 Switch类 StopWatch精确度 堆栈信息 线程 进程 远程过程调用
一、跟踪系统概念介绍
在.NET 2.0中,除了用来在程序中输出调试和跟踪信息的Debug类和Trace类外,还有TraceSource类。TraceSource类是.NET 2.0新添加的类,微软公司建议在新开发的程序中使用TraceSource类来产生调试跟踪信息。调试开关用来过滤信息的输出,跟踪侦听器(TraceListener)类用来向外界真正输出调试信息。所有类的参数都可通过程序配置文件进行设置,而不需要重新编译程序,修改程序配置信息即可改变该程序的调试跟踪信息输出的行为。
跟踪侦听器用来收集、存储和路由跟踪消息,是真正对外输出日志信息的类对象。系统提供的跟踪侦听器往往不能满足我们在实际应用程序中的需要。例如,在无人职守环境下,服务程序的执行日志需要输出到能够自动切换的具有固定大小的日志文件中,而以上系统提供的跟踪侦听器均无法完成该任务。此时需要制定自己的跟踪侦听器。在使用Debug、Trace和TraceSource对象时,系统可根据配置信息为以上对象生成跟踪侦听器。如果没有配置信息,系统将会为Debug和Trace生成缺省的跟踪侦听器(DefoultTraceListener),该侦听器将消息发送到调试器的消息窗口(Visual studio的debug窗口)。系统还提供了EventLogTraceListener、ConsoleTraceListener、TextWriteTraceListener、DelimitedListTraceListener和XmlWriterTraceListener跟踪侦听器。Debug、Trace和TraceSource所使用的跟踪侦听器被放在各自的Listeners属性中,可通过编程来访问和维护该侦听器列表。
所有的跟踪侦听器均派生自TraceListener类,该类是抽象类,在用户制定的派生类中,需要重载和消息输出有关的函数包括:TraceData(),TraceEvent(),Write(),WriteLine(),Fail()。这些函数负责接受其它程序部件调用,输出跟踪消息。
TraceListener类还实现了IDisposable接口,该接口中的Dispose()和Finalize()被用来关闭和释放资源。
完成自定义跟踪侦听器后,可将该侦听器放在单独的动态链接库中,方便其它程序共享该组件。实例化该侦听器,可通过配置文件来完成,也可通过编程,手工实例化侦听器对象,再将侦听器添加到TraceSource或Trace的Listeners属性中。
二、定义自己的跟踪开关类
跟踪开关用于启用、禁用和筛选跟踪输出。它们是存在于代码中的对象,可以通过.config文件从外部进行配置。.NET Framework中提供了三种类型的跟踪开关:BooleanSwitch类、TraceSwitch类和SourceSwitch类。BooleanSwitch类用作切换开关,可启用或禁用各种跟踪语句。TraceSwitch和SourceSwitch类用于为特定的跟踪级别启用跟踪开关,以便显示为该级别及其下的所有级别指定的Trace或TraceSource消息。如果禁用此开关,则不会显示跟踪消息。
跟踪开关作用于TraceListener类,通过跟踪侦听器的Filter属性,可访问该侦听器的跟踪开关对象。当系统提供的跟踪开关类不能满足程序要求时,需要开发用户自己的跟踪开关。所有的跟踪开关对象都派生自Switch类,派生类中必须实现SwitchSetting方法和OnValueChanged方法。Switch类的Value属性用来设置或返回用来过滤消息的值的字符串。对Value属性的赋值将会调用OnValueChanged方法,在该方法中,需要将字符串的Value值转换成int型的值,用来设置SwitchSetting属性。
同用户自定义跟踪侦听器一样,建议将用户自定义跟踪开关放到单独的动态链接库中,方便不同的应用程序共享该组件。同样,可通过配置文件,为跟踪侦听器对象生成对应的跟踪开关对象。
三、用StopWatch来精确测量代码执行的时间
通常,我们根据经验,测量一段代码的执行时间,是在代码的开始位置记录当前的系统时间,在代码结束时,也记录一次系统当前时间,再比较这两个时间,获得这两个时间之间的差别,精确到毫秒。
但本方法存在以下问题:首先是精度不够。当代码执行时间在毫秒以下时,无法测量时间。其次,系统的当前时间的更新间隔并不能达到毫秒级,而是1/18秒。这样,以上方法的实际测量精度是1/18秒,精度太低。
为解决以上问题,.NET Framework 2.0中提供了Stopwatch类。该类提供了一组属性和方法,能够精确测量代码的执行时间。Stopwatch实例可以测量一个时间间隔的运行时间,也可以测量多个时间间隔的总运行时间。在典型的Stopwatch方案中,先调用Start方法,然后调用Stop方法,最后使用Elapsed属性检查运行时间。
Stopwatch类的Frequency属性是以每秒刻度数表示的计时器频率,ElapsedTicks属性是获取当前实例测量得出的总运行时间(用计时器刻度表示)。其中计时器刻度是指Frequency分之一秒的时间间隔。当然,也可通过Elapsed方法获得TimeStamp对象,来获得100毫微秒为单位的计时器刻度(Ticks)时间。
四、利用StackTrace获取函数调用堆栈信息
在设计应用程序的日志系统时,打印函数的进入和退出记录,是经常需要使用的功能。此时获取当前函数的名称以及所在类的名称,甚至函数调用堆栈信息,是必须作为日志信息输出的内容。如何实现以上功能呢?
.NET Framework的StackTrace类,属性以逆向时间顺序列出了方法调用,即描述了最近的方法调用,还为堆栈上的每个方法调用都列出一行堆栈跟踪信息。如果在编译时生成了调试符号,调试符号包含在构造StackFrame和StackTrace对象时使用的文件、方法名、行号和列信息这些数据中的大部分。但是,由于优化期间发生的代码转换,StackTrace属性报告的方法调用可能没有预期的多。
StackTrace是一个包含StackFrame对象的堆栈,堆栈最上面的是最近发生的函数调用信息。StakFrame对象中包含函数名称,文件名称,行号,函数参数等信息。应用程序可在需要的地方生成StackTrace对象,然后遍历该堆栈,以输出程序执行路径相关的跟踪消息。
五、利用CorrelationManager对象在不同线程和进程间保持调用线索和相关数据
在分布式应用程序中,一个方法的执行,会延续到另外进程的方法中,如何关联不同进程中的方法执行线索,成为日志系统设计时的一个棘手问题。
.NET framework为我们提供了一个方便的解决方案——CorrelationManager对象。该对象用来关联同属于某个逻辑事务的多个跟踪,可以使用唯一操作的标识对通过单个逻辑操作生成的跟踪进行标记,以将其与通过其他逻辑操作生成的跟踪区分开来。例如,在基于报文处理的应用程序中,可利用此功能,通过以不同的报文号对操作进行标记,以区分和关联不同报文在不同进程和函数中产生的日志信息。
逻辑操作也可以嵌套。LogicalOperationStack属性公开嵌套的逻辑操作标识的堆栈。对StartLogicalOperation方法的每个调用都会将一个新的逻辑操作标识压入堆栈。对StopLogicalOperation方法的每个调用都会从堆栈中弹出一个逻辑操作标识。逻辑操作标识是对象。
六、截获和记录远程过程调用
在进行远程过程调用时,函数调用方和函数被调用方都需要对该过程进行记录,该功能是日志系统应该具备的基本功能。要实现自动记录该过程,显然需要截获所有的远程过程调用。
在客户端,有两种方法能够截获对远程对象的方法的调用。第一种方法,使用用户制定的RealProxy类。在创建远程对象的本地代理类时,直接创建用户制定的RealProxy对象,再用RemotingServices对象的Connect方法创建一个系统提供的RealProxy对象。当用户调用远程对象的方法时,调用请求被转给了用户定制RealProxy的Invoke方法,在该方法中,可加入跟踪消息输出代码,输出方法调用的详细信息,如:远程对象名称、方法名称、参数值等。在用户制定的RealProxy中,使用函数调用的消息数据,调用实际的RealProxy对象的对应函数,再将返回的值封装成ReturnMessage对象,返回给实际调用函数。在退出Invoke方法前,还可向日志系统输出函数调用返回值,或函数调用出错的详细信息,远程过程调用执行时间等信息。第二种方法是制定ClientChannelSink类和ClientChannelSinkProvider类。在生成新的客户端通道时,由用户制定的ClientChannelSinkProvider类对象来生成ClientChannelSink类,并将其挂接到客户端的Remote.NET体系中的客户端的信道接收链的插接点上。这样在客户制定的ClientChannelSink的类对象的ProcessMessage方法中,可添加代码,检查每次函数调用的详细数据,并输出到日志系统中。同样,也可在函数调用返回时,打印函数返回结果的详细信息到日志系统中。
在服务端,也可采用同客户端类似的处理方式。生成用户定制的ServerChannelSink类和ServerChannelSinkProvider类,在ServerChannelSink类的ProcessMessage方法中,输出日志信息。
七、捕获未被处理的异常
在程序执行过程中,需要经常检查代码执行的结果,特别是要注意捕捉适当的异常。但总还是有未被捕捉到的异常被抛出,使程序被非正常终止。将所有的异常都捕捉到,并详细记录这些异常的相关数据,成为了日志系统应该具有的功能。
在应用程序所在域中,有一个UnhandledException事件,该事件是在应用程序当前域中出现了未被捕获并处理的异常时被触发。我们可以在应用程序所在的域对象上,通过处理UnhandledException事件,打印未处理异常的详细信息到日志系统中,为最终处理这些异常提供方便。
参考文献:
[1]Andrew Haigh著.贾爱霞译.面向对象的分析与设计.北京:机械工业出版社,2003.
[2]黄忠成.Framework的设计与应用:基于Windows Forms的应用开发实践.电子工业出版社,2006.
[3]Chappell,D著.荣耀译..NET大局观.电子工业出版社,2006.