基于EF Core存储的Serilog持久化服务

前言

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

新书宣传

有关新书的更多介绍欢迎查看《C#与.NET6 开发从入门到实践》上市,作者亲自来打广告了!

正文

对代码感兴趣的朋友可以移步Github。这里直接介绍一下基本用法。

这个库分为四个包:实体模型包定义基本实体类型;基本扩展包定义了模拟日志类别和严重性级别筛选的过滤器,方便为不同的输出目标自定义过滤器(内置的筛选器仅支持在全局使用,且会对所有输出目标生效,粒度不够细,只能自己写一个基于过滤器的扩展模拟相同的行为);配置扩展包定义了从IConfiguration读取并构建过滤器的辅助方法,支持配置的实时自动更新;EF Core服务包定义了基于EF Core的Serilog的Sink,Sink实现批处理接口,能避免频繁向数据库插入单条日志记录。方便为分离项目的解决方案按需引用,减少无关类型的污染。

以在ASP.NET Core中使用为例:

实体模型和上下文

csharp 复制代码
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日志就可以安全的写入了。

使用两个上下文的情况下可以在日志上下文中配置实体从迁移中排除,把日志表迁移托管给主上下文。

服务注册

csharp 复制代码
// 注册主上下文
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)

csharp 复制代码
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是配置扩展,可自动监控配置。配置应该类似以下结构:

json 复制代码
{
  "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

相关推荐
向宇it6 小时前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
数据的世界016 小时前
.NET开发人员学习书籍推荐
学习·.net
向宇it8 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
paixiaoxin8 小时前
CV-OCR经典论文解读|An Empirical Study of Scaling Law for OCR/OCR 缩放定律的实证研究
人工智能·深度学习·机器学习·生成对抗网络·计算机视觉·ocr·.net
坐井观老天12 小时前
在C#中使用资源保存图像和文本和其他数据并在运行时加载
开发语言·c#
pchmi15 小时前
C# OpenCV机器视觉:模板匹配
opencv·c#·机器视觉
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭16 小时前
C#都可以找哪些工作?
开发语言·c#
19004317 小时前
.NET重点
.net
m0_6632340117 小时前
在 .NET 5.0 运行 .NET 8.0 教程:使用 ASP.NET Core 创建 Web API
前端·asp.net·.net
boligongzhu18 小时前
Dalsa线阵CCD相机使用开发手册
c#