基于EF Core存储的Serilog持久化服务
前言
Serilog是 .NET 上的一个原生结构化高性能日志库,这个库能实现一些比内置库更高度的定制。日志持久化是其中一个非常重要的功能,生产环境通常很难挂接调试器或者某些bug的触发条件很奇怪。为了在脱离调试环境的情况下尽可能保留更多线索来辅助解决生产问题,持久化的日志就显得很重要了。目前Serilog支持文件和部分数据库持久化,文件日志的查找分析比较麻烦,而使用数据库持久化则会导致特定数据库依赖。既然有EF Core这种专门负责抽象底层数据库的持久化框架,为何不直接使用呢。怀着这样的心情去Nuget找了一圈,结果一无所获,无奈又只能自己写一个。
新书宣传
有关新书的更多介绍欢迎查看《C#与.NET6 开发从入门到实践》上市,作者亲自来打广告了!

正文
对代码感兴趣的朋友可以移步Github。这里直接介绍一下基本用法。
这个库分为四个包:实体模型包定义基本实体类型;基本扩展包定义了模拟日志类别和严重性级别筛选的过滤器,方便为不同的输出目标自定义过滤器(内置的筛选器仅支持在全局使用,且会对所有输出目标生效,粒度不够细,只能自己写一个基于过滤器的扩展模拟相同的行为);配置扩展包定义了从IConfiguration读取并构建过滤器的辅助方法,支持配置的实时自动更新;EF Core服务包定义了基于EF Core的Serilog的Sink,Sink实现批处理接口,能避免频繁向数据库插入单条日志记录。方便为分离项目的解决方案按需引用,减少无关类型的污染。
以在ASP.NET Core中使用为例:
实体模型和上下文
public class YourLogRecord : LogRecord
{
    public int YourProperty { get; set; }
}
public class YourApplicationDbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 使用默认类型。
        modelBuilder.UseLogRecord(b =>
        {
            b.ToTable($"{nameof(LogRecord)}s");
        });
        // 使用自定义类型,需要继承LogRecord。
        modelBuilder.UseLogRecord<YourLogRecord>(b =>
        {
            b.ToTable($"{nameof(YourLogRecord)}s");
        });
    }
}
public class YourLogDbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.UseLogRecord(b =>
        {
            b.ToTable($"{nameof(LogRecord)}s", tb => tb.ExcludeFromMigrations());
        });
        modelBuilder.UseLogRecord<YourLogRecord>(b =>
        {
            b.ToTable($"{nameof(YourLogRecord)}s", tb => tb.ExcludeFromMigrations());
        });
    }
}
需要注意,一定要使用两个不同的上下文类型,其中一个专用于存储日志数据。因为EF Core本身也会产生日志,如果使用一个上下文,一般配置下一定会产生无限循环。EF Core产生日志,通过EF Core写入日志,写入日志会导致产生新的EF Core日志……读取日志可以使用日志上下文,这样的话日志实体只需要日志上下文配置即可。不过还是推荐在主要上下文同时注册日志模型,这样读取日志产生的EF Core日志就可以安全的写入了。
使用两个上下文的情况下可以在日志上下文中配置实体从迁移中排除,把日志表迁移托管给主上下文。
服务注册
// 注册主上下文
services.AddDbContext<YourApplicationDbContext>(options =>
{
    options.UseSqlite("app.db")
});
// 注册日志上下文
services.AddDbContext<YourLogDbContext>(options =>
{
    // 重要!
    // 抑制此上下文的命令执行相关日志生成以消除无限写入循环。
    options.ConfigureWarnings(b => b.Ignore(RelationalEventId.CommandExecuted, RelationalEventId.CommandError));
    options.UseSqlite("app.db")
});
// 注册日志过滤器配置监视器管理器服务。
services.AddMinimumLevelOverridableSerilogFilterConfigurationMonitorManager();
基础使用(Program.cs)
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseSerilog((hostBuilder, serviceProvider, configuration) =>
        {
            configuration
                .ReadFrom.Configuration(hostBuilder.Configuration)
                .ReadFrom.Services(serviceProvider)
                .WriteTo.Logger(internalConfiguration =>
                {
                    internalConfiguration
                        .Filter.ByIncludingOnly(
                            // 添加一个基于配置监视器的日志过滤器
                            new MinimumLevelOverridableSerilogFilterConfigurationMonitor(
                                serviceProvider,
                                // 配置路径
                                "SerilogFilterExtensions:EntityFrameworkCore"
                            ).Filter)
                        // 使用默认日志类型
                        .WriteTo.EntityFrameworkCore(
                            serviceProvider.GetRequiredService<IServiceScopeFactory>(),
                            // 日志上下文提取工厂,取决于上下文服务应该如何获取,例如使用上下文工厂服务或者直接获取
                            static sp => sp.GetRequiredService<YourLogDbContext>(),
                            // 日志的JSON序列化选项
                            new()
                            {
                                ReferenceHandler = ReferenceHandler.IgnoreCycles,
                                Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
                            });
                        // 使用自定义日志类型
                        .WriteTo.EntityFrameworkCore<YourLogDbContext, YourLogRecord>(
                            serviceProvider.GetRequiredService<IServiceScopeFactory>(),
                            static sp => sp.GetRequiredService<YourLogDbContext>(),
                            new()
                            {
                                ReferenceHandler = ReferenceHandler.IgnoreCycles,
                                Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
                            });
                });
        }, writeToProviders: true);
