.NET Core的日志[4]:将日志写入EventLog

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

面向Windows的编程人员应该不会对Event Log感到陌生,以至于很多人提到日志,首先想到的就是EventLog。EventLog不仅仅记录了Windows系统自身针对各种事件的日志,我们的应用也可以利用提供的API将日志消息写到EventLog中。与EventLog相关的API都定义在System.Diagnostics.EventLog这个类型中,我们不仅仅可以利用它读取、写入和删除日志,还可以使用它来创建和删除Event Source。.NET Core的日志模型利用EventLogLogger实现了与EventLog的集成,不过EventLogLogger使用的是一个抽象化的EventLog。本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、抽象化的EventLog
二、EventLogLogger
三、EventLogLoggerProvider

一、抽象化的EventLog

EventLogLogger定义在“Microsoft.Extensions.Logging.EventLog”这个NuGet包中。就目前这个版本来说,该NuGet包承载的程序集只适用于.NET Framework应用,换句话说,EventLogLogger仅仅提供针对Windows EventLog的支持。尽管如此,日志模型仍然通过一个接口对EventLog的相关操作进行了抽象。

ConsoleLogger采用IConsole接口对针对不同平台的控制台进行了抽象,EventLogLogger使用的抽象化EventLog通过IEventLog接口来表示。如下面的代码片段所示,IEventLog接口仅仅定义了一个唯一的方法WriteEntry来写日志,提供的参数用于指定日志的消息文本(message)、类型(type)、事件ID(eventID)以及类别(category)。为了避免单条日志包含过多的内容,IEventLog接口定义了一个只读属性MaxMessageSize来设置日志消息的文本允许的最大长度。

   1: public interface IEventLog
   2: {
   3:     void WriteEntry(string message, EventLogEntryType type, int eventID, short category);
   4:     int MaxMessageSize { get; }
   5: }
   6:  
   7: public enum EventLogEntryType
   8: {
   9:     Error           = 1, 
  10:     Warning         = 2,
  11:     Information     = 4,
  12:     SuccessAudit    = 8 
  13:     FailureAudit    = 16
  14: }

EventLog记录下来的没条日志都具有一个通过枚举EventLogEntryType表示的类型,这个类型相当于日志的等级。对于定义在这个枚举中的五种类型,Error、Warning和Information与同名的日志等级具有相同的含义,而SuccessAudit和FailureAudit代表针对“审核(Audit)”事件的日志,它们分别代表针对“成功事件”和“失败事件”的审核。当EventLogLogger在利用EventLog写入日志的时候,会将指定的日志等级转化成EventLog的日志类型,转换规则很简单:针对Error、Warning和Information的日志等级转换成同名的EventLog日志类型,其他的等级则之间转换成Information类型。

具有如下定义的 WindowsEventLog是对IEventLog接口的默认实现者。一个WindowsEventLog实际上就是对一个EventLog对象的封装,后者通过DiagnosticsEventLog属性表示。这个封装的EventLog对象在构造函数通过指定相应参数的日志名称(logName)、机器名(machineName)和Event Source名称(sourceName)来创建。在实现的WriteEntry方法中,这个EventLog的WriteEntry被直接调用来完成日志的写入。WindowsEventLog的MaxMessageSize属性返回一个常量31839,日志消息文本不能超过这个长度。

   1: public class WindowsEventLog : IEventLog
   2: {
   3:     private const int MaximumMessageSize = 31839;
   4:  
   5:     public int MaxMessageSize
   6:     {
   7:         get { return MaximumMessageSize; }
   8:     }
   9:  
  10:     public System.Diagnostics.EventLog DiagnosticsEventLog { get; }
  11:  
  12:     public WindowsEventLog(string logName, string machineName, string sourceName)
  13:     {
  14:         DiagnosticsEventLog = new System.Diagnostics.EventLog(logName, machineName, sourceName);
  15:     }
  16:  
  17:     public void WriteEntry(string message, EventLogEntryType type, int eventID, short category)   
  18:     {
  19:         DiagnosticsEventLog.WriteEntry(message, type, eventID, category);
  20:     }
  21: }

二、EventLogLogger

日志模型利用EventLogLogger实现了与EventLog的整合。具体来说,一个EventLogLogger实际上是对EventLog对象的封装,它利用后者向EventLog写入日志。EventLogLogger类型具有如下的定义,我们可以看到它具有两个构造函数,除了提供Logger名称之外,其中一个构造函数具有一个类型为EventLogSettings的参数,这个封装的EventLog对象正式通过该参数提供的信息创建出来的。

   1: public class EventLogLogger : ILogger
   2: {
   3:     public EventLogLogger(string name);
   4:     public EventLogLogger(string name, EventLogSettings settings);
   5:  
   6:     public IDisposable BeginScope<TState>(TState state);  
   7:     public bool IsEnabled(LogLevel logLevel);
   8:     public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, 
   9:     Func<TState, Exception, string> formatter);
  10: }
  11:  
  12: public class EventLogSettings
  13: {
  14:     public IEventLog              EventLog { get; set; }
  15:  
  16:     public string                 LogName { get; set; }
  17:     public string                 MachineName { get; set; }
  18:     public string                 SourceName { get; set; } 
  19:  
  20:     public Func<string, LogLevel, bool>     Filter { get; set; }
  21: }

