C# 中的简单异步日志记录器






4.88/5 (22投票s)
Clearcove.Logging 是一个非常简单的日志记录库,旨在通过直接的许可条款满足大多数日志记录需求。
引言
我知道你在想什么——世界真的需要另一个日志记录库吗?
如果您正在寻找 .NET 中的日志记录库,您有很多选择。有 NLog、Log4Net、Enterprise Logging、Serilog 和 Common.Logging,仅举几个我想到的例子。找到由才华横溢的开发人员编写的日志记录库并不难,他们投入了大量时间和精力来创建健壮且功能丰富的软件。
这个问题还需要解决吗?
背景
几年前,我发现自己正在寻找一个日志记录库。我是一个商业桌面应用程序的作者,该应用程序通过互联网分发。因此,我有三个硬性要求:
- 日志条目应异步写入。我见过太多应用程序由于同步日志记录而遭受极端性能问题。
- 该库应尽可能小。我不想让用户为像日志记录这样简单的功能下载和加载一个 1 MB 的 DLL。越小越好。
- 我不想增加应用程序许可的复杂性。目前,我的客户必须同意我的许可条款。添加具有单独许可条款的第三方组件可能意味着需要额外的工作来评估我的产品。也许这只是偏执,但我希望保持简单。
我认为这些要求相当直接,但事实证明我找不到任何满足我需求的东西。特别是,我发现许多日志库的许可条款是不可接受的,因为我只是不想被迫分发“另一个”许可。
所以我写了自己的日志记录库——Clearcove.Logging
。它非常轻量级,只有 83 行代码。完整的实现也在一个 .cs 文件中,因此可以重复使用,而不必导入库。代码是用 VS 2017 编写的,但我已尝试编写与早期版本兼容的代码。日志记录库以 .NET 2.0 为目标,以吸引更广泛的受众。
我认为这种日志记录方法是适合的
- 没有复杂日志记录需求的应用程序
- 小型应用程序,如实用程序,可以受益于简化的部署
- 必须将许可复杂性降至最低的情况
那么,它是如何工作的?
Using the Code
首先,我考虑了我想要记录哪些信息。我想要一个简单的 API,可用于记录时间戳、日志记录器名称、线程 ID 和消息等信息。我非常熟悉 Log4Net API,并且大量借鉴了它。
要声明和使用日志记录器,您可以使用以下语法:
var log = new Logger(typeof(Program)); // Class level declaration.
log.Error(“My error message”, exception); // Logging from within a method.
log.Info(“My info message”);
如果您过去使用过其他日志记录库,那么这种语法应该很熟悉。
数据封装
接下来,我希望将我的日志条目表示为一个简单的对象。这样做的主要原因是我希望我的日志记录器能够引发日志记录事件。我有时会在创建单元和集成测试时使用这些事件,因为我觉得这很有帮助。这只是个人偏好。如果您对引发日志记录事件不感兴趣,那么代码可以简化。
日志记录事件被封装在 LogMessageInfo
对象中,该对象实现为
public sealed class LogMessageInfo
{
public readonly DateTime Timestamp;
public readonly string ThreadId;
public readonly string Level;
public readonly string Logger;
public readonly string Message;
}
写入日志条目
现在,我们来谈谈重点。上面讨论的代码的 API 实现和数据封装部分是冗长的但非常简单。然而,异步日志记录有点微妙。例如,如果发生导致应用程序关闭的异常,会发生什么?我们如何知道所有日志条目都会按照接收顺序写入?有几种方法可以解决这个问题。Clearcove.Logger
以一种简单但有些笨拙的方式解决了这个问题:
static void Main(string[] args)
{
var targetLogFile = new FileInfo("./MyApp.log");
Logger.Start(targetLogFile); // Loggers will complains if you skip initialization
try
{
Run(args);
}
finally
{
Logger.ShutDown(); // Removing this line may result in lost log entries.
}
}
这是一个 Clearcove.Logging
与 Log4Net 等其他实现不同的示例。我们必须告诉我们的日志记录器何时开始和停止记录。在尝试将任何日志条目写入日志文件之前,我们必须这样做。将 Logger.ShutDown()
调用放在 finally
语句中,应该能让我们的日志记录器有机会在应用程序关闭前将所有待处理的日志条目写入日志文件。当然,也会有一些日志条目无法写入的情况。例如,如果机器断电。如果您担心这些边缘情况,您可能需要考虑同步日志记录。
Clearcove.Logging
通过使用单个 System.Thread.Timer
实例来实现异步日志写入。线程计时器的周期未设置,因此计时器只会触发一次。在所有待处理的日志条目成功写入日志文件后,计时器将重置为在下一个间隔触发。这种行为类似于设置计时器的周期,但可以防止计时器在间隔延迟的情况下被触发多次。
最后,日志条目使用简单的 File.AppendAllText
调用写入文件。此调用可能不是向日志文件进行多次写入的最有效方法,但选择它是基于希望代码尽可能简单。
好了,这就是全部内容。一个非常简单的日志实现,完全能够满足大多数应用程序的日志记录需求。它对我很有效,解决了我的所有日志记录问题,同时将我的依赖项降至最低。
未来工作
拥有一个简单的日志记录器的好处之一是它易于理解,并且可以快速定制以满足您的需求。例如,滚动日志文件、同步日志记录、外部配置等。这些功能的实现留给读者作为练习。玩得开心!
此日志记录器实现的一个主要缺点是它仅限于 .NET。我计划很快发布此日志记录库的 Java 版本。
另外,请注意,一些 CodeProject 用户可能会在下方发布增强功能。我将尝试合并不增加复杂性的更改,但如果您发现此日志记录器不能完全满足您的需求,那么阅读下面的评论可能会有所帮助。
关注点
让我陷入这个困境的一个原因是希望简化软件许可。我努力寻找最好的方法来免费分发 Clearcove.Logger
而不增加许可复杂性。根据我的研究,我相信 Ms-PL 是最允许的许可证。它简单、易于阅读和理解,并且重要的是要求您的二进制分发“在符合此许可证的许可证下”发布。在我看来,这句话可以有多种解释,让您有很大的灵活性。我当然希望软件尽可能自由,同时仍然为您提供您应该关心的保护。如果您有更开放的许可证建议,请告诉我。
历史
- 0.9 - 我在生产环境中使用了一个变体。为了文章做了一些小的修改,但我认为它们不会引入新的错误。
- 0.91 - 删除了字符串插值,使代码更易于编译。文章的语法更改。
- 0.92 - 根据建议更新为使用 ISO-8601 日期格式。文章的次要语法更改。
- 0.93 - 修复了记录异常堆栈跟踪的问题。