.NET 应用程序中的日志记录和跟踪

已完成

在继续开发应用程序且过程变得更复杂时,你需要对应用程序应用其他调试诊断。

跟踪是在应用程序运行时监视其执行情况的一种方式。 开发.NET 应用程序时,可以向其添加跟踪和调试检测。 可在开发应用程序期间以及部署应用程序后使用该检测。

这一简单技术功能非常强大。 可以在需要多个调试器的情况下使用:

  • 传统的调试器可能难以调试长期存在的问题。 通过日志,可以对较长的时间跨度进行详细的事后剖析。 与此相反,调试器限制为只能进行实时分析。
  • 多线程应用程序和分布式应用程序通常难以调试。 附加调试器往往会修改行为。 可以根据需要分析详细日志,以了解复杂的系统。
  • 分布式应用程序中的问题可能是由许多组件之间的复杂交互导致的。 将调试器连接到系统的每个部分可能并不合理。
  • 许多服务不应停止。 附加调试器往往会导致超时失败。
  • 问题并非总是可预见的。 日志记录和跟踪旨在降低开销,以便在出现问题的情况下可以始终记录程序。

将信息写入输出窗口

到目前为止,我们一直在使用控制台向应用程序用户显示信息。 其他类型的应用程序是使用 .NET 生成的,其中包含移动应用、Web 应用和桌面应用等用户界面,并且没有可见的控制台。 在这些应用程序中,System.Console 可在“幕后”记录消息。这些消息可能会显示在 Visual Studio 或 Visual Studio Code 的输出窗口中。 它们还可能会输出到系统日志,如 Android 的 logcat。 因此,当在非控制台应用程序中使用 System.Console.WriteLine 时,应慎重考虑。

在这里,除了 System.Console,还可以使用 System.Diagnostics.DebugSystem.Diagnostics.TraceDebugTrace 都是 System.Diagnostics 的一部分,并且仅在附加了相应的侦听器时写入日志。

选择使用哪种打印样式 API 由用户自己决定。 主要区别包括:

  • System.Console
    • 始终启用,并始终写入控制台。
    • 适用于客户可能需要在发行版中看到的信息。
    • 由于这是最简单的方法,所以常常用于临时调试。 此调试代码通常不会签入到源代码管理中。
  • System.Diagnostics.Trace
    • 仅在定义 TRACE 时启用。
    • 写入附加侦听器,默认情况下为 DefaultTraceListener。
    • 创建将在大多数生成中启用的日志时,请使用此 API。
  • System.Diagnostics.Debug
    • 仅在定义 DEBUG 时才启用(处于调试模式时)。
    • 写入附加调试器。
    • 创建仅在调试生成中启用的日志时,请使用此 API。
Console.WriteLine("This message is readable by the end user.");
Trace.WriteLine("This is a trace message when tracing the app.");
Debug.WriteLine("This is a debug message just for developers.");

设计跟踪和调试策略时,请考虑自己所需的输出形式。 使用不相关信息填充的多个 Write 语句会创建难以阅读的日志。 另一方面,如果使用 WriteLine 将相关语句放置在单独的行上,可能会难以区分哪些信息应该在一起。 通常,当需要将来自多个源的信息组合起来创建单个信息性消息时,使用多个 Write 语句。 当需要创建单个完整消息时,使用 WriteLine 语句。

Debug.Write("Debug - ");
Debug.WriteLine("This is a full line.");
Debug.WriteLine("This is another full line.");

此输出来自前面使用 Debug 生成的日志记录:

Debug - This is a full line.
This is another full line.

定义 TRACE 和 DEBUG 常数

默认情况下,当应用程序在调试模式下运行时,将定义 DEBUG 常数。 可以通过在属性组的项目文件中添加 DefineConstants 条目进行控制。 除了对 Debug 配置启用 DEBUG 之外,下面的示例还演示了对 DebugRelease 配置启用 TRACE

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DefineConstants>TRACE</DefineConstants>
</PropertyGroup>

如果在未附加到调试器时使用 Trace,则需要配置跟踪侦听器,如 dotnet-trace

条件跟踪

除了简单的 WriteWriteLine 方法之外,还可以使用 WriteIfWriteLineIf 添加条件。 例如,以下逻辑将检查计数是否为零,然后写入调试消息:

if(count == 0)
{
    Debug.WriteLine("The count is 0 and this may cause an exception.");
}

可以在单个代码行中重写它:

Debug.WriteLineIf(count == 0, "The count is 0 and this may cause an exception.");

还可以将这些条件用于 Trace 以及在应用程序中定义的标志:

bool errorFlag = false;  
System.Diagnostics.Trace.WriteIf(errorFlag, "Error in AppendData procedure.");  
System.Diagnostics.Debug.WriteIf(errorFlag, "Transaction abandoned.");  
System.Diagnostics.Trace.Write("Invalid value for data request");

验证是否存在特定条件

断言或 Assert 语句会测试指定为 Assert 语句参数的条件。 如果条件的计算结果为 true,则不会发生任何操作。 如果条件的计算结果为 false,则断言将失败。 如果运行的是调试生成,则程序会进入中断模式。

可以使用位于 System.Diagnostics 命名空间中的 DebugTraceAssert 方法。 程序的发行版中不包含 Debug 类方法,因此它们不增大发行代码的大小,也不会减慢发行代码的速度。

自由使用 System.Diagnostics.Debug.Assert 方法来测试代码正确时应为 true 的条件。 例如,假设你编写了一个整数除法函数。 根据数学规则,除数绝不能为零。 你可以使用断言测试此条件:

int IntegerDivide(int dividend, int divisor)
{
    Debug.Assert(divisor != 0, $"{nameof(divisor)} is 0 and will cause an exception.");

    return dividend / divisor;
}

当在调试器中运行此代码时,将对断言语句进行评估。 但在发行版中不会进行此比较,因此不会产生额外的开销。

注意

使用 System.Diagnostics.Debug.Assert 时,请确保在删除 Assert 后,Assert 内的任何代码都不会更改程序的结果。 否则,可能会意外引入仅出现在程序的发行版中的 bug。 请特别注意包含函数或过程调用的断言。

利用 System.Diagnostics 命名空间中的 DebugTrace 是在运行和调试应用程序时提供附加上下文的好方法。