TracerX Logger and Viewer for .NET
易于使用的日志记录器,并附带功能强大的查看器,支持按线程、日志记录器等进行过滤。
介绍
.NET 应用程序的 TracerX 日志记录器易于上手,但又具备高级功能。它可以将输出发送到多个目的地(循环文件、事件日志等)。它可以生成文本和/或二进制文件。
它有一个功能强大的查看器,支持按线程、类别、跟踪级别等进行过滤和着色。查看器还可以折叠和展开每个方法调用的输出,显示绝对或相对时间戳,并查看/遍历导致任何输出行的调用堆栈。根据我的经验,这些功能使得诊断问题比使用纯文本编辑器容易得多。
本文将解释如何使用 TracerX,并包含一些内嵌代码示例。
TracerX 是一个 C# Visual Studio 2019 项目(以 .NET 3.5、.NET 4.0、.NET 4.6 和 .NET Core 为目标)。它被许多商业产品所使用。
日志记录器组件可作为 NuGet 包使用(只需搜索 TracerX)。
日志记录器、查看器和(可选但非常适合远程访问的)服务源代码和二进制文件可在 GitHub 上获取(MarkLTX/TracerX (github.com))。
目录
TracerX 查看器
本文主要介绍如何使用 TracerX 日志记录器,但查看器是主要吸引点。这是 TracerX 日志查看器的屏幕截图。
以下大多数功能都可以通过探索菜单来发现,但有些功能需要双击才能访问。不要忘记尝试行和列标题的上下文菜单!
- 您可以按线程名称、线程编号、日志记录器、跟踪级别、文本通配符和方法名称来过滤和/或着色日志。
- 消息文本会根据堆栈深度进行缩进。
- 您可以双击显示“+”和“-”的行来折叠和展开方法调用的输出。
- 您可以使用面包屑导航条和/或上下文菜单在调用堆栈中上下导航。
- 您可以点击面包屑导航条中的箭头来查看(并跳转到)给定级别调用的方法。
- 您可以查看绝对或相对时间戳。任何一行都可以作为“零时间”记录。
- 您可以双击带有黄色三角形的行来折叠和展开包含嵌入式换行符的行。
- 您可以为单个行或所有行添加书签,这些行...
- 包含指定的搜索字符串。
- 来自一个或多个选定的线程、日志记录器或跟踪级别。
- 您可以在一个窗口中查看(并导航到)导致所选行的调用堆栈。
- 您可以跳转到同一线程的下一个块,或者不同线程的下一个块。
- 您可以选择行并将文本列或所有列复制到剪贴板。
- 您可以自定义列(显示/隐藏列,更改它们的顺序)。
- 长消息(带或不带嵌入式换行符)可以在可调整大小的文本窗口中查看。
TracerX 的“Hello World”
这是一个生成 TracerX 日志文件的非常简单的程序。
using TracerX;
namespace HelloWorld
{
class Program
{
// Create a Logger named after this class (could have any name).
static Logger Log = Logger.GetLogger("Program");
static void Main(string[] args)
{
// Open the output file using default settings.
Logger.DefaultBinaryFile.Open();
// Log a string.
Log.Info("Hello, World!");
}
}
}
Main()
中的第一条语句使用默认文件名(从可执行文件名称派生)在默认目录(用户的 ApplicationData
目录)中打开输出文件。
第二条语句通过名为“Program”的 Logger
将字符串以 Info
跟踪级别记录下来。
输出在查看器中显示如下。请注意,某些列已隐藏,并且在打开文件时会自动记录第 1-16 行(如果您愿意,也可以禁用它们)。
日志记录基础
让我们超越“hello world”,介绍 TracerX 的一些底层概念和功能。
日志记录器
TracerX 使用 log4net 的日志记录器层次结构概念。所有日志记录都通过 Logger
类实例进行。每个 Logger
都有一个名称,该名称会显示在查看器中的“Logger”列下。每个 Logger
都应代表应用程序中的一个日志记录类别,例如“Reporting”或“Configuration”。
您必须决定创建多少 Logger
以及它们的名称。一种方法是为应用程序中的每个类创建一个 Logger
,如下所示。
using TracerX;
class CoolClass
{
private static readonly Logger Log =
Logger.GetLogger("CoolClass");
// etc.
}
静态方法 Logger.GetLogger()
是在代码中创建 Logger
的唯一方式。它有以下重载。
public static Logger GetLogger(string name)
public static Logger GetLogger(Type type)
GetLogger()
会创建具有指定名称的 Logger
实例(如果它尚不存在)。该名称会显示在查看器中的“Logger”列下。所有具有相同名称的后续调用都会返回相同的实例,因此,如果需要,多个不同文件中的代码可以使用相同的 Logger
。
Type
会被转换为其完全限定名称并传递给 string
重载。
日志记录器树
Logger
名称可以包含句点,例如“MyNamespace.CoolClass”。TracerX 使用名称中由句点分隔的子字符串来创建 Logger
的树。例如,“A.B”是“A.B.Anything”的父级。请注意,“A.B”不必存在,“A.B.C”就可以存在。Logger
的创建顺序无关紧要。TracerX 在创建时将每个 Logger
插入到树中的相应位置。
预定义日志记录器
TracerX 具有以下预定义的日志记录器(Logger
类的 static
实例)。
预定义日志记录器 | 描述 |
Logger.Root | Logger 树的根。所有其他 Logger (包括您创建的)都是 Root Logger 的后代。您可以通过 Logger.Root 进行所有日志记录,而无需调用 GetLogger() ,但这会使您在查看器中无法按 Logger 名称过滤输出,也无法在运行时控制不同代码区域的日志量。 |
Logger.Current | 调用线程最后引用的 |
Logger.StandardData | TracerX 内部用于在日志文件打开时记录有关环境、计算机和用户信息的 Logger 。 |
跟踪级别
TracerX 记录的每个消息都有以下“跟踪级别”之一(从最低到最高)。这些是 TracerX.TraceLevel
enum
类型的值。
Fatal(致命)
Error(错误)
Warn(警告)
Info(信息)
Debug
Verbose(详细)
CodeProject 文章 《日志记录的艺术》 [^] 包含了一份关于为每条消息选择适当 TraceLevel
的优秀指南。
如何记录消息
假设您有一个名为 Log
的 Logger
实例,您可以使用如下语句进行日志记录。
Log.Info("A string logged at the Info level.");
Log.Debug("Several objects at Debug level ", obj1, obj2);
消息的跟踪级别取决于您调用的方法(Log.Info()
、Log.Debug()
等)。
您可以向上述方法传递任意数量的参数。参数不必是字符串。如果(并且仅当)方法的跟踪级别已启用,每个参数都会转换为 string
并连接起来,形成在查看器的“Text”列中显示的消息。您应该避免自己调用 ToString()
,因为如果消息因其跟踪级别未被记录而浪费了处理时间。
每个跟踪级别还有一个版本,其语义与 string.Format()
相同,如下所示。
Log.VerboseFormat("obj1 is {0}.", obj1);
如何记录方法调用
记录您的方法调用可以启用查看器中的几个强大功能,例如按方法名称过滤/着色、折叠/展开给定方法调用的输出以及查看/遍历调用堆栈的能力。要记录给定方法的调用,请将方法的内容包装在一个 using
块中,如下所示(这假定您已创建了一个名为 Log
的 Logger
)。
private void YourMethod()
{
using (Log.InfoCall())
{
// Other code goes here!
}
}
与之前一样,跟踪级别由 Logger
方法的名称隐含(例如,Log.InfoCall()
、Log.DebugCall()
等)。如果启用了该跟踪级别,则会发生以下情况。
- TracerX 获取调用方法的名称(即“YourMethod”)。
- 一条消息,如“YourMethod: entered”,会被记录下来。
- 在“
using
”块内的任何日志记录的缩进都会增加。 - 查看器会在方法列中显示“YourMethod”,用于“
using
”块内的任何日志记录。 - 返回一个对象,其
Dispose
方法记录一条消息,如“YourMethod: exiting”,并恢复线程的先前缩进和方法名称。
您可以选择将方法名称作为 string
传递给 InfoCall()
、DebugCall()
等。S这样做的几个原因是:
- 您打算混淆您的代码(并且您不希望日志中出现混淆后的方法名称)。
- 您想在“
if
”语句、“for
”循环或其他块中模拟方法调用。 - 您想使用“自定义”方法名称或在方法名称中包含额外信息。
仅供参考,所有对 InfoCall()
、DebugCall()
等的调用都返回相同的对象。如果此类调用的跟踪级别未启用,则调用将返回 null
。
请注意,此功能与 async/await 不兼容。它要求“using”块的进入和退出发生在同一线程上。在“using”块中使用 await 关键字违反了这一要求,并导致 NullReferenceExceptions 等问题。
另一个代码示例
此代码演示了一种在 Program.Main()
调用之前初始化 TracerX 的技术。然后,它使用不同的日志记录器记录了几个消息和方法调用。
using System;
using System.Threading;
using System.IO;
using TracerX;
namespace Sample
{
class Program
{
private static readonly Logger Log = Logger.GetLogger("Program");
// Just one way to initialize TracerX early.
private static bool LogFileOpened = InitLogging();
// Initialize the TracerX logging system.
private static bool InitLogging()
{
// It's best to name most threads.
Thread.CurrentThread.Name = "MainThread";
// Load TracerX configuration from an XML file.
Logger.Xml.Configure("TracerX.xml");
// Open the log file.
return Logger.DefaultBinaryFile.Open();
}
static void Main(string[] args)
{
using (Log.InfoCall())
{
Helper.Bar();
Helper.Foo();
}
}
}
class Helper {
private static readonly Logger Log = Logger.GetLogger("Helper");
public static void Foo()
{
using (Log.DebugCall())
{
Log.Debug(DateTime.Now, " is the current time.");
Bar();
}
}
public static void Bar()
{
using (Log.DebugCall())
{
Log.Debug("This object's type is ", typeof(Helper));
}
}
}
}
日志记录目的地
每次调用 Logger.Info()
或 Logger.Debug()
等日志记录方法时,都可以将其输出发送到六个可能的目标中的任何组合。消息发送到哪些目标取决于其 TraceLevel
。每个目标在 Logger
类中都有一个属性,该属性指定 Logger
实例发送到该目标的最大 TraceLevel
。这些属性决定了哪些消息被发送到哪些目标。
目标 | 日志记录器属性 | Root Logger 中的初始值 | 其他 Loggers 中的初始值 |
二进制文件 | BinaryFileTraceLevel | TraceLevel.Info | TraceLevel.Inherited(继承) |
文本文件 | TextFileTraceLevel | TraceLevel.Off(关闭) | TraceLevel.Inherited(继承) |
控制台(即命令窗口) | ConsoleTraceLevel | TraceLevel.Off(关闭) | TraceLevel.Inherited(继承) |
Trace.WriteLine() | DebugTraceLevel | TraceLevel.Off(关闭) | TraceLevel.Inherited(继承) |
事件日志 | EventLogTraceLevel | TraceLevel.Off(关闭) | TraceLevel.Inherited(继承) |
事件处理程序 | EventHandlerTraceLevel | TraceLevel.Off(关闭) | TraceLevel.Inherited(继承) |
例如,假设您声明了一个名为 Log
的 Logger
,并且您的代码包含以下语句。
Log.Info("Hello, World!");
此语句的 TraceLevel
是 Info
。因此……
- 如果
Log.BinaryFileTraceLevel
>=TraceLevel.Info
,则消息将被发送到二进制日志文件。 - 如果
Log.TextFileTraceLevel
>=TraceLevel.Info
,则消息将被发送到文本日志文件。 - 如果
Log.ConsoleTraceLevel
>=TraceLevel.Info
,则消息将被发送到控制台。 - 如果
Log.DebugTraceLevel
>=TraceLevel.Info
,则消息将传递给Trace.WriteLine()
。 - 如果
Log.EventLogTraceLevel
>=TraceLevel.Info
,则消息将发送到应用程序事件日志。 - 如果
Log.EventHandlerTraceLevel
>=TraceLevel.Info
,则会引发Logger.MessageCreated
事件。
请注意,对于所有您创建的 Logger
,这些属性的初始值均为 Inherited
。这意味着每个属性的有效 TraceLevel
值将从父 Logger
继承,一直到根 Logger
。因此,您的 Logger
将继承上面表格中显示的 Root 跟踪级别,除非您显式覆盖它们,但这通常不会这样做。大多数用户只需设置 Root 级别,然后让所有其他 Logger
继承它们。
另请注意,根 Logger 对所有目的地(除二进制文件外)的初始(默认)TraceLevel
为 Off
。这意味着除非您更改这些 TraceLevel
,否则不会向这些目的地发送任何输出。典型的场景是应用程序设置以下级别。
Logger.Root.BinaryFileTraceLevel = TraceLevel.Debug;
Logger.Root.EventLogTraceLevel = TraceLevel.Error;
这会导致任何级别为 Debug
或更低的日志消息写入文件,而级别为 Error
或更低的日志消息也会写入事件日志。
无论这些属性如何设置,除非二进制文件或文本文件已打开,否则不会向它们发送任何输出。
文件日志记录
TracerX 支持两种类型的输出文件:二进制和文本。这两种类型都包含每次记录的消息的时间戳、线程名称/编号、跟踪级别和方法名称。每个 Logger
都可以写入两种文件,其中一种,或都不写入,具体取决于配置。
每个 Logger
都有一个 BinaryFile
属性,用于指定该 Logger
使用的二进制文件。此属性最初等于静态属性 Logger.DefaultBinaryFile
,以便所有 Logger
共享相同的二进制文件。因此,当您配置和打开 Logger.DefaultBinaryFile
时,您就是在配置和打开共享二进制文件。您可以为单个 Logger
(或一组 Logger
)创建自己的 BinaryFile
实例。
对于文本文件,也适用相同的模式,只是文本文件的类是 TextFile
,相应的 Logger
实例属性是 TextFile
,而所有实例初始化时对应的静态属性是 Logger.DefaultTextFile
。
使用二进制文件的一个主要优点是您可以使用 TracerX-Viewer.exe 应用程序查看它,该应用程序具有强大的过滤、搜索、着色、格式化和导航功能。二进制文件也比文本文件更紧凑(取决于您在文本文件的格式字符串中指定的字段)。二进制文件的缺点是您只能使用查看器查看它。为了获得两全其美,您可以选择同时生成这两种文件。然后,如果您有查看器,您可以使用它;如果没有,您可以使用记事本。
最大文件大小
这两种文件类型都需要您指定最大文件大小,最高可达 4095 兆字节。通常,您会指定 1 到 100 兆字节。当文件达到最大大小时会发生什么取决于文件的 FullFilePolicy
属性设置。它是一个 enum
,具有以下值。
Wrap(换行)
Roll(滚动)
Close
循环日志记录
两种文件类型都支持循环日志记录,当 FullFilePolicy
设置为 Wrap
时发生。在循环日志记录中,文件被分成两部分。第一部分被保留,永远不会丢失(直到文件本身被删除)。第二部分是循环部分。当日志文件增长到指定的*最大*大小时,TracerX 会“换行”文件。也就是说,它会将写入位置设置回循环部分的开头,并开始替换旧输出与新输出。这可能会发生很多次,而且是完全自动的。
建议对长时间运行的应用程序和可能生成不可预测的大量输出(如服务)的程序使用循环日志记录。
Viewer 应用程序会自动查找循环部分二进制文件中的第一个时间顺序记录,并按时间顺序显示所有记录。对于文本文件,您需要手动完成此操作。
要启用循环日志记录,必须将配置属性 FullFilePolicy
设置为 Wrap
,并且 CircularStartSizeKb
和/或 CircularStartDelaySeconds
(稍后描述)不得为零。
归档
两种文件类型都支持归档。当正在打开一个新的输出文件并且已存在同名的旧文件时,会发生归档。TracerX 会通过将 _01 追加到基本文件名来“归档”(即备份)现有文件。现有的 _01 文件会变成 _02 文件,依此类推,直到您指定的*值*。最旧的文件会获得*最大*的数字。您可以指定的*最大*值为 99。
如果 FullFilePolicy
是 Roll
并且文件增长到其*最大*指定大小时,文件将被关闭、归档并重新打开。这可能发生很多次,并且类似于 log4net 的 RollingFileAppender
。当归档与追加到文本文件结合使用时,来自给定执行的输出可能会开始在 LogFile_02.txt 的*中间*,并结束于 LogFile.txt。这可能会令人困惑。
追加
两种文件类型都支持(可选)在日志打开时追加到现有文件,而不是归档文件。TracerX 可以配置为从不追加,或者在现有文件*不是*太大的时候追加。
配置属性
每个文件类(BinaryFile
和 TextFile
)都有几个配置属性。这些属性在下表中进行了描述。它们都有合理的默认值,因此您可以轻松开始(记住 Hello World),但您可能至少想在生产代码中设置目录。例如,要设置创建文件的目录,您可以编写如下代码。
Logger.DefaultBinaryFile.Directory = "C:\\Logs";
// and/or
Logger.DefaultTextFile.Directory = "C:\\Logs";
以下属性是两个文件对象共有的(实际上是从基类继承的)。其中大多数属性在相应文件打开后无法更改或不应更改。除 Name
外,都可以通过 XML 配置文件设置,方法是在设置代码中的这些属性之前、之后或代替调用 Logger.XML.Configure()
。请注意,XML 配置仅适用于 Logger.DefaultBinaryFile
和 Logger.DefaultTextFile
,而不适用于您自己创建的文件对象。
属性 | 描述 |
Directory(目录) | 文件所在目录。默认值对应于 设置此项时会展开环境变量。以下伪 %变量% 也受支持。 %LOCAL_APPDATA% 被替换为 %COMMON_APPDATA% 被替换为 %DESKTOP% 被替换为 %MY_DOCUMENTS% 被替换为 %EXEDIR% 被替换为可执行文件所在的目录,无论当前目录如何。 |
名称 | 文件名。默认值是进程或 AppDomain 的名称。对于文本文件,文件扩展名将被强制转换为“.txt”,对于二进制文件,则强制转换为“.tx1”,无论您指定什么,所以最好省略扩展名。 如果您希望文件名以“_00”结尾,请将 |
AppendIfSmallerThanMb | 此设置决定是归档现有文件(如果存在)(请参阅 Archives),还是以追加模式打开。如果现有文件小于指定*兆字节*数(1 MB = 2^20 字节),则以追加模式打开。否则,会将其归档。因此,如果此属性为 0(默认值),则在打开时文件始终会被归档,并启动一个新文件。 |
MaxSizeMb | 此设置指定文件允许增长*兆字节*数(当文件被创建而不是追加时,它真正指定了最大大小)。当文件增长到此大小时会发生什么取决于 FullFilePolicy 。如果启用了循环日志记录(请参阅 CircularStartSizeKb 和 CircularStartDelaySeconds ),它将换行。如果未启用循环日志记录,并且文件已增长到此大小时,文本文件将被关闭、归档(请参阅 Archives)并重新打开。二进制文件将被简单地关闭。 |
FullFilePolicy | 此设置决定当文件增长到其*最大*允许大小时会发生什么。 Wrap(换行) - 如果循环日志记录已开始(基于 Roll(滚动) - 文件被关闭、归档(请参阅 Close(关闭) - 文件被关闭(游戏结束)。 |
Archives(归档) | 指定要保留的日志文件的*归档*(备份)数量。该值必须在 0 - 9999 的范围内。如果现有文件未以追加模式打开,则通过在文件名后附加“_01”来重命名它进行归档。现有的 _01 文件重命名为 _02,依此类推。大于指定*值*的现有归档将被删除。如果指定的*值*为 0,则在打开新文件时,现有文件将被简单地替换。 |
Use_00 | 如果此 bool 为 true ,“_00”将被追加到 Name 属性指定的文件名。这将使文件在 Explorer 中逻辑上更易于排序并与其相关的“_01”、“_02”等文件分组。 |
UseKbForSize | 如果此 bool 为 true ,则 MaxSizeMb 和 AppendIfSmallerThanMb 属性的单位为千字节(KiloBytes),而不是兆字节(MegaBytes)。 |
CircularStartSizeKb | 当 FileFullPolicy 为 Wrap 时适用。当文件增长到此 KB 大小时(1 KB = 1024 字节),此点之后的*部分*(直到*最大*大小)将成为循环部分。在这一点之前的*部分*将始终被保留,无论循环部分换行多少次。将其设置为 0 可禁用此功能。 |
CircularStartDelaySeconds | 当 FileFullPolicy 为 Wrap 时适用。当文件打开*此*秒数后,循环日志将开始。将其设置为 0 可禁用此功能。 |
还有几个只读属性,如 CurrentSize
、CurrentPosition
和 Wrapped
。
文件事件
两个文件类都有以下事件,您可以附加处理程序。传递给每个处理程序的“sender
”对象是正在打开或关闭的 BinaryFile
或 TextFile
对象。如果 FullFilePolicy
是 Roll
,则每次文件关闭并重新打开时都会引发这些事件。
- Opening(打开中) - 在文件打开之前引发。这是设置
Directory
、Name
、MaxSizeMb
等属性的最后机会,然后才能被执行实际打开、归档等操作的内部代码引用。 - Opened(已打开) - 文件打开后引发。
- Closing(关闭中) - 在文件关闭之前引发。
- Closed(已关闭) - 文件关闭后引发。
二进制文件日志记录
要将输出发送到二进制文件,请执行以下操作。
- 设置您的
Logger
对象的BinaryFileTraceLevel
属性,以将所需详细程度发送到二进制文件。默认值为TraceLevel.Info
,因此您必须更改它才能从Logger.Debug()
或Logger.Verbose()
获取任何输出。通常,您只需设置Logger.Root.FileTraceLevel
,所有其他日志记录器(如果有)都会继承该值。 - 根据需要设置
Logger.DefaultBinaryFile
的属性,例如Directory
属性。 - 调用
Logger.DefaultBinaryFile.Open()
来打开文件。文件名、位置、追加模式和其他行为取决于对象的各种属性。
BinaryFile
类除了前面表格中给出的属性外,还有以下属性。
属性 | 描述 |
密码 | 指定的密码将用作 AES 加密密钥来加密文件内容,并且密码的单向哈希将存储在文件头中。Viewer 应用程序将提示用户输入密码。在 Viewer 打开和解密文件之前,必须匹配哈希值。使用此功能时,性能会下降约 60%。 |
PasswordHint(密码提示) | 如果 Password 和 PasswordHint 都不为 null 或空,则 PasswordHint 将存储在文件头中,并在 Viewer 提示输入密码以打开文件时显示给用户。 |
AddToListOfRecentlyCreatedFiles(添加到最近创建文件列表) | 此布尔值决定该文件是否会出现在查看器的最近创建文件列表中。 |
当二进制文件打开时,一些关于当前执行环境的“标准数据”会通过一个预定义的 Logger
(称为 StandardData
)自动为您记录。StandardData
日志记录器的目的是让您控制此输出。例如,Verbose
输出可能被认为是敏感的,因此您可以在打开文件之前通过编写以下代码来阻止它。
Logger.StandardData.BinaryFileTraceLevel = TraceLevel.Debug;
// or
Logger.StandardData.BinaryFileTraceLevel = TraceLevel.Off;
文本文件日志记录
要将输出发送到文本文件,请执行以下操作。
- 设置您的
Logger
对象的TextFileTraceLevel
属性,以将所需详细程度发送到文本文件。默认值为TraceLevel.Off
,因此您必须更改它才能在文本文件中获得任何输出。通常,您只需设置Logger.Root.TextFileTraceLevel
,所有其他日志记录器(如果有)都会继承该值。 - 根据需要设置
Logger.DefaultTextFile
的属性,例如Directory
属性。 - 调用
Logger.DefaultTextFile.Open()
来打开文件。文件名、位置、追加模式和其他行为取决于对象的各种属性。
除了前面表格中介绍的属性外,TextFile
类还有一个名为 FormatString
的附加属性,用于确定写入每个记录的字段。它类似于传递给 string.Format()
的格式字符串,但使用以下替换参数。
替换参数 | 类型 | 描述 |
{msg} | 字符串 | 传递给 Logger.Info() 、Logger.Debug() 等的日志消息。 |
{line} | ulong | 行号/计数器。在应用程序启动时初始化(而不是在文件重新打开时),并且每次写入文件的一行时递增。 |
{level} | 字符串 | TracerX.TraceLevel 枚举值之一(例如,Warning 、Info 等)。 |
{logger} | 字符串 | 用于记录消息的 Logger 对象名称。 |
{thnum} | int | 分配给记录消息的线程的编号。此编号与 Thread.ManagedThreadID 或操作系统线程 ID 无关。它只是 TracerX 在给定线程第一次调用时递增的计数器。 |
{thname} | 字符串 | 消息被记录时调用线程的名称。 |
{time} | 日期时间 | 消息被记录时系统时区的时间。 |
{method} | 字符串 | 记录消息的方法名称。要获得有意义的值,您必须在代码中使用 Logger.DebugCall() 、Logger.InfoCall() 等。 |
{ind} | 字符串 | 缩进(空格)。长度取决于调用者的堆栈深度。当您在代码中使用 Logger.DebugCall() 、Logger.InfoCall() 等时,此值会发生变化。 |
这些替换参数的工作方式与传递给 string.Format()
的 {0}、{1} 等类似。适用于每个替换参数类型的格式说明符可以包含在 FormatString
中。例如,您可以指定以下内容。
Logger.DefaultTextFile.FormatString = '{line,6}, {time:HH:mm:ss.fff}, {msg}';
这将把行号填充到 6 个空格,在时间戳中包含毫秒,并用逗号分隔字段。
控制台日志记录
要将应用程序的“实时”日志记录发送到控制台(即命令窗口),请设置您的 Logger
对象的 ConsoleTraceLevel
属性,以获取所需的详细程度(例如,对于所有日志消息,设置为 TraceLevel.Verbose
)。
此纯文本输出的格式由 Logger.ConsoleLogging.FormatString
确定,它使用与 Logger.DefaultTextFile.FormatString
相同的替换参数。
调试日志记录
这指的是传递给 System.Diagnostics.Trace.WriteLine()
的输出。此功能最初使用 Debug.WriteLine()
(因此称为“调试日志记录”),但后来我意识到它可能在发布版本中有用。当在调试器中运行应用程序时,此输出会出现在 Visual Studio 的 Output 窗口中。有一些实用程序可以拦截并显示此输出,而无需 Visual Studio。
要启用此输出,请将您的 Logger
对象的 DebugTraceLevel
属性设置为所需详细程度(例如,对于所有日志消息,设置为 TraceLevel.Verbose
)。
此纯文本输出的格式由 Logger.DebugLogging.FormatString
确定,它使用与 Logger.DefaultTextFile.FormatString
相同的替换参数。
事件日志记录
日志记录可以发送到您选择的事件日志,但这可能应该比其他目的地更谨慎地使用。例如,您应该只将其限制为某些日志记录器和/或跟踪级别。
Logger.EventLogging
是一个用于配置此功能的静态类。它具有以下属性。
属性 | 描述 |
EventLog(事件日志) | 指定 TracerX 用于所有事件日志记录的事件日志。初始值设置为 |
FormatString(格式字符串) | 指定写入事件日志的字段。有关替换参数,请参阅*文本文件日志记录*主题。 |
MaxInternalEventNumber(最大内部事件编号) | 这适用于 TracerX 的内部事件,而不是您的日志消息。它指定 TracerX 允许记录的*最大*内部事件编号。稍后将详细介绍。默认值 = 200。 |
InternalEventOffset(内部事件偏移) | 在记录 TracerX 的内部事件编号之前添加的一个数字。使用此选项可防止 TracerX 记录的事件编号与您的应用程序的事件编号冲突。默认值 = 1000。 |
EventIdMap(事件 ID 映射) | 一个 Dictionary<TraceLevel, ushort> ,用于指定每个 TraceLevel 值使用的事件 ID 编号。此项无法在 XML 中设置。 |
EventTypeMap(事件类型映射) | 一个 Dictionary<TraceLevel, EventLogEntryType> ,用于指定每个 TraceLevel 值使用的 EventLogEntryType 。此项无法在 XML 中设置。 |
除了您的日志消息外,TracerX 还可以记录它自己的内部事件,特别是当它在打开输出文件或解析 XML 配置文件时遇到错误时。内部事件编号分为三个范围。
- 1-100 = 错误事件,例如由于权限不足而无法打开日志文件。
- 101-200 = 警告事件,例如日志文件达到最大大小时未启用循环日志记录。
- 201-300 = 信息事件,例如日志文件成功打开。
如果您想阻止所有这些事件,可以将 Logger.EventLogging.MaxInternalEventNumber
设置为 0。但是,推荐值(也是默认值)是 200,因此如果发生错误和警告,它们将被记录下来。
事件处理程序日志记录
Logger
类有一个名为 MessageCreated
的 public static event
,可以在每次记录消息时引发。要启用此功能,请设置您的 Logger
对象的 EventHandlerTraceLevel
属性,以获取所需的详细程度(例如,对于所有日志消息,设置为 TraceLevel.Verbose
)。
传递给事件处理程序的“sender
”参数是通过该消息记录的 Logger
对象。“args
”参数包含消息的 TraceLevel
、线程名称、日志记录器名称、消息文本等的独立字段。这使您能够为任何消息执行自定义处理。例如,您可以执行以下任何操作。
- 如果消息来自某个特定
Logger
,则发送电子邮件。 - 如果消息的跟踪级别为
Error
,则播放声音。 - 将消息写入数据库表或文件。
- 设置
EventArgs.Cancel = true
以阻止消息到达任何其他目的地。
XML 配置
TracerX 的许多配置属性都可以通过 XML 设置。静态类 Logger.Xml
包含以下方法,用于从 XML 加载配置设置。
bool Configure(string configFile) | 从指定的 XML 文件读取 TracerX 元素。 |
bool Configure() | 从应用程序的 .config 文件中读取 TracerX 元素。稍后将详细介绍! |
bool Configure(FileInfo configFile) | 从指定的 XML 文件读取 TracerX 元素。 |
bool Configure(Stream configStream) | 从指定的 Stream 对象读取 TracerX 元素。 |
bool ConfigureAndWatch(FileInfo configFile) | 从指定的 XML 文件中读取 TracerX 元素,并使用 FileSystemWatcher 监视该文件的更改。如果文件发生更改,它将被重新解析。 |
bool ConfigureFromXml(XmlElement element) | 从指定的 XmlElement 中提取配置设置。其他方法最终会调用此方法。 |
如果您使用 XML 配置功能,则应在打开 Logger.DefaultBinaryFile
或 Logger.DefaultTextFile
之前调用上述方法之一,因为许多设置都是用于配置这些文件的。您自己创建的 BinaryFile
或 TextFile
实例不受 XML 配置的影响。
TracerX 会在遇到 XML 解析问题(如无效元素)时记录事件。除非您希望此事件转到默认事件日志(应用程序事件日志)并使用默认源(“TracerX - <exe name>”),否则您应该在解析 XML 之前设置 EventLogging.EventLog
属性。因此,推荐的调用顺序是……
Logger.EventLogging.EventLog = new EventLog("Your Log", ".", "Your Source"); // Optional
Logger.Xml.Configure("YourTracerXConfig.xml");
Logger.DefaultBinayFile.Open();
上面的 XML 设置了可通过 XML 设置的所有 TracerX
属性,但也可以创建和配置其他日志记录器。也就是说,您不必包含所有显示的元素。
<?xml version="1.0" encoding="utf-8" ?>
<TracerX>
<BinaryFile>
<Directory value="%LOCALAPPDATA%" />
<Use_00 value="false" />
<AppendIfSmallerThanMb value="0" />
<MaxSizeMb value="10" />
<UseKbForSize value="false" />
<FullFilePolicy value="Wrap" />
<Archives value="3" />
<CircularStartSizeKb value="300" />
<CircularStartDelaySeconds value="60" />
<AddToListOfRecentlyCreatedFiles value="true" />
</BinaryFile>
<TextFile>
<Directory value="%LOCALAPPDATA%" />
<Use_00 value="false" />
<AppendIfSmallerThanMb value="0" />
<MaxSizeMb value="2" />
<UseKbForSize value="false" />
<Archives value="3" />
<FullFilePolicy value="Wrap" />
<CircularStartSizeKb value="300" />
<CircularStartDelaySeconds value="60" />
<FormatString
value="{time:HH:mm:ss.fff}
{level} {thname} {logger}+{method} {ind}{msg}" />
</TextFile>
<MaxInternalEventNumber value="200" />
<InternalEventOffset value="1000" />
<ConsoleFormatString
value="{time:HH:mm:ss.fff} {level} {thname} {logger}+{method} {ind}{msg}" />
<DebugFormatString
value="{time:HH:mm:ss.fff} {level} {thname} {logger}+{method} {ind}{msg}" />
<EventLogFormatString
value="{time:HH:mm:ss.fff} {thname} {logger}+{method} {msg}"/>
<Logger name="Root">
<BinaryFileTraceLevel value="info" />
<TextFileTraceLevel value="off" />
<EventHandlerTraceLevel value="off" />
<ConsoleTraceLevel value="off" />
<EventlogTraceLevel value="off" />
<DebugTraceLevel value="off" />
</Logger>
<Logger name="StandardData">
<BinaryFileTraceLevel value="verbose" />
</Logger>
</TracerX>
上面看到的 TracerX
元素可以放在自己的文件中,也可以包含在应用程序的 .config 文件中,如下所示。请注意 section
标签的存在,该标签引用了 TracerX
部分。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="TracerX" type="TracerX.XmlConfigSectionHandler, TracerX" />
</configSections>
<TracerX>
<LogFile>
<Directory value="%EXEDIR%" />
</LogFile>
</TracerX>
</configuration>
出于以下原因,我更倾向于将 TracerX
配置放在单独的 XML 文件中。
- 用户可以通过在配置 TracerX 时在.config 文件中引入语法错误,从而使应用程序无法启动。
- 一些用户无权写入Program Files目录,该目录通常是您的应用程序及其.config文件的位置。
- 您可能希望向用户发送一个新的 TracerX 配置文件,而不是要求他编辑它以启用更高的跟踪级别、更改文件大小等。如果它与 app.config 文件分开,这一点会更容易做到。
线程
日志记录器和查看器都支持多线程应用程序。日志记录器是线程安全的,查看器显示每个日志消息的线程名称和线程编号。按线程过滤(例如,查看单个线程的输出)的能力尤其有用。
线程名称
尽可能为您的线程赋予有意义的名称,包括主线程。这将帮助您理解查看器中的输出。给定的线程可以这样命名。
Logger.Thread = "Some Name";
上面的语句将“Some Name”赋值给当前线程。每个线程都可以通过这种方式为自己指定不同的名称。线程可以随时更改其名称。
如果线程名称尚未通过 Logger.Thread
设置,TracerX 将尝试使用 System.Threading.Thread.CurrentThread.Name
。如果这也未设置,查看器将生成“Thread 1”和“Thread 2”之类的名称。
请注意,线程对象的 System.Threading.Thread.CurrentThread.Name
属性是只能写入一次的,这意味着如果您尝试在它不是 null
时为其赋值,将会引发异常。因此,您可能希望在赋值前测试 Name
是否为 null
,或者坚持使用 Logger.Thread
,后者您可以随时更改。
线程 ID
查看器中的线程编号列**不是**线程的 ManagedThreadId
属性。我发现如果反复创建和销毁线程,CLR 会重复使用相同的 ID 号。也就是说,新创建的线程倾向于获得与最近终止的线程相同的 ManagedThreadId
。由于 ManagedThreadId
在进程生命周期内不能唯一标识一个线程,因此 TracerX 使用自己的线程编号,这只是一个计数器,每当它看到一个新线程时就会递增。TracerX 查看器允许您按线程名称或编号进行过滤。
对象渲染
TracerX 实现了 log4net 的自定义对象渲染概念,使用了 IObjectRenderer
、DefaultRenderer
和 RendererMap
。这些与 log4net 使用的接口/类名称相同。
大多数情况下,TracerX 只是调用对象的 ToString()
实现,将对象渲染为要记录的 string
。当这种行为对于给定类型不够时,您可以编写一个 IObjectRenderer
的实现并将其添加到 RendererMap
中,以使 TracerX 调用您的代码将指定类型(及其派生类型)的对象渲染为 string
。
例如,假设我们希望 TracerX 使用包含毫秒的特定格式说明符来渲染 DateTime
,例如“yyyy/MM/dd HH:mm:ss.fff”。首先,我们编写一个实现 IObjectRenderer
的类。只有一个方法需要实现。
using System;
using TracerX;
namespace Tester
{
class DateTimeRenderer : IObjectRenderer
{
public void RenderObject(object obj, System.IO.TextWriter writer)
{
DateTime dateTime = (DateTime)obj;
writer.Write(dateTime.ToString("yyyy/MM/dd HH:mm:ss.fff"));
}
}
}
在我们的某个地方的代码中,我们必须将 DateTimeRenderer
的实例添加到 RendererMap
中,如下所示。
RendererMap.Put(typeof(DateTime), new DateTimeRenderer());
现在,假设我们有一个名为 Log
的 Logger
,我们用它来记录当前时间,语句如下。
Log.Info("The current time is ", DateTime.Now);
传递给 Log.Info
的 DateTime
使用注册的渲染器进行渲染,结果如下。
The current time is 2007/11/23 14:56:06.765
请注意,以下语句与 string.Format()
具有相同的语义,因此会产生不同的结果。
Log.InfoFormat("The current time is {0}", DateTime.Now);
结果(如下)有所不同,因为上面的语句将 DateTime
对象直接传递给了 string.Format()
,而不是使用注册的渲染器进行渲染。
The current time is 11/23/2007 2:56:06 PM
默认渲染器
如果待渲染的对象不是 string
并且其类型(或基类型)不在 RendererMap
中,TracerX 会尝试使用预定义的 DefaultRenderer
。此类对数组、集合和 DictionaryEntry
对象有特殊处理。如果对象不是这些类型之一,它将简单地调用 ToString()
。
异常渲染
TracerX 会预先加载 RendererMap
,其中包含 Exception
类型的渲染器。此渲染器会记录所有嵌套的内部异常,并报告比 Exception.ToString()
更多的信息。您可以通过调用 RendererMap.Clear()
来从 RendererMap
中删除此渲染器。
TracerX vs. log4net
相似之处
- TracerX 像 log4net 一样使用日志记录器层次结构。
- TracerX 提供与 log4net 相同的跟踪级别,再加上一个(
Verbose
)。 - TracerX 中复制了 log4net 日志记录方法的签名(例如,
Log.Info()
、Log.Debug()
等)。 - TracerX 可以通过 XML 文件、应用程序配置文件、
XmlElement
对象或以编程方式进行配置。 - 输出可以定向到多个目的地。
- TracerX 可选择使用一组“滚动”文本文件。
RendererMap
集合、IObjectRenderer
接口和DefaultRenderer
对象在 TracerX 中的工作方式与在 log4net 中相同。
区别
- TracerX 没有用于添加多个或自定义 Appender 的通用框架。它不会将用户消息记录到数据库表或电子邮件,尽管您可以使用
Logger.MessageCreated
事件进行拦截并对其进行任何您想做的事情。 - TracerX 不支持远程日志记录,但您可以在计算机上运行 TracerX-Service 以启用远程查看。
- TracerX 没有类型转换器、数据格式化程序(
IObjectRenderer
除外)、插件或存储库层次结构。它的实现相对简单。
TracerX 的优势
- TracerX 有一个功能强大的查看器。
- 查看器允许您在日志生成*之后*(以及之前)决定您关心哪些线程、日志记录器和跟踪级别。
- TracerX 可以在单个文件中执行循环日志记录。您可以将初始日志记录(包含初始化/启动的输出)的一部分指定为保留,即使日志换行后也是如此。查看器按时间顺序显示输出。
- TracerX 的二进制文件比 log4net 的文本文件更紧凑。
- TracerX 支持加密文件。