65.9K
CodeProject 正在变化。 阅读更多。
Home

BaseLib.Tracer: 正确进行跟踪

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.30/5 (10投票s)

2016年10月19日

公共领域

9分钟阅读

viewsIcon

34056

近实时、多线程安全跟踪,适用于任何 .NET 技术

Content

简介

在每个 .NET 项目中,无论使用何种技术,从一开始就需要一些基本功能

跟踪以及如何处理异常。

当然,.NET 和 Visual Studio 提供了一些跟踪功能,但是

  • 使用起来很不方便(Trace, Debug, TraceSource, TraceListener, TraceSource, TraceSwitch, ...)
  • Visual Studio 的跟踪太慢而且阻塞,对于多线程应用程序来说毫无用处
  • 跟踪处理在调用线程上运行,跟踪异常会给调用者带来问题

跟踪和错误处理的要求

  • 从第一行代码即可使用(无需初始设置)
  • 花费时间非常少(大约一微秒)
  • 跟踪处理在其自己的线程上执行
  • 跟踪持续运行,不会引起任何存储问题
  • 跟踪消息过滤和相应处理
  • 易于扩展(将跟踪存储在数据库中,发送电子邮件处理某些问题,在 GUI 中显示某些消息等)

BaseLib.Tracer 将跟踪消息写入一个快速缓冲区。另一个线程清空缓冲区,处理跟踪,最后将消息存储在一个环形缓冲区中,旧的跟踪消息会被覆盖。对于调试和异常调查,通常只需要最新的跟踪消息就足够了。

Design overview

它可以配置为将一些跟踪消息永久存储到文件,和/或使用任何自定义实现的跟踪处理。

典型用例

  • 应用程序从第一行代码开始就将其正在发生的事情写入跟踪,例如它尝试打开哪些文件或其他它尝试访问的资源。如果应用程序未能启动,跟踪信息将显示哪些功能已成功完成,以及哪些功能导致启动失败。当应用程序在远程计算机上运行时,此信息非常有用,因为在远程计算机上很难观察到正在发生的事情。
  • 当发生异常时,跟踪信息会包含在异常消息中,显示在异常发生之前 (!) 发生了什么。
  • 异常和警告消息将存储在日志文件中。如果文件达到一定大小,将创建一个新的日志文件。如果日志文件过多,最早的将被删除。
  • 异常可能会通过电子邮件发送给开发人员,包括跟踪信息。

跟踪消息

跟踪的目的是将(调试)消息写入跟踪。然后可以使用跟踪来调查为什么会发生某个问题,或者更准确地说,应用程序在问题发生之前做了什么。Tracer 根据调用哪种跟踪方法,区分四种类型的跟踪消息

  • Tracer.Trace()
  • Tracer.TraceWarning()
  • Tracer.TraceError()
  • Tracer.TraceException()

应用程序可以决定普通跟踪、警告或错误之间的区别。Tracer 以相同的方式将它们全部写入跟踪,并相应地标记它们。此外,应用程序可以配置,例如将所有消息存储在 RAM 中,仅将警告和错误写入跟踪文件,并将错误通过电子邮件发送到开发帐户。

Tracer.TraceWithFilter() 除了跟踪消息之外,还接受一个文本,该文本以后可用于过滤。

Tracer.TraceException() 应该在捕获到异常时使用。它们被 Tracer 视为其他消息,尽管所有异常详细信息都会在跟踪中显示,并且如果附加了调试器,它可能会执行 Break()

跟踪应用程序启动

为了从执行的第一行代码开始使用完整的跟踪功能,Tracer 被设计为一个 static 类。

像这样跟踪应用程序的启动可能很有用

Tracer.Trace("Application started");

Configuration configuration;
Tracer.Trace("Read Configuration started");
try {
  configuration = ReadConfiguration();
} catch (Exception ex) {
  Tracer.TraceException(ex, "Read Configuration failed, used default configuration");
  configuration  = Configuration .Default;
}
Tracer.Trace(configuration.ToString());
Tracer.Trace("Read Configuration completed");

Tracer.Trace("Start Connect DB");
try {
  ConnectDB();
  Tracer.Trace("DB connected");
} catch (Exception ex) {
  Tracer.TraceException(ex, "Connect DB failed");
}

注释

  • 第一行写入跟踪。除其他外,这对于在跟踪中显示跟踪何时确切启动非常有用。
  • 对于设置应用程序所需的每一步,请使用自己的 _try_ 语句。如果发生异常,请执行其他操作,以便应用程序仍能启动并显示一些(错误)信息。

设置跟踪

应用程序可以配置一些跟踪功能

Tracer.IsTracing = true;
Tracer.IsWarningTracing = true;
Tracer.IsErrorTracing = true;
Tracer.IsExceptionTracing = true;
Tracer.IsBreakOnWarning = false;
Tracer.IsBreakOnError = true;
Tracer.IsBreakOnException = true;

//setup tracing file
TraceLogFileWriter =new TraceLogFileWriter(
  directoryPath: Environment.CurrentDirectory + @"\LogFiles",
  fileName: "FinanceLog",
  fileExtension: "log"
  maxFileByteCount: 10*1000*1000,
  maxFileCount: 5,
  logFileWriterTimerInitialDelay: 1000, //msec
  logFileWriterTimerInterval: 10*1000 //msec
  newFileCreated,
  filter);

这段代码不必在应用程序的最开始,因为 Tracer 可以在没有丢失任何消息的情况下使用其默认设置。可以读取一些(跟踪)配置并在出现问题时进行跟踪,即使 Tracer 尚未设置,然后使用读取的配置数据设置 Tracer

