目录
介绍
JLogger6是一个作为.NET 6库组件的单一实例组件,可与支持.NET 6的任何.NET项目一起使用。
- NuGet组件可通过搜索NuGet或Jeff.Jones.JLogger直接转到 NuGet Gallery | Jeff.Jones.JLogger6 6.0.1 来查找。
- NuGet包的源代码可在 GitHub - MSBassSinger/JLogger6: .NET 6 version for JLogger 中找到。
- 本文中讨论的演示代码可在 GitHub - MSBassSinger/LoggingDemo6: Demo for JLogger 6 中找到。
JLogger具有以下特点:
- 多线程使用——作为单例,它可以从任何线程访问,并使用锁定技术来确保没有冲突。
- 高吞吐量——如果日志由多个线程同时使用,则日志写入不会停止调用线程。JLogger使用先进先出(FIFO)队列,其中日志写入放在队列中,并在后台同时写入到单独线程中的文件。该WriteDebugLog命令获取参数,创建日志数据,将其放入队列中。这些步骤都没有阻塞。
- 发送电子邮件——调试日志写入可以选择发送电子邮件(需要SMTP配置数据)
- 多种日志条目类型——有多种日志条目类型可供选择。它们中的每一个的含义取决于编写代码的用户。某些日志类型是为组件保留的,在处理日志条目时将被忽略。这些详述如下。
- 每天新建日志文件——午夜后,将创建一个新的日志文件,以便命名日志文件以显示日志处于活动状态的日期和时间跨度。
- 日志保留——日志将在指定的天数后自动删除,除非指定零,在这种情况下不会删除任何日志文件。
- 制表符分隔的日志文件——日志作为制表符分隔的文件写入。这样就可以在Excel等程序中打开文件进行分析。
- (可选)将日志写入数据库——当用户希望将日志写入数据库时,此选项可用于SQL Server。提供了用于创建DBLog表和用于插入日志记录和对记录执行日志保留的存储过程的脚本。
- (可选)将日志文件存储在Azure文件存储中——通过指定Azure存储,当日志关闭时,日志文件将复制到该位置并在本地删除。
日志记录变得简单
多年来,我尝试了几个日志记录包。其中一些非常好,大多数在某些情况下很有用。我无意贬低他们中的任何一个。您可能会发现其他软件包更适合您想要的。当我在演示应用中描述此NuGet包的用法时,你可能会发现你想要的简单性、多功能性、可伸缩性和性能。
设置记录器
记录器中的关键项之一是LOG_TYPE枚举。这提供了一种指定条目类型的日志条目的方法,以及从调用代码中选择是否创建日志条目的简单方法。这允许在运行时更改记录的内容和未记录的内容。这将在后面的部分中变得明显。
这些代码行用于说明JLogger的用法。变化比文档所能显示的要多,但这显示了JLogger的完全功能使用。
首先,引用库的using:
using Jeff.Jones.JLogger6;
using Jeff.Jones.JHelpers6;
下面是为要启用的调试日志选项设置类范围变量的示例。对于开发、QA、生产和故障排除生产,您设置的内容可能有所不同。
程序的此全局值通常存储在某个配置数据位置。
LOG_TYPE m_DebugLogOptions = LOG_TYPE.Error | LOG_TYPE.Informational |
LOG_TYPE.ShowTimeOnly | LOG_TYPE.Warning |
LOG_TYPE.HideThreadID |
LOG_TYPE.ShowModuleMethodAndLineNumber |
LOG_TYPE.System | LOG_TYPE.SendEmail;
下一步是在需要记录器之前尽早设置用于配置Logger的变量,通常在程序启动代码中。
Boolean response = false;
String filePath = CommonHelpers.CurDir + @"\";
String fileNamePrefix = "MyLog";
// This value applies to both debug files and to DB log entries.
Int32 daysToRetainLogs = 30;
// Setting the Logger data so it knows how to build a log file, and
// how long to keep them. The initial debug log options is set here,
// and can be changed programmatically at anytime in the
// Logger.Instance.DebugLogOptions property.
response = Logger.Instance.SetLogData
(filePath, fileNamePrefix, daysToRetainLogs, logOptions, "");
如果使用数据库存储日志,请使用此代码作为示例,而不是前面的代码。
注意:您可以使用“useDBLogging = false”进行设置,它将使用上述SetLogData方法中指定的文件。
这些行显示如何设置基于DB的日志记录。DBLog表的T-SQL脚本和两个存储过程必须在要在其中包含日志条目的数据库上执行。
如果使用Windows身份验证访问数据库,请确保Windows帐户在SQL Server上具有必要的权限,并且可以将DBUserName和DBPassword保留为“”。内部数据库连接根据SetDBConfiguration()传入的值构造正确的连接字符串。
Boolean response = false;
String serverInstanceName = "MyComputer.SQL2020";
String dbUserName = "";
String dbPassword = "";
Boolean useWindowsAuthentication = true;
Boolean useDBLogging = true;
String databaseName = "myData";
response = Logger.Instance.SetDBConfiguration(serverInstanceName,
dbUserName,
dbPassword,
useWindowsAuthentication,
useDBLogging,
databaseName);
提供了三个数据库脚本,这些脚本必须在目标SQL Server上运行。
- DBLog.sql——创建DBLog表和主键索引。
- spDebugLogDelete.sql——删除早于特定日期的记录。请参阅其使用的DataAccessLayer.ProcessLogRetention()方法。
- spDebugLogInsert.sql——插入日志记录。请参阅其使用的DataAccessLayer.WriteDBLog()方法。
如果记录到文件,并且你想要使用Azure文件存储,则需要添加此配置。出于性能原因,日志文件在打开时在本地使用(由于网络开销,一次通过网络写入一行到Azure文件会慢得多)。关闭日志后,日志文件将复制到指定的Azure文件存储。日志保留在Azure文件存储上运行,而不是在本地运行,因为本地日志文件在复制到Azure文件存储后会被删除。
// Optional configuration for Azure file storage
String resourceID = "<AZURE_CONNECTION_STRING>";
String fileShareName = "<AZURE_FILE_SHARE_NAME>";
String directoryName = "<AZURE_DIRECTORY_NAME>";
response = Logger.Instance.SetAzureConfiguration
(resourceID, fileShareName, directoryName, true);
无论日志的位置如何,其中一个选项是发送电子邮件。这是允许对指定它的日志条目使用电子邮件的配置。单独启用不会发送电子邮件。有关此内容的更多信息,请参阅下面的代码部分:
// Email setup.
// Note that the Debug Log Options must have the LOG_TYPES.SendEmail flag in order for a
// given log entry to send an email.
// If that flag is not in the log options bitset, then adding to the flags for a
// log entry will not send email. Both must be present in the log options and the
// log entry for the email to be sent.
Int32 smtpPort = 587; // Or whatever port your email server uses.
Boolean useSSL = true;
List<String\ sendToAddresses = new List<String>();
sendToAddresses.Add("MyBuddy@somewhere.net");
sendToAddresses.Add("John.Smith@anywhere.net");
response = Logger.Instance.SetEmailData("smtp.mymailserver.net",
"logonEmailAddress@work.net",
"logonEmailPassword",
smtpPort,
sendToAddresses,
"emailFromAddress@work.net",
"emailReplyToAddress@work.net",
useSSL);
// This is an example of how to use the LOG_TYPES.SendMail flag when writing to the log
// so that an email is sent.
if ((m_DebugLogOptions & LOG_TYPES.Error) == LOG_TYPES.Error)
{
Logger.Instance.WriteDebugLog(LOG_TYPES.Error & LOG_TYPES.SendEmail,
exUnhandled,
"Optional Specific message if desired");
}
如果不需要发送电子邮件,则使用相同的日志条目:
if ((m_DebugLogOptions & LOG_TYPES.Error) == LOG_TYPES.Error)
{
Logger.Instance.WriteDebugLog(LOG_TYPES.Error,
exUnhandled,
"Optional Specific message if desired");
}
配置只需执行一次。但是,为了与动态目标保持一致,可以在运行时将Logger.DebugLogOptions属性设置为所需的任何位集。例如,如果要在不重新启动系统的情况下增加日志记录,只需更新配置文件中的调试日志选项值以打开更多日志记录位,并让监视配置文件的任何进程更新Logger.DebugLogOptions属性。现在将记录更多内容,您可以将日志记录量减少到所需的正常水平。
代码中的日志记录示例
需要注意的一件事是调用日志记录方法之前的位集比较。如果未打开该位,则不会执行记录某些内容的代码。因此,除非使用更多日志条目,否则添加更多日志条目不会影响性能。因此,可以对性能和流程等内容进行编码,但除非打开,否则不会影响性能。此方法允许在代码中设计许多通用性以进行调试和分析,而不会影响性能。
// Example of use in a method
void SomeMethod()
{
// Use of the Flow LOG_TYPE shows in the log when a method was entered,
// and exited. Useful for debugging, QA, and development. The Flow bit
// mask is usually turned off in production to reduce log size.
if ((m_DebugLogOptions & LOG_TYPE.Flow) == LOG_EXCEPTION_TYPE.Flow)
{
Logger.Instance.WriteToDebugLog(LOG_TYPE.Flow, "1st line in method", "");
}
// This variable notes when the method started.
DateTime methodStart = DateTime.Now;
try
{
// Do some work here
// This is an example of logging used during
// process flow. The bitmask used here does not
// have to be "Informational", and may be turned
// off in production.
Logger.Instance.WriteToDebugLog(LOG_TYPE.Informational,
"Primary message",
"Optional detail message");
// Do some more work
}
catch (Exception exUnhandled)
{
// Capture some runtime data that may be useful in debugging.
exUnhandled.Data.Add("SomeName", "SomeValue");
if ((m_DebugLogOptions & LOG_TYPE.Error) == LOG_TYPE.Error)
{
Logger.Instance.WriteToDebugLog(LOG_TYPE.Error,
exUnhandled,
"Optional detail message");
}
}
finally
{
if ((m_DebugLogOptions & LOG_TYPE.Performance) == LOG_TYPE.Performance)
{
TimeSpan elapsedTime = DateTime.Now - methodStart;
Logger.Instance.WriteToDebugLog(LOG_TYPE.Performance,
String.Format("END;
elapsed time = [{0:mm} mins,
{0:ss} secs, {0:fff} msecs].", objElapsedTime));
}
// Capture the flow for exiting the method.
if ((m_DebugLogOptions & LOG_TYPE.Flow) == LOG_EXCEPTION_TYPE.Flow)
{
Logger.Instance.WriteToDebugLog(LOG_TYPE.Flow, "Exiting method", "");
}
}
} // END of method
日志记录代码的大部分使用都可以复制和粘贴,从而缩短开发时间。
ILogger选项
为了在使用该ILogger接口的.NET应用程序中使用JLogger6,只需获取对Logger.Instance对象接口ILogger的引用。
LogLevel日志类型的JLogger6转换:
- LogLevel.Critical添加到LOG_TYPE.Fatal调试日志选项
- LogLevel.Debug添加LOG_TYPE.System到调试日志选项
- LogLevel.Error添加LOG_TYPE.Error到调试日志选项
- LogLevel.Information添加LOG_TYPE.Informational到调试日志选项
- LogLevel.Warning添加LOG_TYPE.Warning到调试日志选项
- LogLevel.Trace添加LOG_TYPE.Flow到调试日志选项
这些是ILogger方法以及它们如何使用基础Logger实例。
void Log<TState>(LogLevel logLevel, EventId eventId,
TState state, Exception exception, Func<TState, Exception, string> formatter)
以以下方式写入日志:
WriteDebugLog(logType, exception, $"EventID = {eventId.ToString()};
State = {state.ToString()}");
bool IsEnabled(LogLevel logLevel)
检查调试日志选项位集以查看转换后的位是否已启用。
IDisposable BeginScope<TState>(TState state)
启动日志并返回对Logger.Instance对象的IDisposable引用。
让我们看一下日志
这是前几行的示例。当日志启动时,Logger会自动拍摄许多系统值的快照,这些值已被证明在以后的诊断和分析中很有用。
这些列是:
- 时间(或Date/Time,取决于LOG_TYPE标志,ShowTimeOnly)。这提供了低至毫秒的时间。通常,日志在午夜关闭并启动新日志,因此通常使用该ShowTimeOnly标志。
- 日志类型——条目的日志类型(与日志条目一起使用的LOG_TYPE值的名称)
- 消息——日志条目的主消息
- 添加信息——附加的,通常更详细的,解释Message的信息。
- 异常数据——异常数据集合的名称/值对。明智地使用此Exception类功能对于节省故障排除和诊断时间至关重要。
- 堆栈信息——来自异常实例的堆栈信息
- 模块——发生日志条目的模块的名称
- 方法——发生日志条目的方法的名称
- 行号——发生错误的行号
- 线程 ID——.NET线程ID(如果提供)
日志文件也可以直接在Excel(或任何解释制表符分隔列的电子表格)中打开。Excel允许比记事本等文本编辑器更复杂的搜索和分析。
如果无法写入日志文件或日志数据库表,则Logger会创建一个“紧急日志文件”,也以制表符分隔。它是直接编写的(而不是通过队列)并提供基本信息。
无论如何,Logger尝试确保写入日志条目。
演示应用概览
演示程序是一个.NET 6 Windows窗体应用程序。与Logger的正常实现不同,Logger实际上在“运行测试”按钮调用的代码中配置、运行和关闭。本演示的目的是展示如何在各种配置中使用Logger。
日志文件配置
Azure文件存储配置
数据库配置
可以从 GitHub - MSBassSinger/LoggingDemo6: Demo for JLogger 6 下载代码,以便您可以根据需要逐步执行并对其进行测试。
即将推出的功能
我正在开发下一个版本,以支持文件日志和数据库中的用户定义字段。该概念是允许在配置日志时定义和/或创建用户定义的字段,因此,随着时间的推移,它们可能会针对任何给定的应用程序而更改。
此外,我正在研究数据库日志存储的选项,以添加审核表,以便在DBLogAudit表中记录从DBLog表中删除的记录。对于必须保留日志记录和对日志记录执行的操作的用户,可能需要此选项。
结论
我想创建一个具有更简单、更一致的设置和使用记录器。我想提供广泛的日志类型,而不必返回并重新编码,因此打开和关闭位可以满足这一点。我希望记录器能够以高吞吐量的日志条目处理多个线程和任务,而不会影响性能。
依赖注入(DI)纯粹主义者可能反对使用单例。但是,DI是一个设计概念,旨在应用于在另一个对象中创建的、影响业务规则的对象。对于记录器,它不是在对象中创建的,它不会影响业务规则。因此,使用单例记录器并不违反DI的原始目的。正如对象可以通过构造函数、方法或属性注入依赖项一样,引入外部对象(Logger实例)与引入单一实例一样有效。在所有三种情况下,都会注入(推送或拉取)而不是创建对Logger实例的引用。
一些开发人员已经有了最喜欢的记录器。其他人只是用最少的工作拿最简单的东西。但是,如果您愿意查看JLogger6中是否有价值,并希望充分利用日志记录,我希望您能给这个记录器一个好机会。
背景
40多年来,我一直在多个操作系统和多种语言上开发软件。早在Windows之前。早在Linux之前。日志记录的几个一致性之一是需要日志记录以衡量性能。记录错误、警告和其他信息,所有这些都使解决生产、QA和开发应用程序问题的工作变得不那么痛苦。我编写了此组件,以便我可以在任何场景中使用日志记录,这些场景仅针对我需要的内容进行配置,而不会减慢应用程序执行速度。
使用演示代码
从GitHub存储库中提取演示项目。该代码注释良好,并演示了如何配置和使用JLogger6。该演示是在Visual Studio 2022中用C#编写的。演示代码面向.NET 6。JLogger6以.NET 6为目标。
兴趣点
我想创建一个易于使用、易于设置的日志记录组件,并且使用位比较,在不需要时会跳过对日志的方法调用。当我遇到数百到数千个任务/线程尝试写入日志文件的情况时,我决定使用排队方法来写入日志文件。它大大减慢了UI的速度。更改为该体系结构消除了该问题。
https://www.codeproject.com/Articles/5163318/JLogger6-When-You-Care-to-Write-the-Very-Best