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

ef,core,serilog · 浏览次数 : 79

小编点评

前言 Serilog 是一个 .NET 生态系统中的高性能日志库,提供了丰富的配置选项和自定义能力。在生产环境中,日志持久化对于故障排查和性能分析至关重要。虽然 EF Core 提供了数据库持久化的抽象层,但有时我们可能需要更细粒度的控制,或者希望避免对特定数据库的依赖。 本文将介绍如何在 .NET Core 项目中使用 Serilog 来实现日志持久化,并提供一些高级用法和配置示例。 一、Serilog 基本用法 1. 实体模型和上下文 使用 Serilog,我们可以定义一个简单的日志实体类 `YourLogRecord` 和一个上下文类 `YourApplicationDbContext`,用于存储日志数据。 ```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); }); } } ``` 2. 服务注册 在项目启动时,我们需要注册两个上下文:一个用于常规数据操作,另一个用于日志持久化。同时,我们还需要注册一个日志过滤器配置监视器服务。 ```csharp // 注册主上下文 services.AddDbContext(options => { options.UseSqlite("app.db"); }); // 注册日志上下文 services.AddDbContext(options => { // 抑制此上下文的命令执行相关日志生成以消除无限写入循环。 options.ConfigureWarnings(b => { b.Ignore(RelationalEventId.CommandExecuted, RelationalEventId.CommandError); }); options.UseSqlite("app.db"); }); // 注册日志过滤器配置监视器服务。 services.AddMinimumLevelOverridableSerilogFilterConfigurationMonitorManager(); ``` 3. 基础使用 在 Program.cs 中,我们配置 Serilog,使其读取配置并输出到不同的目标。 ```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")) .WriteTo.EntityFrameworkCore(serviceProvider.GetRequiredService(), new()) .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day); }); }, writeToProviders: true); ``` 二、高级用法 1. 自定义日志类型 有时候,我们需要为特定的日志类型编写自定义的sink。这时,我们可以使用 Serilog 的 `UseLogRecord` 方法,它会根据指定的类型创建一个新的日志上下文。 ```csharp modelBuilder.UseLogRecord(); ``` 2. 配置监视器 除了使用默认的日志级别过滤器外,我们还可以自定义过滤器。例如,我们可以忽略与 EF Core 相关的日志。 ```csharp options.ConfigureWarnings(b => { b.Ignore(RelationalEventId.CommandExecuted, RelationalEventId.CommandError); }); ``` 3. 日志格式化 我们可以自定义日志的输出格式,包括时间戳、日志级别、消息等。 ```csharp .logContext .WriteTo.Logger(l => l .FilterByExcluding(new MinimumLevelOverridableSerilogFilterConfigurationMonitor(serviceProvider, "SerilogFilterExtensions:EntityFrameworkCore")) .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}") ); ``` 结语 本文介绍了在 .NET Core 项目中使用 Serilog 进行日志持久化的基本用法和高级用法。通过合理地配置和使用 Serilog,我们可以轻松地实现日志的持久化,从而更好地支持生产环境的故障排查和性能分析。

正文

前言

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

新书宣传

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

正文

对代码感兴趣的朋友可以移步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"
      }
    }
  }
}

image

结语

为了实现对 .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
image

欢迎读者和广大朋友一起交流,如发现本书错误也欢迎通过博客园、QQ群等方式告知笔者。

本文地址:https://www.cnblogs.com/coredx/p/18298297.html

与基于EF Core存储的Serilog持久化服务相似的内容:

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

前言 Serilog是 .NET 上的一个原生结构化高性能日志库,这个库能实现一些比内置库更高度的定制。日志持久化是其中一个非常重要的功能,生产环境通常很难挂接调试器或者某些bug的触发条件很奇怪。为了在脱离调试环境的情况下尽可能保留更多线索来辅助解决生产问题,持久化的日志就显得很重要了。目前Ser

基于EF Core存储的国际化服务

前言 .NET 官方有一个用来管理国际化资源的扩展包Microsoft.Extensions.Localization,ASP.NET Core也用这个来实现国际化功能。但是这个包的翻译数据是使用resx资源文件来管理的,这就意味着无法动态管理。虽然官方有在文档中提供了一些第三方管理方案,但是都不太

EF Core + MySQL 基本增删改查

# 前言 基于EF Core + MySQL的基本增删改查,示例是基于[.NET6 + EF Core + MySQL 创建实体和数据库、EFCore 数据迁移](https://www.cnblogs.com/lym003/p/17411699.html)项目基础上的内容增加。同时也是对[基于Ca

使用EF 连接 数据库 SQLserver、MySql 实现 CodeFirst

1.新建项目,下载Nuget安装包 创建项目需要注意几点,如果是基于 .net framework 的项目 需要选择 相应版本的 EF, 如果是跨平台则选择EF Core版本。 我这里选择的是 .net framework 版本。红框里面是 实现EF Code First 需要的包。 对应的版本:

EF Core从TPH迁移到TPT

Intro EF Core支持多种方式处理具有继承关系的表,现在支持TPH、TPC(EF Core 7)、TPT,具体的实现方式可以参考官方文档和这篇文章。 大致总结一下不同的方式的区别: TPH:所有的类型都放在一张表中,使用discriminator字段用以区别不同的类型 TPT:不同的子类型有

造轮子之ORM集成

Dotnet的ORM千千万,还是喜欢用EF CORE 前面一些基础完成的差不多了,接下来可以集成数据库了,官方出品的ORM还是比较香。所以接下来就是来集成EF CORE。 安装包 首先我们需要安装一下EF CORE的NUGET包,有如下几个: Microsoft.EntityFrameworkCor

Ubuntu22.04 安装单机版kubernetes

# 前言 上期讲到要实现.net 6框架下的EF Core操作数据库基本增删改查,没有及时兑现。没有兑现的原因就是因为安装kubernetes。安装kubernetes的过程是灾难性的,也是十分顺利的。灾难性是因为在安装kubernetes过程中误操作,在/etc下执行了一个重置的命令导致我的工作站

外键拆分手记

我习惯性使用OData,它的$expand与层级查询非常好用,这个功能非常依赖于数据库的导航属性,也就是外键结构。最近想着把一个单体的系统拆分为多个小系统,首先需要处理外键依赖的问题。 多个服务各自有各自的数据库,数据库层面并不互通,也就无法使用外键约束。 我使用EF Core来描述数据库的结构,有

WTM的项目中EFCore如何适配人大金仓数据库

一、WTM是什么 WalkingTec.Mvvm框架(简称WTM)最早开发与2013年,基于Asp.net MVC3 和 最早的Entity Framework, 当初主要是为了解决公司内部开发效率低,代码风格不统一的问题。2017年9月,将代码移植到了.Net Core上,并进行了深度优化和重构,

基于 Three.js 的 3D 模型加载优化

作为一个3D的项目,从用户打开页面到最终模型的渲染加载的时间也会比普通的H5项目要更长一些,从而造成大量的用户流失。为了提升首屏加载的转化率,需要尽可能的降低loading的时间。这里就分享一些我们在模型加载优化方面的心得。