更新:对于 System.Diagnostics 的扩展,提供一些您可能需要的缺少的侦听器,请参阅 CodePlex 上的 Essential.Diagnostics ( http://essentialdiagnostics.codeplex.com/ )
构架
问:你们使用什么框架?
答:System.Diagnostics.TraceSource,内置于 .NET 2.0。
它为应用程序提供了强大、灵活、高性能的日志记录,但是许多开发人员并没有意识到它的功能并且没有充分利用它们。
在某些领域,附加功能很有用,或者有时功能存在但没有很好的文档记录,但这并不意味着整个日志框架(设计为可扩展的)应该像一些流行的替代方案一样被丢弃并完全替换(NLog、log4net、Common.Logging 甚至 EntLib 日志记录)。
与其改变向应用程序添加日志语句的方式并重新发明轮子,不如在您需要的几个地方扩展 System.Diagnostics 框架。
在我看来,其他框架,甚至是 EntLib,都只是患有 Not Invented Here Syndrome,我认为他们浪费了时间重新发明在 System.Diagnostics 中已经很好工作的基础知识(例如您如何编写日志语句),而不是填补现有的少数空白。简而言之,不要使用它们——它们不是必需的。
你可能不知道的功能:
- 使用采用格式字符串和参数的 TraceEvent 重载可以帮助提高性能,因为参数作为单独的引用保存,直到 Filter.ShouldTrace() 成功。这意味着在系统确认消息之前不会对参数值进行昂贵的 ToString() 调用。
- Trace.CorrelationManager 允许您关联有关相同逻辑操作的日志语句(见下文)。
- VisualBasic.Logging.FileLogTraceListener 适合写入日志文件并支持文件轮换。尽管在 VisualBasic 命名空间中,它也可以很容易地在 C#(或其他语言)项目中使用,只需包含 DLL。
- 使用 EventLogTraceListener 时,如果您使用多个参数和空或 null 格式字符串调用 TraceEvent,则如果您使用本地化消息资源,则参数将直接传递给 EventLog.WriteEntry()。
- 服务跟踪查看器工具(来自 WCF)对于查看活动相关日志文件的图表非常有用(即使您没有使用 WCF)。这确实可以帮助调试涉及多个线程/活动的复杂问题。
- 通过清除所有侦听器(或删除默认值)来避免开销;否则 Default 会将所有内容传递给跟踪系统(并产生所有 ToString() 开销)。
您可能希望扩展的区域(如果需要):
- 数据库跟踪侦听器
- 彩色控制台跟踪侦听器
- MSMQ / 电子邮件 / WMI 跟踪侦听器(如果需要)
- 实现 FileSystemWatcher 以调用 Trace.Refresh 以进行动态配置更改
其他建议:
使用结构化事件ID,并保留一个参考列表(例如,将它们记录在一个枚举中)。
为系统中的每个(重要)事件拥有唯一的事件 ID 对于关联和查找特定问题非常有用。很容易追溯到记录/使用事件 ID 的特定代码,并且可以很容易地为常见错误提供指导,例如错误 5178 表示您的数据库连接字符串错误等。
事件 id 应该遵循某种结构(类似于电子邮件和 HTTP 中使用的回复代码理论),这允许您在不知道具体代码的情况下按类别对待它们。
例如,第一个数字可以详细说明一般类:1xxx 可用于“开始”操作,2xxx 用于正常行为,3xxx 用于活动跟踪,4xxx 用于警告,5xxx 用于错误,8xxx 用于“停止”操作,9xxx 用于致命错误,等等
第二个数字可以详细说明区域,例如21xx表示数据库信息(41xx表示数据库警告,51xx表示数据库错误),22xx表示计算模式(42xx表示计算警告等),23xx表示另一个模块等。
分配的结构化事件 ID 还允许您在过滤器中使用它们。
问:如果使用跟踪,是否使用 Trace.Correlation.StartLogicalOperation?
答: Trace.CorrelationManager 对于在任何类型的多线程环境中关联日志语句非常有用(这在当今几乎是任何事情)。
您至少需要为每个逻辑操作设置一次 ActivityId 才能关联。
然后可以将 Start/Stop 和 LogicalOperationStack 用于简单的基于堆栈的上下文。对于更复杂的上下文(例如异步操作),使用 TraceTransfer 到新的 ActivityId(在更改它之前)允许关联。
服务跟踪查看器工具可用于查看活动图(即使您不使用 WCF)。
问:您是手动编写此代码,还是使用某种形式的面向方面编程来编写?愿意分享代码片段吗?
答:您可能想要创建一个范围类,例如 LogicalOperationScope,它 (a) 在创建时设置上下文,并且 (b) 在释放时重置上下文。
这允许您编写如下代码来自动包装操作:
using( LogicalOperationScope operation = new LogicalOperationScope("Operation") )
{
// .. do work here
}
在创建范围时,如果需要,可以首先设置 ActivityId,调用 StartLogicalOperation,然后记录 TraceEventType.Start 消息。在 Dispose 上,它可以记录停止消息,然后调用 StopLogicalOperation。
问:你们是否提供任何形式的跟踪源粒度?例如,WPF TraceSources 允许您在不同级别配置它们。
答:是的,随着系统变得越来越大,多个跟踪源很有用/很重要。
虽然您可能希望始终记录所有警告及以上消息,或所有信息及以上消息,但对于任何规模合理的系统,活动跟踪(启动、停止等)和详细日志记录的数量会变得过多。
与其只有一个开关可以将其全部打开或关闭,不如能够一次为系统的一个部分打开此信息很有用。
这样,您可以从通常的日志记录(所有警告、错误等)中找到重大问题,然后“放大”您想要的部分并将它们设置为活动跟踪甚至调试级别。
您需要的跟踪源的数量取决于您的应用程序,例如,您可能希望每个程序集或应用程序的每个主要部分都有一个跟踪源。
如果您需要更精细的控制,请添加单独的布尔开关来打开/关闭特定的高容量跟踪,例如原始消息转储。(或者可以使用单独的跟踪源,类似于 WCF/WPF)。
您可能还需要考虑将活动跟踪与一般(其他)日志记录分开的跟踪源,因为它可以更容易地按照您的需要配置过滤器。
请注意,即使使用不同的源,消息仍然可以通过 ActivityId 关联,因此请根据需要使用尽可能多的消息。
听众
问:您使用什么日志输出?
这可能取决于您正在编写的应用程序类型以及正在记录的内容。通常不同的东西会出现在不同的地方(即多个输出)。
我通常将输出分为三组:
(1) 事件 - Windows 事件日志(和跟踪文件)
例如,如果编写服务器/服务,那么在 Windows 上的最佳实践是使用 Windows 事件日志(您没有要报告的 UI)。
在这种情况下,所有致命、错误、警告和(服务级别)信息事件都应转到 Windows 事件日志。信息级别应保留给这些类型的高级事件,即您想要在事件日志中进入的事件,例如“服务启动”、“服务停止”、“连接到 Xyz”,甚至可能是“计划启动” 、“用户登录”等。
在某些情况下,您可能希望将事件日志写入应用程序的内置部分,而不是通过跟踪系统(即直接写入事件日志条目)。这意味着它不会被意外关闭。(请注意,您仍然希望在跟踪系统中记录相同的事件,以便进行关联)。
相反,Windows GUI 应用程序通常会将这些报告给用户(尽管他们也可能会记录到 Windows 事件日志中)。
事件也可能有相关的性能计数器(例如错误数/秒),协调任何直接写入事件日志、性能计数器、写入跟踪系统并报告给用户以使它们发生在同时。
即,如果用户在特定时间看到错误消息,您应该能够在 Windows 事件日志中找到相同的错误消息,然后在跟踪日志中找到具有相同时间戳的相同事件(以及其他跟踪详细信息)。
(2) 活动 - 应用程序日志文件或数据库表(和跟踪文件)
这是系统进行的常规活动,例如提供网页、提交股票市场交易、接受订单、执行计算等。
活动跟踪(开始、停止等)在这里很有用(以正确的粒度)。
此外,使用特定的应用程序日志(有时称为审核日志)也很常见。通常这是一个数据库表或应用程序日志文件,包含结构化数据(即一组字段)。
根据您的应用程序,这里的情况可能会有些模糊。一个很好的例子可能是一个将每个请求写入网络日志的网络服务器;类似的示例可能是消息传递系统或计算系统,其中每个操作都与特定于应用程序的详细信息一起记录。
一个不太好的例子是股票市场交易或销售订单系统。在这些系统中,您可能已经记录了活动,因为它们具有重要的业务价值,但是将它们与其他操作相关联的原则仍然很重要。
除了自定义应用程序日志外,活动通常还具有相关的性能计数器,例如每秒的事务数。
一般来说,您应该协调跨不同系统的活动记录,即在增加性能计数器和记录到跟踪系统的同时写入应用程序日志。如果您同时执行所有操作(或在代码中一个接一个),那么调试问题会更容易(比它们都发生在代码中的不同时间/位置)。
(3) 调试跟踪 - 文本文件,或者 XML 或数据库。
这是详细级别和更低级别的信息(例如,用于打开/关闭原始数据转储的自定义布尔开关)。这提供了系统在子活动级别所做的事情的胆量或细节。
这是您希望能够为应用程序的各个部分打开/关闭的级别(因此有多个源)。您不希望这些东西弄乱 Windows 事件日志。有时会使用数据库,但更有可能是在特定时间后清除的滚动日志文件。
此信息与应用程序日志文件之间的一个很大区别在于它是非结构化的。虽然应用程序日志可能包含 To、From、Amount 等字段,但详细调试跟踪可能是程序员输入的任何内容,例如“检查值 X={value}, Y=false”或随机注释/标记,如“完成了,再试一次”。
一项重要的做法是确保您放入应用程序日志文件或 Windows 事件日志的内容也以相同的详细信息(例如时间戳)记录到跟踪系统。这允许您在调查时关联不同的日志。
如果您计划使用特定的日志查看器,因为您有复杂的相关性,例如服务跟踪查看器,那么您需要使用适当的格式,即 XML。否则,一个简单的文本文件通常就足够了——在较低级别的信息基本上是非结构化的,因此您可能会发现数组转储、堆栈转储等。如果您可以关联回更高级别的更多结构化日志,事情应该好好地。
问:如果使用文件,您是使用滚动日志还是仅使用单个文件?您如何使日志可供人们使用?
答:对于文件,通常您希望从可管理性的角度滚动日志文件(使用 System.Diagnostics,只需使用 VisualBasic.Logging.FileLogTraceListener)。
可用性再次取决于系统。如果您只是在谈论文件,那么对于服务器/服务,可以在必要时访问滚动文件。(Windows 事件日志或数据库应用程序日志将有自己的访问机制)。
如果您无法轻松访问文件系统,那么对数据库的调试跟踪可能会更容易。[即实现一个数据库 TraceListener]。
我为 Windows GUI 应用程序看到的一个有趣的解决方案是,它在运行时将非常详细的跟踪信息记录到“飞行记录器”中,然后当您将其关闭时,如果它没有问题,它就会简单地删除该文件。
但是,如果它崩溃或遇到问题,则不会删除该文件。如果它发现错误,或者下次运行时它会注意到该文件,然后它可以采取措施,例如压缩它(例如 7zip)并通过电子邮件发送或以其他方式提供。
如今,许多系统都将故障自动报告给中央服务器(在与用户核对之后,例如出于隐私原因)。
查看
问:您使用什么工具来查看日志?
答:如果您有多个日志出于不同的原因,那么您将使用多个查看器。
Notepad/vi/Notepad++ 或任何其他文本编辑器是纯文本日志的基础。
如果您有复杂的操作,例如带有传输的活动,那么您显然会使用像服务跟踪查看器这样的专用工具。(但如果你不需要它,那么文本编辑器会更容易)。
由于我通常将高级信息记录到 Windows 事件日志中,因此它提供了一种以结构化方式快速获取概览的方法(查找漂亮的错误/警告图标)。如果日志中没有足够的内容,您只需要开始搜索文本文件,尽管至少日志为您提供了一个起点。(此时,确保您的日志具有协调的整体变得有用)。
通常,Windows 事件日志还使这些重要事件可用于监控工具,如 MOM 或 OpenView。
其他 -
如果您登录到数据库,则可以轻松过滤和排序信息(例如,放大特定的活动 ID。(对于文本文件,您可以使用 Grep/PowerShell 或类似工具来过滤您想要的特定 GUID)
MS Excel(或其他电子表格程序)。如果您可以使用正确的分隔符导入结构化或半结构化信息,这对于分析结构化或半结构化信息很有用,以便不同的值进入不同的列。
在调试/测试中运行服务时,为了简单起见,我通常将它托管在控制台应用程序中,我发现彩色控制台记录器很有用(例如,红色表示错误,黄色表示警告等)。您需要实现自定义跟踪侦听器。
请注意,该框架不包含彩色控制台记录器或数据库记录器,因此,现在,如果您需要它们,则需要编写它们(这不是太难)。
让我很恼火的是几个框架(log4net、EntLib 等)浪费时间重新发明轮子并重新实现了基本的日志记录、过滤和日志记录到文本文件、Windows 事件日志和 XML 文件,每个框架都有自己的不同的方式(每个日志语句都不同);然后,每个人都实现了自己的版本,例如数据库记录器,而其中大部分已经存在,而只需要为 System.Diagnostics 提供更多的跟踪侦听器。谈论重复工作的巨大浪费。
问:如果您正在构建 ASP.NET 解决方案,您是否还使用 ASP.NET 健康监控?您是否在运行状况监视器事件中包含跟踪输出?Trace.axd 呢?
这些东西可以根据需要打开/关闭。我发现 Trace.axd 对于调试服务器如何响应某些事情非常有用,但它通常在使用频繁的环境或长期跟踪中没有用处。
问:自定义性能计数器呢?
对于专业应用程序,尤其是服务器/服务,我希望看到它完全配备了性能监视器计数器和记录到 Windows 事件日志。这些是 Windows 中的标准工具,应该使用。
您需要确保包含您使用的性能计数器和事件日志的安装程序;这些应该在安装时创建(以管理员身份安装时)。当您的应用程序正常运行时,它不需要具有管理权限(因此无法创建丢失的日志)。
这是练习以非管理员身份进行开发的一个很好的理由(当您需要安装服务等时,有一个单独的管理员帐户)。如果写入事件日志,.NET 将在您第一次写入时自动创建丢失的日志;如果您以非管理员身份开发,您将尽早发现这一点,并避免在客户安装您的系统但由于他们没有以管理员身份运行而无法使用它时出现令人讨厌的意外。