Serilog的内置日志级别筛选仅可用于全局,无法针对各个Sink独立配置,因此笔者只能自己实现一个相同效果的过滤器。其中CoreDX.Serilog.Extensions是过滤器本体,可手动基于代码构建,CoreDX.Serilog.Extensions.Configuration是配置扩展,可自动监控配置。配置应该类似以下结构:
{
  "SerilogFilterExtensions": {
    "EntityFrameworkCore": {
      "Default": "Warning",
      "Override": {
        "Microsoft.AspNetCore.DataProtection.KeyManagement": "Error",
        "Microsoft.AspNetCore.DataProtection.Repositories": "Error",
        "Microsoft.EntityFrameworkCore.Database.Command": "Error",
        "Microsoft.EntityFrameworkCore.Model.Validation": "Error"
      }
    }
  }
}

结语
为了实现对 .NETStantard 2.0 的兼容代码上使用了条件编译预处理实现一份代码一个项目同时编译到所有框架,最大程度共用代码简化代码管理。其中 .NET 6 以下使用Json.NET序列化,其他的使用System.Text.Json序列化。
许可证:MIT
代码仓库:CoreDX.Serilog.Sinks.EntityFrameworkCore - Github
Nuget:CoreDX.Serilog.Sinks.EntityFrameworkCore
Nuget:CoreDX.Serilog.Sinks.EntityFrameworkCore.Models
Nuget:CoreDX.Serilog.Extensions
Nuget:CoreDX.Serilog.Extensions.Configuration
QQ群
读者交流QQ群:540719365

欢迎读者和广大朋友一起交流,如发现本书错误也欢迎通过博客园、QQ群等方式告知笔者。
本文地址:https://www.cnblogs.com/coredx/p/18298297.html
基于EF Core存储的Serilog持久化服务的更多相关文章
- C# 嵌入dll  动软代码生成器基础使用  系统缓存全解析  .NET开发中的事务处理大比拼  C#之数据类型学习  【基于EF Core的Code First模式的DotNetCore快速开发框架】完成对DB First代码生成的支持  基于EF Core的Code First模式的DotNetCore快速开发框架  【懒人有道】在asp.net core中实现程序集注入
		C# 嵌入dll 在很多时候我们在生成C#exe文件时,如果在工程里调用了dll文件时,那么如果不加以处理的话在生成的exe文件运行时需要连同这个dll一起转移,相比于一个单独干净的exe,这种形 ... 
- 基于EF Core的Code First模式的DotNetCore快速开发框架
		前言 最近接了几个小单子,因为是小单子,项目规模都比较小,业务相对来说,也比较简单.所以在选择架构的时候,考虑到效率方面的因素,就采取了asp.net+entity framework中的code f ... 
- 【基于EF Core的Code First模式的DotNetCore快速开发框架】完成对DB First代码生成的支持
		前言 距离上一篇文章<基于EF Core的Code First模式的DotNetCore快速开发框架>已过去大半个年头,时光荏苒,岁月如梭...比较尴尬的是,在这大半个年头里,除了日常带娃 ... 
- .net Core 基于EF Core 实现数据库上下文
		在做项目时,需要将某一些功能的实体建立在另一个数据库中,连接不同的数据库用以存储记录.通过查找资料,实现EF Core上下文. 下面是实现上下文后的解决方案的目录: 1.UpAndDownDbCont ... 