如上面的代码片段所示,我们可以利用EventLogSettings对象的EventLog属性向EventLogLogger提供一个具体的EventLog对象。如果这个属性为Null,EventLogLogger将会利用EventLogSettings的其他三个属性(LogName、MatcheName和SourceName)创建一个WindowsEventLog对象。除了这四个与创建或者提供EventLog对象相关的四个属性之外,EventLogSettings还具有另一个Func<string, LogLevel, bool>类型的属性Filter,它自然代表日志消息过滤器。

当EventLogLogger在利用EventLogSettings创建WindowsEventLog对象的时候,如果相关的属性(LogName、MatcheName和SourceName)在EventLogSettings对象中未作显式设置,它会采用预定义的默认值。具体来说这三者对应的默认值为“Application”[1]、“.”(代表本机)和“Application”。如果我们调用第一个构造函数重载,实际上内部会利用这些默认值创建一个WindowsEventLog对象。如果调用构造函数时连名称(name参数)都没有指定,那么类型名称(“EventLogLogger”)将被用来命名创建的Logger。

EventLogLogger和DebugLogger一样也不支持日志上下文范围,所以它的BeginScope<TState>方法和返回的对象其实毫无意义。EventLogSettings的Filter属性返回Func<string, LogLevel, bool>对象将被IsEnabled方法使用,如果个委托对象没有被显式提供,意味着这个方法总是返回True。当Log方法被调用的时候,它会采用与DebugLogger完全一致的方式来格式化最终的日志消息文本,所以针对异常的重复描述的问题依然存在。日志消息最终通过调用EventLog的WriteEntry方法被写到EventLog中,但是在这之前会检查格式化后的日志消息文本是否超过通过MaxMessageSize属性限制的长度,如果超过这个限制,日志消息将会被拆分并分多次写入EventLog。

三、EventLogLoggerProvider

EventLogLogger由对应的EventLogLoggerProvider来提供,下面的代码体现了这个类型完整的定义。我们可以调用如下所示的三个扩展方法AddEventLog来创建相应的EventLogLoggerProvider并将其注册到指定的LoggerFactory之上,我们可以通过这个方法指定用于提供或者辅助创建EventLog的EventLogSettings对象和最低日志等级。

   1: public class EventLogLoggerProvider : ILoggerProvider, IDisposable
   2: {
   3:     private readonly EventLogSettings _settings;
   4:     public EventLogLoggerProvider() : this(null)
   5:     {}
   6:  
   7:     public EventLogLoggerProvider(EventLogSettings settings)
   8:     {
   9:         _settings = settings;
  10:     }
  11:  
  12:     public ILogger CreateLogger(string name)
  13:     {
  14:         return new EventLogLogger(name, _settings ?? new EventLogSettings());
  15:     }
  16:  
  17:     public void Dispose()
  18:     {}
  19: }
  20:  
  21: public static class EventLoggerFactoryExtensions
  22: {
  23:     public static ILoggerFactory AddEventLog(this ILoggerFactory factory);
  24:     public static ILoggerFactory AddEventLog(this ILoggerFactory factory, EventLogSettings settings);
  25:     public static ILoggerFactory AddEventLog(this ILoggerFactory factory, LogLevel minLevel);
  26: }

我们同样通过一个简单的控制台应用来演示针对EventLog的日志记录。我们首选创建一个空的控制台应用,并在project.json中添加所需的依赖。由于针对EventLog的日志记录只适用于.NET Framework应用,所以我们仅仅为应用定义了一个针对.NET Framework 4.6.1(net461)的框架。

   1: {
   2:   ...
   3:   "dependencies": {
   4:     "Microsoft.Extensions.DependencyInjection"    : "1.0.0",
   5:     "Microsoft.Extensions.Logging"                : "1.0.0",
   6:     "Microsoft.Extensions.Logging.EventLog"       : "1.0.0"
   7:   },
   8:  
   9:    "frameworks": {
  10:     "net461": {
  11:       "frameworkAssemblies": {
  12:         "System.Runtime": {
  13:           "type": "build"
  14:         }
  15:       }
  16:     }
  17:   }
  18: }

