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

内容纲要

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

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

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

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

将信息写入输出窗口

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

因此除了 System.Console 之外,还可以使用 System.Diagnostics.Debug 和 System.Diagnostics.Trace。 Debug 和 Trace 都是 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 之外,下面的示例还演示了对 Debug 和 Release 配置启用 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。

条件跟踪

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

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 命名空间中的 Debug 或 Trace 的 Assert 方法。 程序的发行版中不包含 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 时,请确保在删除断言后,断言内的任何代码都不会更改程序的结果。 否则,可能会意外引入仅出现在程序的发行版中的 bug。 请特别注意包含函数或过程调用的断言。

正如你所见,利用 System.Diagnostics 命名空间中的 Debug 和 Trace 是在运行和调试应用程序时提供附加上下文的好方法。

练习 - 日志记录和跟踪

现在,已开始开发应用程序,可以向逻辑添加其他诊断了,以便帮助开发人员添加新功能。 我们可以使用我们学习的关于调试诊断的新知识来完成此任务。

写入调试控制台

在调试应用程序之前,让我们添加其他调试诊断。 当应用程序在调试模式下运行时,其他诊断将有助于诊断应用程序。

在 Program.cs 文件的顶部,我们将添加一个新的 using 语句以引入 System.Diagnostics,以便我们可以使用 Debug 方法。

using System.Diagnostics;

将 WriteLine 语句添加到 Fibonacci 方法的开头,以便在调试代码时清楚地进行展示。

Debug.WriteLine($"Entering {nameof(Fibonacci)} method");
Debug.WriteLine($"We are looking for the {n}th number");

在 for 循环结束时,我们可以打印出每个值。 我们也可以通过使用 WriteIf 或 WriteLineIf 来使用条件打印语句。 仅当 for 循环结束时 sum 为 1 时,才会添加打印行。

for (int i = 2; i <= n; i++)
{                  
    sum = n1 + n2;
    n1 = n2;
    n2 = sum;
    Debug.WriteLineIf(sum == 1, $"sum is 1, n1 is {n1}, n2 is {n2}");    
}

调试应用程序,应显示以下输出:

Entering Fibonacci method
We are looking for the 5th number
sum is 1, n1 is 1, n2 is 1

检查带有断言的条件

在某些情况下,当不满足特定条件时,可能需要停止整个正在运行的应用程序。 使用 Debug.Assert 可以检查条件并输出有关应用程序状态的其他信息。 让我们在 return 语句之前添加检查,以确保 n2 为 5。

// If n2 is 5 continue, else break.
Debug.Assert(n2 == 5, "The return value is not 5 and it should be.");
return n == 0 ? n1 : n2;

我们的应用程序逻辑已经是正确的,接下来,让我们将 Fibonacci(5); 更新为 Fibonacci(6);,其结果会有所不同。

调试应用程序。 当在代码中运行 Debug.Assert 时,调试器将停止应用程序,以便可以检查变量、监视窗口、调用堆栈等。 它还会将消息输出到调试控制台。

---- DEBUG ASSERTION FAILED ----
---- Assert Short Message ----
The return value is not 5 and it should be.
---- Assert Long Message ----

   at Program.<<Main>$>g__Fibonacci|0_0(Int32 n) in C:\Users\Jon\Desktop\DotNetDebugging\Program.cs:line 23
   at Program.<Main>$(String[] args) in C:\Users\Jon\Desktop\DotNetDebugging\Program.cs:line 3

停止调试,然后通过在终端中输入以下命令,在不调试的情况下运行应用程序。

dotnet run

应用程序在断言失败后终止,并且已将信息记录到应用程序输出。

Process terminated. Assertion failed.
The return value is not 5 and it should be.
   at Program.<<Main>$>g__Fibonacci|0_0(Int32 n) in C:\Users\Jon\Desktop\DotNetDebugging\Program.cs:line 23
   at Program.<Main>$(String[] args) in C:\Users\Jon\Desktop\DotNetDebugging\Program.cs:line 3

现在,让我们在终端中输入以下命令,以便在 Release 配置中运行应用程序。

dotnet run --configuration Release

应用程序已成功运行到完成,因为我们不再处于 Debug 配置中。

恭喜,你已成功有效地使用 .NET 的功能(包括 Debug.WriteLine 和 Debug.Assert)调试了代码。 干得不错!

给TA打赏
共{{data.count}}人
人已打赏
.NET

第8章 数据绑定控件(二)

2022-8-23 12:40:22

.NET

第9章 初识 Web Pages

2022-8-23 16:28:37

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索