J4JLogger:提供源代码信息的Serilog包装器






4.90/5 (3投票s)
一个C#库,可自动将源代码信息(例如,调用方法)添加到Serilog事件中
引言
我喜欢Serilog并将其用于我的所有项目。但开箱即用,它没有能力记录日志事件的源代码信息(例如,源文件、调用方法、行号)。J4JLogger
就是一个可以做到这一点的Serilog包装器。
它还提供了一种通过SMS发送日志事件的方式(对于真正严重的问题很有用)。虽然目前只包含一个Twilio提供商,但为其他SMS库添加其他提供商也很简单。
最后,J4JLogger
提供了一个缓存日志记录器,可用作日志记录目标,直到“真正”的日志记录系统设置好为止(例如,在IHostBuilder
构造IHost
时)。缓存的条目可以在日志系统建立后倾倒到该系统中,从而提供在调试启动问题时可能很有用的信息。
您可以在J4JLogger的Github页面上找到更多文档。
背景
当我在编写——以及调试!——代码时,我通常喜欢能够直接转到引起问题的地点。由于我几乎总是包含日志记录,查看日志事件将是一个不错的起点……但是,开箱即用的Serilog不提供包含该信息的方式。
包含它的一个方法是编写一个 enriquecedor(丰富器),在Serilog的世界中,它允许你在日志记录过程中插入额外的信息。但真正的挑战是首先获取信息。在所有日志调用中指定方法调用名称、源代码文件名和行号会非常繁琐。而且由于在开发过程中名称和数字经常变化,它也容易出错。
有一个更好的方法:C#编译器允许你在任何方法调用中自动包含该信息,如果你在参数列表中包含某些参数,并用特定的属性装饰它们并分配特定的默认值。这是一个例子
public void Write(
LogEventLevel level,
string template,
[CallerMemberName] string memberName = "",
[CallerFilePath] string srcPath = "",
[CallerLineNumber] int srcLine = 0 );
当你调用这个方法时,关于方法被调用上下文的信息——调用成员的名称、源代码文件的路径以及调用的行号——都可以在Write()
方法中获得。这就是J4JLogger
包含在日志事件中的信息。
在某些情况下,这可能需要对Serilog调用各种方法的“Serilog方式”进行微小更改。问题出现在许多Serilog日志方法包含泛型参数,这些参数可能被编译器误认为是memberName
、srcPath
和/或srcLine
。
考虑以下方法,对其进行了增强以包含这些额外的上下文参数
public void Error<T0>(
string template,
T0 propertyValue,
[CallerMemberName] string memberName = "",
[CallerFilePath] string srcPath = "",
[CallerLineNumber] int srcLine = 0);
如果你要记录涉及字符串参数的内容
_logger.Error( "This is a Serilog template with a parameter '{0}'", "some text");
编译器将不知道“some text”是propertyValue
还是memberName
。
为了避免这种歧义,你必须像这样调用方法
_logger.Error<string>( "This is a Serilog template with a parameter '{0}'", "some text");
现在编译器知道“some text”是propertyValue
,正如你所希望的那样。
这个问题并不总是出现。通常在第一个参数是string
时会出现。
Using the Code
有许多方法可以设置J4JLogger
(请参阅Github页面了解详细信息)。这里有一个使用了Serilog的IConfiguration
的附加库
using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using J4JSoftware.Logging;
using Microsoft.Extensions.Configuration;
using Serilog;
namespace ConfigurationBasedExample
{
// shows how to use J4JLogger with a configuration file
// see the ConfigurationBasedExample project for other files and details
class Program
{
static void Main(string[] args)
{
var loggerConfig = new J4JLoggerConfiguration();
var configRoot = new ConfigurationBuilder()
.AddJsonFile( Path.Combine( Environment.CurrentDirectory,
"appConfig.json" ), true )
.Build();
loggerConfig.SerilogConfiguration
.ReadFrom
.Configuration( configRoot );
var logger = loggerConfig.CreateLogger();
logger.SetLoggedType(typeof(Program));
logger.Information("This is an Informational logging message");
logger.Fatal("This is a Fatal logging message");
}
}
}
你可以在Github网站上看到控制台输出的样子。日志文件看起来是这样的
2021-09-15 10:14:59.173 -07:00 [INF] This is an Informational logging message
ConfigurationBasedExample.Program::Main
(C:\Programming\J4JLogging\examples\ConfigurationBasedExample\Program.cs:32)
2021-09-15 10:14:59.238 -07:00 [FTL] This is a Fatal logging message
ConfigurationBasedExample.Program::Main
(C:\Programming\J4JLogging\examples\ConfigurationBasedExample\Program.cs:33)
附加功能
短信消息
偶尔,我发现我想要立即收到某些日志事件的通知(例如,如果我依赖的正在运行的服务出现问题)。为了满足这种用例,我添加了将日志事件作为SMS消息发送的功能。
要做到这一点,你必须向日志记录器添加一个Serilog接收器(sink),该接收器在Serilog和SMS服务之间进行接口。该库包含一个可以与Twilio配合使用的接收器。但你可以添加其他的,这并不难(请参阅Github文档了解详细信息)。
SMS功能是通过调整日志记录器的SmsHandling
属性来触发的。这可以通过设置它或通过以下便捷方法来完成
public void SendNextEventToSms() => SmsHandling = SmsHandling.SendNextMessage;
public void SendAllEventsToSms() => SmsHandling = SmsHandling.SendUntilReset;
public void StopSendingEventsToSms() => SmsHandling = SmsHandling.DoNotSend;
我通常会逐条消息地使用SMS功能(即SendNextMessage
),以免使我的SMS收件箱过载。SendNextMessage
会将下一个日志事件,并且仅下一个日志事件,发送到所有Serilog SMS接收器。
缓存日志记录器
我是Microsoft的IHostBuilder
/IHost
API的忠实粉丝。它允许你在启动时以开发人员定义的方式配置应用程序,然后将执行交给任何你声明为运行时代理的方法。它支持使用IConfigurationBuilder
/IConfiguration
API和依赖注入。在我的项目中,我经常使用它来设置J4JLogging
子系统。
考虑到大多数启动例程的复杂性,在初始化期间记录事件会很方便,以便研究问题。不幸的是,试图通过简单地使用J4JLogger
(或者任何其他日志记录器,我猜)来实现这一点会产生一个鸡生蛋还是蛋生鸡的问题:你无法记录到一个尚未配置和构建的日志记录器。
我绕过这个问题的方法是定义一个J4JLogger
的特殊版本,J4JCachedLogger
,它不包装Serilog日志记录器。事实上,它根本不发出任何日志事件。相反,它将它们存储在内存中,直到“真正”的日志记录器设置好。届时,你可以通过调用以下方法将所有缓存的日志事件倾倒到J4JLogger
中
public abstract bool OutputCache( J4JCachedLogger cachedLogger );
如J4JLogger
中所实现的,该方法按创建顺序将所有缓存的事件写入日志(在J4JCachedLogger
中,该实现什么都不做,因为事件已经在缓存中了)。
这并不是鸡生蛋问题的完整解决方案,因为如果初始化失败,在J4JLogger
实例设置好之前,就没有东西可以发出缓存的事件了。我正在努力为这种情况提供一种查看/输出这些缓存事件的替代方法。
普通事件
我有时需要在一个UI中显示日志消息,或者其中的一部分。为了简化这一点,我在IJ4JLogger
中添加了一个C#事件,你可以监听它
event EventHandler<NetEventArgs>? LogEvent;
NetEventArgs
是一个简单的类,它只是回显一个日志事件
public class NetEventArgs
{
public NetEventArgs( LogEventLevel level, string mesg )
{
Level = level;
LogMessage = mesg;
}
public LogEventLevel Level { get; }
public string LogMessage { get; }
}
请注意,除非你将NetEventSink
添加到你的Serilog
配置中,否则你不会收到任何事件。最简单的方法是使用提供的扩展方法(该方法在J4JLoggerConfiguration
的实例上调用,而不是在底层Serilog配置对象上调用)
public static LoggerConfiguration NetEvent(
this J4JLoggerConfiguration loggerConfig,
string outputTemplate = NetEventSink.DefaultTemplate,
LogEventLevel restrictedToMinimumLevel = LogEventLevel.Verbose
)
你可以提供自己的Serilog输出模板和要报告的最小事件级别。
历史
- 2021年10月1日:添加了关于通过C#事件监听日志事件的材料
- 2021年9月30日:添加了额外的细节和经验教训
- 2021年9月29日:在CodeProject上首次发布