我们编写了下面这段程序来完成针对EventLog的日志记录。如下面的代码片段所示,我们首先为即将写入的日志创建了一个名为“Demo”的Event Source(它一般代表日志被写入的应用或者服务的名称)。接下来我们采用依赖注入的方式创建了一个LoggerFactory对象,并调用扩展方法AddEventLog创建了一个EventLoggerProvider对象并将其注册到LoggerFactory上。我们在调用这个AddEventLog方法时指定了一个EventLogSettings对象,并将其SourceName属性设置为“Demo”。我们最终利用这个LoggerFactory对象创建出对应的Logger,并利用它写入了一条等级为Error的日志。

   1: public class Program
   2: {
   3:     public static void Main(string[] args)
   4:     {
   5:         if(!EventLog.SourceExists("Demo"))
   6:         {
   7:             EventLog.CreateEventSource("Demo", "Application");
   8:         }
   9:  
  10:         new ServiceCollection()
  11:             .AddLogging()
  12:             .BuildServiceProvider()
  13:             .GetService<ILoggerFactory>()
  14:             .AddEventLog(new EventLogSettings { SourceName = "Demo" })
  15:             .CreateLogger<Program>()
  16:             .LogError("数据库连接失败(数据库:{ Database},用户名:{ User})", "TestDb", "sa");
  17:     }
  18: }

由于上面这段程序涉及到Event Source查看和创建,所以需要在管理员权限下才能正常运行。程序运行后查看Event Viewer,我们将会看到被写入的这条日志消息。如图10所示,由于我们调用扩展方法AddEventLog时提供的EventLogSettings并没有显式指定EventLog名称,所以我们的日志默认会写入Application这个EventLog。

10


[1] Windows默认提供了三种EventLog,分别是Application、System和Security,应用产生的日志只能写到Application和System日志中,Security日志是只读的。除了这三种EventLog,我们还可以为应用创建独立的EventLog。

.NET Core的日志[1]:采用统一的模式记录日志
.NET Core的日志[2]:将日志写入控制台
.NET Core的日志[3]:将日志写入Debug窗口
.NET Core的日志[4]:利用EventLog写日志
.NET Core的日志[5]:利用TraceSource写日志

作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
存储 开发框架 JSON
ASP.NET Core OData 9 正式发布
【10月更文挑战第8天】Microsoft 在 2024 年 8 月 30 日宣布推出 ASP.NET Core OData 9,此版本与 .NET 8 的 OData 库保持一致,改进了数据编码以符合 OData 规范,并放弃了对旧版 .NET Framework 的支持,仅支持 .NET 8 及更高版本。新版本引入了更快的 JSON 编写器 `System.Text.UTF8JsonWriter`,优化了内存使用和序列化速度。
|
2月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
2月前
|
开发框架 .NET 中间件
ASP.NET Core Web 开发浅谈
本文介绍ASP.NET Core,一个轻量级、开源的跨平台框架,专为构建高性能Web应用设计。通过简单步骤,你将学会创建首个Web应用。文章还深入探讨了路由配置、依赖注入及安全性配置等常见问题,并提供了实用示例代码以助于理解与避免错误,帮助开发者更好地掌握ASP.NET Core的核心概念。
89 3
|
26天前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
2月前
|
开发框架 NoSQL .NET
利用分布式锁在ASP.NET Core中实现防抖
【9月更文挑战第5天】在 ASP.NET Core 中,可通过分布式锁实现防抖功能,仅处理连续相同请求中的首个请求,其余请求返回 204 No Content,直至锁释放。具体步骤包括:安装分布式锁库如 `StackExchange.Redis`;创建分布式锁服务接口及其实现;构建防抖中间件;并在 `Startup.cs` 中注册相关服务和中间件。这一机制有效避免了短时间内重复操作的问题。
|
3月前
|
开发框架 监控 .NET
开发者的革新利器:ASP.NET Core实战指南,构建未来Web应用的高效之道
【8月更文挑战第28天】本文探讨了如何利用ASP.NET Core构建高效、可扩展的Web应用。ASP.NET Core是一个开源、跨平台的框架,具有依赖注入、配置管理等特性。文章详细介绍了项目结构规划、依赖注入配置、中间件使用及性能优化方法,并讨论了安全性、可扩展性以及容器化的重要性。通过这些技术要点,开发者能够快速构建出符合现代Web应用需求的应用程序。
53 0
|
3月前
|
缓存 数据库连接 API
Entity Framework Core——.NET 领域的 ORM 利器,深度剖析其最佳实践之路
【8月更文挑战第28天】在软件开发领域,高效的数据访问与管理至关重要。Entity Framework Core(EF Core)作为一款强大的对象关系映射(ORM)工具,在 .NET 开发中扮演着重要角色。本文通过在线书店应用案例,展示了 EF Core 的核心特性和优势。我们定义了 `Book` 实体类及其属性,并通过 `BookStoreContext` 数据库上下文配置了数据库连接。EF Core 提供了简洁的 API,支持数据的查询、插入、更新和删除操作。
110 0
|
开发框架 前端开发 .NET
ASP.NET Core 核心特性学习笔记「下」
ASP.NET Core 核心特性学习笔记「下」
|
开发框架 前端开发 中间件
ASP.NET Core 核心特性学习笔记「上」
ASP.NET Core 核心特性学习笔记「上」
|
SQL 机器学习/深度学习 Cloud Native
.NET 云原生架构师训练营(模块二 基础巩固 EF Core 更新和迁移)--学习笔记
- 状态 - 自动变更检测 - 不查询删除和更新 - 并发
250 0
.NET 云原生架构师训练营(模块二 基础巩固 EF Core 更新和迁移)--学习笔记