IsXxxTracing 标志控制普通消息、WarningErrorException 是否被写入 Tracer RAM 缓冲区或立即丢弃。

IsBreakOnXxx 控制在跟踪 Xxx 类型消息时调试器是否应中断。开发人员可以通过调试器更改这些标志的值,从而轻松控制调试过程中调试器中断的频率。

TraceLogFileWriter 的构造函数会自动注册到 Tracer.MessagesTraced 事件。即使是已经跟踪的消息也会被写入指定的日志文件。一旦日志文件大小超过 maxFileByteCount,就会创建一个新文件。如果存在的文件超过 maxFileCount,最早的文件将被删除,以防止跟踪耗尽所有磁盘空间。将其中任何一个参数设置为 0 将阻止检查相应的 max 值。newFileCreated 是在创建新日志文件时调用的方法。filter 是在写入日志文件时对消息进行调用的方法。如果返回 true,则该特定消息不会写入日志文件。

访问跟踪消息

要获取所有存储的跟踪消息,只需调用 Tracer.GetTrace()。它返回一个 TraceMessage 数组,按创建时间排序。在执行 GetTrace() 期间,不会添加新的跟踪消息以保证结果的一致性。但是,它们不会丢失,只是在 GetTrace() 完成后写入。

使用 TraceMessage.ToString() 可以简单地显示跟踪消息。

处理异常

抛出异常

通常,调试器会在抛出异常后中断,并在 catch 语句处停止。此时,抛出异常的局部数据已丢失。使用 Tracer.Exception() 来解决此问题

//an error was detected
throw Tracer.Exception(new MyException("Some info"));

Tracer.Exception() 会在调试器中中断,并且导致异常抛出的数据仍然可用。然后开发人员可以继续让调试器运行,然后抛出异常。开发人员可以通过 IsBreakOnException 在运行时控制是否发生中断。

捕获异常

某些异常可能偶尔会发生。如果程序应该通过执行其他操作来响应异常,则应该在 try catch 语句中捕获异常。其余的异常应该在中心位置捕获,这在不同的 .NET 技术中有所不同

技术栈
控制台应用程序 try catch 语句
Windows Forms 应用程序 Application.ThreadException
WPF 应用程序 Application.DispatcherUnhandledException
ASP.NET Global.asax: Application_Error
多线程 每个线程都需要自己的 try catch 语句

无论是本地还是全局捕获异常,在这两种情况下,最好使用 Tracer.TraceException(exception) 进行跟踪。这会将带有所有异常详细信息的异常写入跟踪。如果附加了调试器并且 IsBreakOnExceptiontrue,则详细信息也会显示在调试器输出窗口中。

通常,了解用户在异常发生前做了什么会很有趣。存储用户与应用程序的所有主要交互将产生过多的信息。但是,在使用 Tracer 时,信息仅写入 RAM,并在一段时间后被覆盖。如果抛出异常,则可以获取异常之前的跟踪消息,并将它们添加到异常中,然后可能会将其写入日志文件。

扩展跟踪处理与 GUI 集成

通常,应用程序需要对跟踪消息进行进一步处理,例如向用户(GUI)显示警告、错误和异常,将所有错误和异常收集到中央数据库中,或将异常通过电子邮件发送给开发人员。通过注册 Tracer.MessagesTraced 事件可以轻松完成此操作。

具有用户界面的应用程序可以提供以下功能

  • 显示和编辑 Tracer 设置
  • 向用户显示错误和异常。应该可以轻松复制跟踪信息,以便将其发送给支持人员。
  • 显示当前存储的跟踪

变更历史

版本 1.1:添加刷新功能,即按需将跟踪消息从临时缓冲区移动到最终缓冲区

每条跟踪消息都会暂时存储在一个环形缓冲区中,因此执行跟踪的线程几乎不会延迟(约 1 微秒)。另一个线程运行一个计时器,每 100 毫秒将跟踪消息复制到最终跟踪环形缓冲区,并调用 MessagesTraced 事件的任何侦听器进行进一步处理,这可能很慢,例如写入文件。

这导致了一些问题,例如,当应用程序进行跟踪后立即关闭时。100 毫秒的计时器未运行,也没有调用事件侦听器。看起来跟踪不起作用。当在异常后使用 GetTrace() 检查跟踪时,也发生了类似的问题,因为计时器又没有运行。对于这些情况,添加了一个 Flush() 方法,该方法强制计时器立即运行,将所有消息从临时缓冲区移动到永久缓冲区,调用事件侦听器,然后才返回。

LogFileWriter 获得了 Dispose() 方法和析构函数,当应用程序关闭时,它们保证在调用这些方法之前,所有跟踪消息都会被写入跟踪文件。在此之前,丢失了 0 到 100 毫秒之间的跟踪消息。

DotNet 框架版本已降至 4.0。

重大更改

TraceMessage[] GetTrace(Action<TraceMessage[]> MessagesTracedHandler) 已重命名为 TraceMessage[] AddMessagesTracedListener(Action<TraceMessage[]> MessagesTracedHandler),以更好地反映该方法的实际用途。

将 ACoreLib 从 CodePlex 迁移到 Github 上的 BaseLib

原始文章是为我在 CodePlex.com 上共享的 ACoreLib 编写的。不幸的是,微软关闭了这个网站,我不再拥有 ACoreLib 的源代码,因为我不断添加新代码并将库重命名为 BaseLib,我在 GitHub.com 上共享它。功能仍然相同,但有些名称已更改。我已使用新名称更新了文章。

下载代码

本文的代码位于 BaseLib/Tracer.cs

如果您对更快、非阻塞的跟踪感兴趣,这有助于调试竞态条件,请阅读本文

© . All rights reserved.