目录
介绍
我喜欢Serilog,并在我所有的项目中使用它。但是开箱即用,它不具备记录已记录事件的源代码信息(例如,源文件、调用方法、行号)的能力。J4JLogger是一个Serilog包装器,它可以做到这一点。
它还提供了一种通过SMS发送日志事件的方法(对于真正严重的问题很有用)。虽然目前只包含一个Twilio提供程序,但为其他SMS库添加额外的提供程序很简单。
最后,在设置“真实”日志系统之前(例如,在IHostBuilder构建IHost过程中)J4JLogger提供适合用作日志目标的缓存记录器。缓存的条目一旦建立就可以转储到日志系统中,提供在调试启动问题时有用的信息。
您可以在J4JLogger的Github页面上找到更多文档。
背景
当我在写作和调试代码时,我通常喜欢能够直接转到导致问题的地方。由于我几乎总是包含日志记录,因此查看日志事件将是一个不错的起点……但是,开箱即用的Serilog不提供包含该信息的方法。
包含它的一种方法可能是编写一个丰富器,在Serilog世界中,它允许您将其他信息插入到日志记录过程中。但真正的挑战是首先获取信息。必须在所有日志记录调用中指定方法调用名称、源代码文件名和行号是非常乏味的。它也很容易被破解,因为名称和数字在开发过程中会发生很大变化。
有一个更好的方法:如果您在参数列表中包含某些参数,用特定属性修饰并分配特定默认值,C#编译器允许您自动将该信息包含在任何方法调用中。这是一个例子:
public void Write(
LogEventLevel level,
string template,
[CallerMemberName] string memberName = "",
[CallerFilePath] string srcPath = "",
[CallerLineNumber] int srcLine = 0 );
当您调用此方法时,有关调用方法的上下文的信息——调用成员的名称、源代码文件的路径和调用的行号——在Write()方法中是可用的。这就是J4JLogger日志事件中包含的信息。
在某些情况下,这可能需要稍微改变调用其各种方法的“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");
编译器不知道“某些文本”是propertyValue还是memberName。
为了避免这种歧义,您必须像这样调用该方法:
_logger.Error<string>( "This is a Serilog template with a parameter '{0}'", "some text");
现在编译器知道"some text"是propertyValue,正如你所期望的那样。
这个问题并不总是出现。它通常在第一个参数是string时发生。
使用代码
有多种方法可以设置J4JLogger(查看Github页面了解详细信息)。这是使用基于IConfiguration的Serilog附加库:
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接收器添加到连接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接收器。
缓存记录器
我是微软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输出模板和最低事件级别以在您这样做时报告。
https://www.codeproject.com/Articles/5314007/J4JLogger-A-Serilog-Wrapper-that-Provides-Source-C