目录
介绍
TracerX记录器易于上手,但具有适用于.NET应用程序的高级功能。它可以将输出发送到多个目标(循环文件、事件日志等)。它可以生成文本和/或二进制文件。
它有一个强大的查看器,支持按线程、类别、跟踪级别等进行过滤和着色。查看器还可以折叠和展开每个方法调用的输出,显示绝对或相对时间戳,以及查看/遍历指向任何输出行的调用堆栈。根据我的经验,这些功能使诊断问题比使用纯文本编辑器容易得多。
本文介绍了如何使用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日志查看器的屏幕截图:
以下大多数功能都可以通过浏览菜单来发现,但通过双击访问的功能除外。不要忘记尝试行和列标题的上下文菜单!
- 您可以按线程名称、线程编号、记录器、跟踪级别、文本通配符和方法名称对日志进行筛选和/或着色。
- 消息文本根据堆栈深度缩进。
- 可以通过双击出现“+”和“-”的行来折叠和展开方法调用。
- 您可以使用面包屑栏和/或上下文菜单在调用堆栈中上下导航。
- 您可以单击面包屑栏中的箭头来查看(并跳转到)在给定级别调用的方法。
- 您可以查看绝对或相对时间戳。任何行都可以是“零时间”记录。
- 您可以通过双击带有黄色三角形的行来折叠和展开包含嵌入换行符的行。
- 您可以为单个行添加书签,也可以为以下所有行添加书签。
- 包含指定的搜索字符串。
- 来自一个或多个选定的线程、记录器或跟踪级别。
- 您可以查看指向所选行的调用堆栈(在窗口中),并导航到调用堆栈中的行。
- 您可以从同一线程跳转到下一个块,也可以从其他线程跳转到下一个块。
- 您可以选择行并将文本列或所有列复制到剪贴板。
- 您可以自定义列(显示/隐藏列,更改其顺序)。
- 长消息,无论是否嵌入换行符,都可以在可调整大小的文本窗口中查看。
“Hello World”与TracerX
这是一个非常简单的程序,用于生成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的一些基本概念和功能。
Loggers
TracerX使用log4net记录器层次结构的概念。所有日志记录都是通过 Logger 类的实例完成的。每个Logger名称都显示在查看器的“记录器”列中。每个Logger都应该表示应用程序中的一个日志记录类别,例如“报告”或“配置”。
您必须决定要创建多少个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。
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(包括您创建的)都是根Logger的后代。您可以在不调用GetLogger()的情况下通过Logger.Root完成所有日志记录,但这会使您无法在查看器中按Logger名称过滤输出,也无法在运行时控制代码的不同区域完成的日志记录量。 |
Logger.Current | 用线程引用的最后一个Logger。通常用于没有自己的记录器的调“通用”代码中,以便使用与调用代码相同的记录器。对于每个线程,它被初始化为Root。 |
Logger.StandardData | TracerX在内部使用Logger,用于在打开日志文件时记录有关环境、计算机和用户的信息。 |
跟踪级别
TracerX记录的每条消息都具有以下“跟踪级别”之一(从低到高)。这些是 enum类型TracerX.TraceLevel的值。
- Fatal
- Error
- Warn
- Info
- Debug
- Verbose
如何记录消息
假设您有一个名为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并连接起来,以形成显示在查看器的“文本”列中的消息。您应该避免自己调用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()等,这样做的原因是: 您计划对代码进行模糊处理(并且不希望在日志中使用经过模糊处理的方法名称)。
- 您希望在“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值。这些属性确定将哪些消息发送到哪些目标。
目的地 | Logger属性 | Root Logger中的初始值 | 其他记录器中的初始值 |
二进制文件 | 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将继承上表中所示的根跟踪级别,除非您显式覆盖它们,这通常不会这样做。大多数用户只是设置根级别,让所有其他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时,您正在配置和打开共享的二进制文件。您可以创建自己的BinaryFile实例,供单个Logger(或Logger组)使用。
同样的模式也适用于文本文件,只不过文本文件的类是TextFile,相应的Logger实例属性是TextFile,并且所有实例初始化时使用的相应静态属性是Logger.DefaultTextFile。
使用二进制文件的主要优点是您可以使用TracerX-Viewer.exe应用程序查看它,该应用程序具有强大的过滤、搜索、着色、格式和导航功能。二进制文件也往往比文本文件更紧凑(具体取决于您在文本文件的格式字符串中指定的字段)。二进制文件的缺点是您只能使用查看器查看它。为了两全其美,您可以选择生成这两个文件。然后,如果有查看器,则可以使用查看器,如果没有,则可以使用记事本。
最大文件大小
这两种文件类型都要求您指定最大文件大小,最大为4095 MB。通常,您将指定1到100 MB。当文件达到最大大小时会发生什么情况取决于文件的FullFilePolicy属性设置。它是具有以下值的枚举:
- Wrap
- Roll
- Close
循环日志记录
这两种文件类型都支持循环日志记录,当FullFilePolicy为Wrap时发生循环日志记录。在循环日志记录中,文件分为两部分。第一部分将被保留并且永远不会丢失(直到文件本身被删除)。第二部分是循环部分。当日志文件增长到最大指定大小时,TracerX会“包装”该文件。也就是说,它将写入位置设置回循环部分的开头,并开始用新输出替换旧输出。这可能会发生很多次,并且是完全自动的。
对于长时间运行的应用程序和程序,建议使用循环日志记录,这些应用程序和程序可能会在每次执行时生成不可预测的大量输出,例如服务。
查看器应用程序将自动在二进制文件的圆形部分中查找第一个按时间顺序排列的记录,并按时间顺序显示所有记录。对于文本文件,您必须手动执行此操作。
若要启用循环日志记录,必须将配置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 | 文件所在的目录。缺省值对应于System.Environment.SpecialFolder.LocalApplicationData。 设置此项时,将展开环境变量。还支持以下伪%variables%: %LOCAL_APPDATA%替换为System.Environment.SpecialFolder.LocalApplicationData。这与默认值相同,但可以使用它来指定子目录,例如%LOCAL_APPDATA%\Product\Logs。 %COMMON_APPDATA% 替换为System.Environment.SpecialFolder.CommonApplicationData。 %DESKTOP% 替换为System.Environment.SpecialFolder.Desktop。 %MY_DOCUMENTS% 替换为System.Environment.SpecialFolder.MyDocuments。 %EXEDIR% 将替换为可执行文件的目录,而不考虑当前目录。 |
Name | 文件的名称。默认值为进程或AppDomain的名称。对于文本文件,文件扩展名被强制为“.txt”,对于二进制文件,无论指定什么,都强制为“.tx1”,因此您不妨省略扩展名。 如果希望文件名以“_00”结尾,请设置Use_00为true。不要将“_00”放在此字符串的末尾,否则最终会得到类似“MyLog_00_01.tx1”的文件名。 |
AppendIfSmallerThanMb | 这决定了现有文件(如果存在)是存档(请参阅存档)还是以追加模式打开。如果现有文件小于指定的兆字节数(1 MB = 2^20字节),则以追加模式打开该文件。否则,它将被存档。因此,如果此属性为0(默认值),则文件始终在打开时存档,并启动新文件。 |
MaxSizeMb | 这指定了允许文件增长的量(以MB为单位)(当创建文件而不是追加文件时,它实际上指定了最大大小)。当文件增长到此数量时会发生什么情况取决于FullFilePolicy。如果启用了循环日志记录,它将换行(请参阅CircularStartSizeKb和CircularStartDelaySeconds)。如果在文件增长到此量时未启用循环日志记录,则文本文件将关闭、存档(请参阅存档),然后重新打开。二进制文件只是关闭的。 |
FullFilePolicy | 这决定了当文件增长到其允许的最大大小时会发生什么情况。 换行——如果循环日志记录已开始(基于CircularStartSizeKb和CircularStartDelaySeconds),则记录器将“换行”回循环部分的开头。如果循环日志记录尚未启动,则行为取决于它是文本文件(文件已“滚动”)还是二进制文件(文件已关闭)。 滚动——文件已关闭、存档(请参阅Archives),然后重新打开。 关闭 ——文件已关闭(游戏结束)。 |
Archives | 指定要保留的日志文件的存档(备份)数。该值必须在0 - 9999范围内。如果现有文件未在追加模式下打开,则通过重命名文件名后附加“_01”来对其进行存档。现有_01文件将重命名为_02,依此类推。将删除编号大于指定值的现有存档。如果指定的值为0,则在打开新文件时只需替换现有文件。 |
Use_00 | 如果bool是true,则“_00”将追加到Name属性指定的文件名中。这会导致文件在资源管理器中与其相关的“_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类还具有这些属性。
属性 | 描述 |
Password | 指定的密码将用作AES加密密钥来加密文件的内容,密码的单向哈希值将存储在文件头中。查看器应用程序将提示用户输入密码。哈希值必须匹配,查看器才会打开和解密文件。使用此功能时,性能会下降约60%。 |
PasswordHint | 如果Password和PasswordHint两者都不是null或空,则PasswordHint存储在文件头中,并在查看器提示输入密码以打开文件时显示给用户。 |
AddToListOfRecentlyCreatedFiles | 此布尔值确定文件是否将显示在查看器最近创建的文件列表中。 |
当二进制文件被打开时,有关当前执行环境的一些“标准数据”将通过名为StandardData的预定义Logger自动为您记录。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} | string | 传递给Logger.Info()、Logger.Debug()等的日志消息。 |
{line} | ulong | 行号/计数器。在应用程序启动时初始化(而不是在文件重新打开时),并针对写入文件的每一行递增。 |
{level} | string | TracerX.TraceLevel枚举值之一(例如Warning、Info等) |
{logger} | string | 用于记录消息的Logger对象的名称。 |
{thnum} | int | 分配给记录该消息的线程的编号。这个数字与Thread.ManagedThreadID或操作系统线程ID无关。当给定线程第一次调用它时,它只是一个由TracerX递增的计数器。 |
{thname} | string | 记录消息时调用线程的名称。 |
{time} | DateTime | 记录消息时系统本地时区的时间。 |
{method} | string | 记录消息的方法的名称。若要获得有意义的值,必须在代码中使用Logger.DebugCall()、Logger.InfoCall()等。 |
{ind} | string | 缩进(空白)。长度取决于调用方的堆栈深度。当您在代码中调用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的“输出”窗口中。有些实用工具可以在没有Visual Studio的情况下截获和显示此输出。
要启用此输出,请设置Logger对象的DebugTraceLevel属性以获取所需的详细级别(例如,TraceLevel.Verbose对于所有日志消息)。
此纯文本输出的格式由Logger.DebugLogging.FormatString确定,它使用与Logger.DefaultTextFile.FormatString相同的替换参数。
事件记录
日志记录可以发送到所选的事件日志,但与其他目标相比,可能应谨慎使用。例如,您可能应该将其限制为某些记录器和/或跟踪级别。
Logger.EventLogging 是用于配置此功能的静态类。它具有以下属性:
属性 | 描述 |
EventLog | 指定TracerX用于所有事件日志记录的事件日志。初始值设置为。请参阅该类的MSDN文档。不能在XML中设置此属性new EventLog("Application", ".", "TracerX - " + Assembly.GetEntryAssembly().GetName().Name)。请参阅MSDN文档中的EventLog类。此属性不能在XML中设置。应该在解析XML或打开日志文件之前以编程方式设置它,因为这些操作可以生成内部事件,这些事件也会转到该属性指定的事件日志中。 |
FormatString | 指定将哪些字段写入事件日志。有关替换参数,请参阅文本文件日志记录主题。 |
MaxInternalEventNumber | 这适用于TracerX的内部事件,而不是您的日志消息。它指定允许TracerX记录的最大内部事件编号。稍后会详细介绍。默认值=200。 |
InternalEventOffset | 在记录TracerX的内部事件编号之前添加到这些事件编号中的数字。使用此选项可防止TracerX记录的事件编号与应用程序的事件编号冲突。默认值=1000。 |
EventIdMap | Dictionary<TraceLevel, ushort>指定要用于每个TraceLevel值的事件ID号。这不能在XML中设置。 |
EventTypeMap | Dictionary<TraceLevel, EventLogEntryType>指定EventLogEntryType要用于每个TraceLevel值的内容。这不能在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 文件的一部分,如下所示。请注意引用该TracerX部分的section标记的存在。
<?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";
上面的语句将名称“某个名称”分配给当前线程。通过这种方式,每个线程都可以给自己起一个不同的名称。线程可以随时更改其名称。
如果线程的名称尚未通过Logger.Thread设置,TracerX将尝试使用System.Threading.Thread.CurrentThread.Name。如果尚未设置,查看器将生成“线程1”和“线程2”等名称。
请注意,线程对象的System.Threading.Thread.CurrentThread.Name属性是write-once,这意味着如果您尝试在它不是null时分配它,则会引发异常。因此,您可能希望在分配之前对其Name是null进行测试,或者坚持使用Logger.Thread可以随时更改的用途。
线程ID
查看器中看到的线程编号列不是线程的ManagedThreadId属性。我发现,如果反复创建和销毁线程,CLR会一遍又一遍地使用相同的ID号。也就是说,新创建的线程往往会获得与最近终止的线程相同的ManagedThreadId。由于ManagedThreadId在进程的生命周期内不唯一标识线程,因此TracerX使用自己的线程编号,该线程编号只是一个计数器,每次看到新线程时都会递增。TracerX查看器允许您按线程名称或编号进行过滤。
对象渲染
TracerX使用IObjectRenderer、DefaultRenderer和RendererMap实现了自定义对象渲染的log4net概念。这些接口/类名与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);
DateTime传递给Log.Info的渲染器与已注册的渲染器一起渲染,结果为:
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与log4net
相似之处
- TracerX使用记录器的层次结构,就像log4net一样。
- TracerX提供与log4net相同的跟踪级别,外加一个(Verbose)。
- log4net 日志记录方法(例如Log.Info()、Log.Debug()等)的签名在TracerX中是重复的。
- TracerX可以通过XML文件、应用程序配置文件、XmlElement对象或编程方式进行配置。
- 输出可以定向到多个目标。
- TracerX可以选择使用一组“滚动”文本文件。
- RendererMap集合、IObjectRenderer接口和DefaultRenderer对象在TracerX中的工作方式与在log4net中的工作方式相同。
差异
- TracerX没有用于添加多个或自定义appender的通用框架。它不会将用户消息记录到数据库表或电子邮件中,尽管您可以使用Logger.MessageCreated事件拦截它们并对它们执行任何您想要的事情。
- TracerX不支持远程日志记录,但您可以在计算机上运行TracerX-Service以启用远程查看。
- TracerX 没有类型转换器、数据格式化程序(除了IObjectRenderer)、插件或存储库层次结构。它的实现相对简单。
TracerX的优势
- TracerX有一个强大的查看器。
- 查看器允许您决定在生成日志后(以及之前)感兴趣的线程、记录器和跟踪级别。
- TracerX可以在单个文件中执行循环日志记录。您可以指定初始日志记录的一部分(包含初始化/启动的输出)即使在日志换行后也要保留。查看器按时间顺序显示输出。
- TracerX的二进制文件比log4net的文本文件更紧凑。
- TracerX支持加密文件。
本文最初发布于 GitHub - MarkLTX/TracerX
https://www.codeproject.com/Articles/23424/TracerX-Logger-and-Viewer-for-NET-2