- 基于ef core 2.0的数据库增删改审计系统
		1.首先是建审计存储表 CREATE TABLE [dbo].[Audit] ( [Id] [uniqueidentifier] NOT NULL, [EntityName] [nvarchar](1 ... 
- 使用Asp.Net Core MVC 开发项目实践[第四篇:基于EF Core的扩展2]
		上篇我们说到了基于EFCore的基础扩展,这篇我们讲解下基于实体结合拉姆达表达式的自定义更新以及删除数据. 先说下原理:其实通过实体以及拉姆达表达式生成SQL语句去执行 第一种更新扩展: 自定义更新字 ... 
- 使用Asp.Net Core MVC 开发项目实践[第三篇:基于EF Core的扩展]
		上篇我们说到了EFCore的基础使用,这篇我们将讲解下基于EFCore的扩展. 我们在Mango.Framework.EFCore类库项目中创建一个类名EFExtended的扩展类,并且引入相关的命名 ... 
- ASP.NET Core MVC+EF Core从开发到部署
		笔记本电脑装了双系统(Windows 10和Ubuntu16.04)快半年了,平时有时间就喜欢切换到Ubuntu系统下耍耍Linux,熟悉熟悉Linux命令.Shell脚本以及Linux下的各种应用的 ... 
- EF Core 数据过滤
		1 前言 本文致力于将一种动态数据过滤的方案描述出来(基于 EF Core 官方的数据筛选器),实现自动注册,多个条件过滤,单条件禁用(实际上是参考ABP的源码),并尽量让代码保持 EF Core 的 ... 
- 项目开发中的一些注意事项以及技巧总结  基于Repository模式设计项目架构—你可以参考的项目架构设计  Asp.Net Core中使用RSA加密   EF Core中的多对多映射如何实现?  asp.net core下的如何给网站做安全设置  获取服务端https证书  Js异常捕获
		项目开发中的一些注意事项以及技巧总结 1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ... 
随机推荐
- 促双碳|AIRIOT智慧能源管理解决方案
			随着"双碳"政策和落地的推进,各行业企业围绕实现碳达峰和碳中和为目标,逐步开展智能化能源管理工作,通过能源数据统计.分析.核算.监测.能耗设备管理.碳资产管理等多种手段,对能源 ... 
- IIS 部署 Python 环境
			1.安装IIS 勾选特殊CGI程序2.Python 环境 (环境变量配置)3.如果没有pip命令 先下载安装pip python setup.py install4.pip install wfast ... 
- 二叉树的遍历(BFS、DFS)
			二叉树的遍历(BFS.DFS) 本文分为以下部分: BFS(广度优先搜索) DFS(深度优先搜索) 先序遍历 中序遍历 后序遍历 总结 BFS(广度优先搜索) 广度优先搜索[^1](英语:Breadt ... 
- zoxide更新后 (cd)异常
			关于zoxide github地址:https://github.com/ajeetdsouza/zoxide 简单来说 zoxide是一个cd的强化版.它会记录你曾经cd过的目录,在你使用cd的时候 ... 
- 8.14考试总结(NOIP模拟39)[打地鼠·竞赛图·糖果·树]
			一举一动,都是承诺,会被另一个人看在眼里,记在心上的. T1 打地鼠 解题思路 数据范围比较小,不需要什么优化. 直接二维前缀和枚举右下角端点就好了. code #include<bits/st ... 
- 7.12考试总结(NOIP模拟12)[简单的区间·简单的玄学·简单的填数]
			即使想放弃,也没法放弃最想要的东西,这就是人 前言 这次应该是和 SDFZ 一起打的第一场比赛吧. 然而我还是 FW 一个... 这次考试也有不少遗憾,主要的问题是码力不足,不敢去直面正解,思考程度不 ... 
- react css-in-js
			CSS-in-JS是一种技术,而不是一个具体的库实现.简单来说CSS-in-JS就是将应用的CSS样式写在JavaScript文件里面,而不是独立为一些css,scss或less之类的文件,这样你就可 ... 
- Linux扩展篇-shell编程(五)-流程控制(三)-for语句
			基本语法: 格式一 for(( 初始值; 循环控制条件; 变量变化)) do statements done 格式二 for 变量 in 值1 值2 值3 ... do statements done ... 
- Linux扩展篇-shell编程(五)-流程控制(一)-if语句
			基本语法: (1)单分支 if [ condition ];then statement(s) fi 或 if [ condition ] then statement(s) fi (2)多分支 if ... 
- 解决TypeError: 'NoneType' object is not subscriptable
			1.捕获异常的方式try: img_list = img_list["name"]except: img_list = "" 2.对象进行判断if img_li ... 
