A Provider-Based Service for ASP.NET Tracing
原文见:http://msdn.microsoft.com/msdnmag/issues/06/06/CuttingEdge/default.aspx
Dino Esposito
当我们扑捉程序错误时,调试器是开发者们最得力的助手。然而,ASP.NET的跟踪,在调试时是一个很棒的不容忽视的辅助,它允许你的ASP.NET代码在执行期间输出消息,提供有用信息有效帮助你发现问题所在。
调试器和跟踪这两个技术有一点区别。调试器是一种自然的交互技术它依赖于你有能力暂停程序执行并检查当前的状态。而跟踪是一种不干扰系统运行,简单的输出代码设定的跟踪信息(类似于经典的“printf”式调试),在执行结束时,你可以取得跟踪代码产生的输出并分析它。
ASP.NET跟踪子系统通过控件,页面或者可以增加自身跟踪输出的组件显露出一个API,可以输出任何可用到的信息给你一些有意义的帮助。
在ASP.NET中,跟踪与页面和运行时紧密的整合在一起。你可以在单独的某页上通过打开一个属性来启用跟踪。你也可以通过改变配置文件Web.config中的一个值来启用整个应用程序的跟踪。例如,你可以在应用程序部署之后启用跟踪。
在本文中,我会回顾一下ASP.NET跟踪的基础知识并讨论ASP.NET 2.0中的新特性。最后,我将探讨ASP.NET页面中对跟踪文本呈现外观的定制。
在ASP.NET中进行跟踪
跟踪作为一个子系统构建为ASP.NET的一个基础结构,给应用程序提供诊断信息关于某个单独请求。通过跟踪,你可以分析浏览器和WEB服务器交换的数据,包括请求的详细信息,时间调配信息,服务器变量,Cookies,头信息,Session状态等等。
当页面的跟踪启用后,ASP.NET可以用两种方式种的一种来显示信息:添加诊断信息到页面输出,也可以通过内建的跟踪查看器程序呈现信息。通过简单设置@Page指令的Trace属性为true就可以用前者来查看跟踪信息,例如:
<%@Page trace="true" ... %>
注意这种情况下跟踪信息作为页面的一部分发送同样地也通过浏览器来显示。当你创建和测试页面时没有问题,但如果你在部署前忘记重置某个页面Trace属性,别人也就看到了这些可能比较敏感的信息,此时它就成为一个烦人的bug。
对于大多数部分,跟踪信息的内容和布局是固定的,但是页面和控件可能被自定义输出为某种宽带或长度。在ASP.NET 2.0中,跟踪输出被分成12不同的块,见图1
在ASP.NET 1.X中仅有一个Cookie集合对象既包括了请求又包含了响应的cookie。也只有一个头集合,同样包括了请求和响应的头。
你可以使用TraceContext类中的方法来输出指定消息和信息到跟踪信息块。使用下面的代码:
' 在Page或其继承类中 Trace.Write("Your message") ' ASP.NET 应用程序的任意地方 HttpContext.Current.Trace.Write("Your message")
通过HttpContext的Trace属性显露TraceContext类的一个实例给开发者,同理Page的Trace属性也是如此。
这些消息基于它在执行时的时间写到跟踪输出中,例如,如果你在页面的Page_Load事件释放一个消息,那么该消息将如图2所示呈现。TraceContext类还有一个Warn方法,跟Write不同的是它将文本呈现为红色。Write和Warn都有一些重载方法:
' Write overloads public void Write ( String message ) public void Write ( String category , String message ) public void Write ( String category , String message , Exception errorInfo ) ' Match Warn overloads public void Warn ( String message ) public void Warn ( String category , String message ) public void Warn ( String category , String message , Exception errorInfo )
第一个重载将跟踪消息写入跟踪日志。(见图2)。第二个重载接受跟踪类别和跟踪内容,跟踪类别可以使用简短的跟踪信息,也可以是任何对应用程序或消息有用的字符串。
第三个重载将跟踪信息写入跟踪日志,包括用户定义的所有类别、跟踪消息和错误信息。跟踪日志不接受HTML格式,文本在呈现到浏览器之前转义。
图2 ASP.NET页面的跟踪日志
TraceContext类还有其他两个属性:IsEnabled 和 TraceMode.IsEnabled是布尔类型,指示是否已为当前 Web 请求启用跟踪。TraceMode属性获取或设置跟踪消息输出到请求浏览器应遵循的排序顺序, 是一种枚举类型包含SortByCategory 和 SortByTime。那你可以通过设定IsEnable编程实现跟踪的启用。在ASP.NET 2.0中, 你也可以使用新的属性Page类的TraceEnabled,该属性仅仅是对Trace.IsEnabled的包装.
<trace> 元素
像我们提到的,多数的跟踪属性可以在配置文件中控制。有一个选项允许你扑捉跟踪信息儿不用在页面呈现中输出,这个功能对于跟踪数据,监视流程,在已经部署为产品的运行环境断言条件都很有用。它在配置文件中对应的块是<trace>,ASP.NET 2.0中大致如下:
<trace enabled="true|false" localOnly="true|false" mostRecent="true|false" pageOutput="true|false" requestLimit="integer" traceMode="SortByTime|SortByCategory" writeToDiagnosticsTrace="true|false" />
各属性的详细信息见图3。
ASP.NET也支持应用级别的跟踪通过内建的跟踪查看器工作--trace.axd。注意这个工具通过<trace>块的enable属性激活,但是不激活每个页面的跟踪日志。这样做使得你可以在产品中打开跟踪却不影响用户页面的输出。
你也行会想在发布的应用程序中关掉个别页面的Trace属性并在web.config中禁用跟踪同时还保持对某页面的跟踪状况,那么你要在web.config中这样做:
<trace enabled="true" pageOutput="false" />
一旦应用程序的跟踪被启用,每个页面请求将发送所有的页面特定跟踪信息给查看者。Trace.axd(图4示)是一个系统定义的HTTP
handler,获取内部跟踪信息的缓存并显示到Web接口。你可以通过请求应用程序根目录下的trace.axd访问跟踪查看器,也可以给页面请求增加id=xxx参数来直接访问跟踪信息。xxx指示一个0下标索引的请求索引。
图4 跟踪查看器
在ASP.NET 1.x中,默认只有10个请求(<trace>中requestLimit的默认值)。而在ASP.NET
2.0中,可以保留最近的十个(或者是requestLimit指定的值),更早的跟踪信息将被覆盖。
所有的跟踪信息被TraceContext类缓存在一个DataSet对象中,当应用程序的AppDomain的HttpRuntime对象首次被初始化时,TraceContext类将被实例化。结果是当trace.axd被启用时,维护的跟踪信息越多,内存方面的消耗越多。
ASP.NET 2.0的一个新特性是允许使用TraceContext.TraceFinished事件,在收集完所有请求信息后被引发。TraceFinished处理程序提供了一个TraceFinishedEventArgs参数接受跟踪消息(TraceRecord)对象集合。每一条存储了一个单独的ASP.NET跟踪消息和所有相关数据,允许你采集这些信息并随意进行处理,显示到页面或者记录到自己的数据库等等
连接到侦听器
ASP.NET下的跟踪跟.NET Framework直接相关,Systems.Diagnostics命名空间定义了两个类:: Trace 和 Debug. 它们提供了对代码进行跟踪的常用方法。Trace 和 Debug类本质相同,都工作在很多特定模块之上例如侦听器。侦听器搜集并将消息存储到Windows事件日志,文本文件,或者其他类似文件中。每个应用程序可以有它自己的一套侦听器;所有注册过的侦听器接受全部的发布消息。ASP.NET 2.0中跟踪子系统被进一步加强,可以转发ASP.NET跟踪信息给任意一个已注册的.NET跟踪侦听器。通过打开web.config文件 <trace> 节的writeToDiagnosticsTrace属性启用这一特性,如下所示:
<trace enabled="true" pageOutput="false" writeToDiagnosticsTrace="true" />
不过仅设置writeToDiagnosticsTrace属性还不行,还需要给程序注册一个或多个侦听器。可以通过<system.diagnostics> 节中的<trace>节实现,例如:
<system.diagnostics> <trace autoflush="true"> <listeners> <add name="myListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="c:\myListener.log" /> </listeners> </trace> </system.diagnostics>
这里有两件事要注意:第一,只有文本才能通过Write或Warn传递给侦听器,也就是说,侦听器不会存储一个标准的ASP.NET跟踪信息,而仅是跟踪信息块的内容。第二,你可以编写自定义的侦听器来发送跟踪信息到你选择的介质--例如SQL Server 数据库,你所要做的仅是继承System.Diagnostics.TraceListener并重载一些方法。然后,将新类注册到要跟踪的WEB应用程序的web.config文件中。(更多关于编写自定义TraceListener请查阅msdn.microsoft.com/msdnmag/issues/06/04/CLRInsideOut.)
有一件有趣的事值得注意,ASP.NET 2.0中System.Web.dll新加了一个源自TraceListener的类型WebPageTraceListener,它与writeToDiagnosticsTrace配套使用。当WebPageTraceListener实例增加到跟踪侦听器集合后,写入 .NET Trace 日志的跟踪消息也将写入ASP.NET Trace日志。
最后,在ASP.NET 2.0中跟踪相关的还有一个新的<deployment>节被增加到 Web configuration 节中。
<deployment retail="true|false" />
当 Retail 为 true 时,ASP.NET 将禁用在配置中定义的某些设置,如跟踪输出、自定义错误和调试能力。<deployment>节只可以在machine.config中设置。【译者:本节仅适用于计算机级别和应用程序级别的配置文件。】
控制跟踪信息
ASP.NET页面检测的标准跟踪信息包括12个部分(如图1)附加数据总大小不超过20k。数据的大小和格式不会带来困扰,因为只有在测试和开发时才会去跟踪页面。但是,适当控制跟踪文本的格式则可以增加数据的易读性。
然而ASP.NET跟踪数据很难捕获,它在处理请求时收集并在TraceContext类内部处理。当请求处理结束时,跟踪信息才被输出。跟踪过程被页面的ProcessRequest方法控制,该方法是处理ASP.NET页面请求的重要方法。基本来说,跟踪信息是在页面HTML标记结束后输出的。那么如何捕获跟踪文本呢?如何给跟踪信息添加数据,删除不必要的内容或者仅是对它进行格式化?
在我2004年9月的专栏(http://msdn.microsoft.com/msdnmag/issues/04/09/cuttingEdge)中,我提供了一个特定页的解决方案,但很难扩展到程序中的所有页面。要实现这一功能也很简单,用自定义流来封装原来的页面输出流,这样就可以取得所有的页面数据,包括页面主体和跟踪原文。在那篇专栏里我仅在示例页实现了这一功能,而且运行良好。
为了实现这一想法,需要给HttpResponse对象增加Filter属性并赋予一个自定义stream对象。Filter属性是用来获取或设置一个包装筛选器对象,该对象用于在传输之前修改 HTTP 实体主体。如何将这一功能应用到应用程序的每个页面?如何在所有启用了跟踪的页面上自动筛选跟踪原文?如何想实现这些功能而无须改动页面源码?请看下文。
MyTracer HTTP Module
思路是安装一个HTTP模块来重载所有启用了跟踪的页面respone的Filter属性。HTTP模块是一个实现了IHttpModule接口的类,并需要在应用程序的web.config中<httpModules>小节中注册。图5是MyTracer模块的源代码,作为自定义ASP.NET跟踪服务的基础。
C# | VB | 隐藏代码 | |
这个模块侦听应用程序级别的事件PostRequestHandlerExecute,该事件当处理请求结束时立即触发。巧妙的是,ProcessRequest处理完页面请求返回后立即通知该事件,此时跟踪信息被生成并输出,但是页面输出依然停留在服务器端尚未发送给浏览器。模块在Init中注册了该事件的委托。
像图5所示,事件处理检查Trace对象的IsEnalbed属性的值,如果这个请求的跟踪已启用,设置自定义stream对象的response的Filter属性。这样以来所有的reponse都会经过自定义stream对象进行读取和随意修改。为什么不直接使用原始的数据流的Filter进行读取和回写进行所需处理呢?由于性能原因,Filter属性返回的stream对象是只写的,一旦调用read方法就会抛出异常。
图6显示了TraceStream示例类的源码,它是一个stream类,重载了Write方法来处理将要发送到浏览器的字节数组。这个字节数组根据需要被转变为字符串,并且,一旦完成,回写到浏览器。新的更智能的跟踪服务的核心是TraceOutputService类。
在深究TraceOutputService类之前,先来一个概述。看以下脚本:
<httpModules> <add name="MyTracerModule" type="MsdnMag.CuttingEdge.MyTracer.TracerModule, MyTracer" /> </httpModules>
通过简单增加上面代码到web.config文件,就会给页面输出应用一个筛选器,来允许你自己编辑一切可见内容包括跟踪信息。
将跟踪作为一个基于Provider的服务
为了处理跟踪信息,你也许希望将跟踪信息从正常的页输出分离出来进行处理,这正是TraceOutputService要完成的功能。
思路是将这一功能置入一个基于Provider的服务。在服务中使用一个静态方法,该方法依次查找并调用默认跟踪服务的provider。通过注册不同的provider,你可以使用任意数量的不同方式处理跟踪信息--比如从视图中排除一些项目,合并其他项目,增加新的项目,通过弹出窗口显示,保存信息到数据库等等。
实现这样的服务有几种不同级别的复杂性和伸缩性可以选择,最简单的选择需要你从web.config或一个自定义的xml文件中读取一行,并且调用Activator.CreateInstance去实例化指定对象,然后使用对象的一个约定接口处理跟踪信息,另一个选择是使用工厂模式提供一个显式实现(explicit)--多少有点像ADO.NET 2.0中DbProviderFactories类。
但是鉴于所有的可能性,多数类ASP.NET的方法将此功能设计成一个服务并适用provider模型。可以分成三步完成:在web.config中定义配置数据,定义一个基本的provider类实现服务接口,最后是设计一个服务类来调用选择的provider。
更多关于设计和撰写自定义provider-based服务的信息可以参阅msdn.microsoft.com/library/en-us/dnaspp/html/ASPNETProvMod_Prt8.asp.
在Web.config中创建Section
在ASP.NET 2.0中,可以用不同的ASP.NET 1.x的方式编程处理web.config中的自定义section。可以从 ConfigurationSection 派生一个类实现自定义的节(section)类型,所在命名空间是System.Configuration。继承该类的方法和逻辑来解析小节绑定的内容。ConfigurationProperty是节类型中可以对Public属性修饰的唯一可用的特性。为了给provider列表建立一个section,可以选用一些系统提供的类型例如ProviderSettingsCollectio(见图7)。
TraceOutputServiceSection类处理XML配置文件中的<providers>集合,并且有一个默认值为StandardTraceOutputProvider的 defaultProvider 属性。不用说,自定义节类型TraceOutputService在此情况下必须显式注册到web.config来实现计划目标:
<configSections> <sectionGroup name="system.web"> <section name="traceOutputService" type="MsdnMag.TraceOutputServiceSection, MyTracer" allowDefinition="MachineToApplication" /> </sectionGroup> </configSections>
由此,下面的脚本会合法而且可以被系统正确处理:
<traceOutputService defaultProvider="RichTraceOutputProvider"> <providers> <add name="StandardTraceOutputProvider" type="MsdnMag.StandardTraceOutputProvider, MyTracer" /> <add name="RichTraceOutputProvider" type="MsdnMag.RichTraceOutputProvider, MyTracer" sections="All" collapseAll="true" /> </providers> </traceOutputService>
<providers> 中的每一项都会由ProviderSettings对象处理,该对象拥有一个键/值集合属性Parameters,允许你自由定义属性集合来描绘provider类型。更多关于.NET Framework 2.0中的配置信息,可以参阅本期杂志Bryan Porter关于此主题的文章。
Trace Provider 接口
跟踪provider公开一个固定接口的组件,调用者可以多态方式调用注册过的provider而无须知道它的内部实现。provider的公共联系是都从ProviderBase基类继承:
public abstract class TraceOutputProvider : ProviderBase { // Properties public abstract string ApplicationName { get; set; } // Methods public abstract string PostProcess(string tracedText); }
注意如果想得到一个纯粹的ASP.NET provider模型就必须从ProviderBase基类继承。该模式也可以推广到其他asp.net预定义的工具。
设计Service类
每个基于provider的service都有一个入口类,他们提供了一些静态方法直接映射选择的provider成员:
Public Shared Function Parse(ByVal pageResponse As String) As String
public static string Parse(string pageResponse)
Parse方法接受完整的页面内容(主体和跟踪信息),它先将页面主体和跟踪信息分离,然后载入选定的provider并调用其PostProcess方法。PostProcess的返回值将被组合到页面主体返回给输出流,最后呈现给用户。
TraceOutputService类的Provider属性指向当前运行的选定provider,Providers属性则可以获取所有已注册的provider。在整个应用程序的生命周期中该类只载入全部的provider一次,随后所有并发的请求共享这些provider实例。这就是为何必须锁定所有的可写入的provider实例。图8列出了TraceOutputService类的完整源码。
可以这样加载Providers集合并通过名字获取当前的provider对象:
_providers = New TraceOutputProviderCollection() ProvidersHelper.InstantiateProviders(section.Providers, _ _providers, GetType(TraceOutputProvider)) _provider = _providers(section.DefaultProvider)
TraceOutputProviderCollection类继承于一个系统提供类ProviderCollection。ProvidersHelper是一个内建的helper类,位于System.Web.Configuration命名空间。它有两个重要方法:InstantiateProviders 和 InstantiateProvider。前者为已注册的provider创建实例;后者实例化一个特定类。两者都从configuration节中获取要创建的provider的详细信息。他们减轻了ASP.NET实例化provider的负担。事实上这些类在内建的provider模型的内部实现中广泛使用。InstantiateXXX方法取代了调用Activator.CreateInstance获取特定类型实例的方式。
封装标准的跟踪器
现在我们有了一个基于provider跟踪服务的功能架构,下面尝试添加一些provider。首先从从一个简单的映射标准输出的provider入手,增加一个签名来验证新的架构是否被应用了.
StandardTraceOutputProvider继承自TraceOutputProvider并且重载了Initialize 和 PostProcess 方法,在Initialize方法中,StandardTraceOutputProvider调用基类的方法并读取配置中可能存在的application名字.(更多细节参见源码)
C# | VB | 隐藏代码 | |
public override string PostProcess(string tracedText) { return GetTopSignature() + tracedText + GetBottomSignature(); } |
通过如下脚本注册跟踪provider:
<traceOutputService defaultProvider="StandardTraceOutputProvider"> <providers> <add name="StandardTraceOutputProvider" type="MsdnMag.StandardTraceOutputProvider, MyTracer" /> </providers> </traceOutputService>
要打开页面的输出跟踪,可以通过设置web.config中的Trace=true启用ASP.NET示例程序中所有页面的跟踪,或者是页面的@Page指令。
更强大的一个ASP.NET跟踪器
现在我们构造一个更强大的跟踪provider,它仅显示选定的section的一个子集(即仅仅是跟踪信息)并且包括合并展开功能增强数据的易读性。
要达到这个目标,需要映射跟踪文本到一个内部实现--一个自定义的TraceBlock结构,并且是基于配置文件的,来决定哪一些增加到输出流中。另外,还要为每个块增加一些JavaScript来控制块的隐藏/显示。图9显示了最终结果。
![](http://msdn.microsoft.com/msdnmag/issues/06/06/CuttingEdge/fig09.gif)
图9 具有合并/展开功能的TraceProvider
跟踪原文是一个<table>元素集合,每个集合为一块(请求的详细信息,跟踪信息等等)。RichTraceOutputProvider对象的PostProcess方法遍历原文并处理TraceBlock对象集,典型的TraceBlock的代码如下:
C# | VB | 隐藏代码 | |
internal class TraceBlock { public TraceSections Code; public string Name; public string ID; public string Body; } |
codePanel字段指示TraceSections枚举表示的section类型(见源码下载)。Name字段表示section的标题,ID字段是基于名字的ID用来描述section,而Body字段包括了section块的原始信息。
provider也定义了两个新的虚属性: Section和CollapseAll。前者是TraceSection值指示要显示给用户的信息块,CollapseAll属性是指示块是否初始化为折叠状态。图9的跟踪输出依照下面的配置脚本:
<add name="RichTraceOutputProvider" type="MsdnMag.CuttingEdge.RichTraceOutputProvider, MyTracer" sections="RequestDetails,TraceInformation,SessionState" collapseAll="true" />
每部分的原始的HTML都通过客户端按钮添加。如果按钮是可见的,跟踪数据的表格也会显示,反正依然。按钮和表格都被绑定到一个click事件里一同控制是否可见。按钮标记和任何必要的脚本代码都在provider的PostProcess方法中添加。如下的脚本关联的每一个按钮和表格:
function Toggle(showInfo, tableName, buttonName) { var table = document.getElementById(tableName); var button = document.getElementById(buttonName); if (showInfo) { table.style["display"] = 'none'; button.style["display"] = ''; } else { table.style["display"] = ''; button.style["display"] = 'none'; } }
默认情况下按钮是隐藏的,而表格是显示的。通过设置CollapseAll为true,做相反改变。如图9所示,点击按钮展开跟踪信息,点击表格的任意位置在可以将其折叠起来。
图9
注意前面的脚本既可工作在IE中,也可运行于Mozilla Firefox和Netscape 7浏览器里。
ASP.NET跟踪是一个很强大的工具,可以帮助开发人员验证页面和应用程序在运行时的行为。本专栏演示了如何创建一个简单易用的架构来捕捉标准跟踪原文,并对其显示方式进行替换或修改。
译者:翻译周期有点长,原文写的也很乱,涉及到的知识比较多,可能出现前后术语不一致的情况,基本依照MSDN的官方翻译,主要查阅和参照中文MSDN2005,源程序是ASP.NET 2.0的,跟文章略有差别。问题和评论请发送至 cutting@microsoft.com.
作者简介
Dino Esposito在意大利罗马工作,任职讲师兼顾问。《Programming ASP.NET》和新书《Introducing ASP.NET 2.0》(两书均由Microsoft Press出版)的作者,他将大部分时间用于讲授ASP.NET和ADO.NET方面的课程以及在各种会议上演讲。如果希望与Dino取得联系,请发送email至 cutting@microsoft.com 或者浏览他的blog站点:http://weblogs.ASP.NET/despos.
译自 MSDN Magazine 2006年6月刊 .
Section Name | What It Displays |
---|---|
Request Details | Information about the current request, such as status code, verb, session ID, and timestamp |
Trace Information | System- and user-defined messages sorted by time or category |
Control Tree | The hierarchy of the controls in the page, including each corresponding view state size |
Session State | All the items stored in Session |
Application State | All the items stored in the Application intrinsic object |
Request Cookies Collection | Name and content of all cookies attached to the request (ASP.NET 2.0 only) |
Response Cookies Collection | Name and content of all cookies attached to the response (ASP.NET 2.0 only) |
Headers Collection | Name and content of all request headers |
Response Headers Collection | Name and content of all headers attached to the response (ASP.NET 2.0 only) |
Form Collection | Name and content of all input fields packed in the HTTP response |
Querystring Collection | Name and content of all query string parameters |
Server Variables | Name and content of all server variables available |
Section Name | What It Indicates |
---|---|
enabled | Whether tracing is enabled through the trace.axd viewer. This attribute alone is not sufficient to append trace information to all pages. The default is false. |
localOnly | Whether the trace.axd viewer should be available only on the local Web server. The default is true. |
mostRecent | The tracing system only maintains tracing information on a specific number of requests, as determined by requestLimit. When mostRecent is true, information from newer requests overwrites that from older requests when the limit has been reached. The default is false, meaning that trace data is recorded only until the limit is reached, causing subsequent traces to be lost (ASP.NET 2.0 only). |
pageOutput | Whether trace output is rendered inside each page of the application. If false, trace output is only accessible through the trace.axd viewer. The default is false. |
requestLimit | The number of trace requests to store on the server. The default is 10; the maximum is 10,000. |
traceMode | The order in which trace information is displayed: sorted by category or by time. The default is SortByTime. |
writeToDiagnosticsTrace | Whether trace output should be forwarded to any .NET trace listeners that are registered. The default is false (ASP.NET 2.0 only). |