数据库 Sinks(.net8)

张开发
2026/4/15 11:01:30 15 分钟阅读

分享文章

数据库 Sinks(.net8)
一、Serilog.Sinks.MySQL1.1 简单实现日志写入 MySQL 数据库在 .NET 生态中若要将 Serilog 日志写入 MySQL 数据库通常有以下两种主流方案使用 Serilog.Sinks.MariaDB这是目前社区最推荐的方案。因为 MariaDB 与 MySQL 高度兼容该包支持通过 MySQL 连接字符串写入 MySQL 数据库。使用 Serilog.Sinks.ADO.NET这是一个通用的 ADO.NET 接收器可以配置为连接 MySQL需要安装 MySql.Data 或 Pomelo.EntityFrameworkCore.MySql 驱动。下面是一个基于 Serilog.Sinks.MariaDB 的完整 .NET8 控制台应用程序示例这是目前最简单且维护最好的方式。1首先要创建数据库和用于存储日志信息的表CREATE DATABASE IF NOT EXISTS loggingdb;USE loggingdb;CREATE TABLE IF NOT EXISTS Logs (Id INT AUTO_INCREMENT PRIMARY KEY,Message TEXT NULL,MessageTemplate TEXT NULL,LogLevel VARCHAR(32) NULL,TimeStamp DATETIME NOT NULL,Exception TEXT NULL,Properties TEXT NULL);2创建一个 .NET 8 控制台应用并安装必要的 NuGet 包# 安装 Serilog 核心包dotnet add package Serilog# 安装 MariaDB Sink (它兼容 MySQL)dotnet add package Serilog.Sinks.MariaDB# 安装 MySQL 驱动 (Sink 依赖此驱动连接 MySQL)dotnet add package MySql.Data3代码实现Program.csusing Serilog;using Serilog.Sinks.MariaDB;using Serilog.Sinks.MariaDB.Extensions;using System;using System.Threading.Tasks;// 1. 配置 MySQL 连接字符串// 请替换为您的实际数据库信息var connectionString Serverlocalhost;Port3306;Databaseloggingdb;Uidroot;Pwd1234Zxcv;;// 关键排查步骤开启 SelfLog将内部错误输出到控制台Serilog.Debugging.SelfLog.Enable(msg Console.WriteLine($[Serilog Internal Error]: {msg}));// 2. 配置 SerilogLog.Logger new LoggerConfiguration().MinimumLevel.Information() // 设置最低日志级别.Enrich.FromLogContext() // enrich 日志上下文.WriteTo.Console() // 同时输出到控制台方便调试.WriteTo.MariaDB(connectionString: connectionString,tableName: logs,autoCreateTable: true, // 如果表已创建设为 false若想让代码自动建表设为 trueformatProvider: null,restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information).CreateLogger();try{Log.Information(应用程序启动开始测试 MySQL 日志写入...);// 模拟一些业务逻辑和日志记录int userId 1001;string userName Alice;Log.Information(用户 {UserId} ({UserName}) 已登录, userId, userName);// 模拟一个警告Log.Warning(用户 {UserId} 的操作响应时间超过阈值 ({Threshold}ms), userId, 200);// 模拟一个错误带异常try{throw new InvalidOperationException(模拟的数据库连接超时错误);}catch (Exception ex){Log.Error(ex, 处理用户 {UserId} 请求时发生严重错误, userId);}Log.Information(测试完成等待日志异步写入数据库...);// 稍微等待一下确保异步日志写入完成在生产环境中通常不需要手动等待程序退出时会刷新await Task.Delay(2000);}catch (Exception ex){Log.Fatal(ex, 应用程序意外终止);}finally{// 3. 关闭并刷新日志await Log.CloseAndFlushAsync();Console.WriteLine(日志已刷新到数据库按任意键退出...);Console.ReadKey();}注意autoCreateTable 参数如果设置为 trueSink 会在首次运行时尝试根据默认架构创建表。但在生产环境中建议手动执行 SQL 创建表如上文“前置准备”所示以便精确控制字段类型、索引和字符集推荐 utf8mb4。异步刷新CloseAndFlushAsyncSerilog 的数据库写入通常是异步批处理的。在控制台应用程序结束前必须调用 Log.CloseAndFlushAsync()否则最后几条日志可能还在内存缓冲区中未写入数据库程序就退出了。结构化日志注意代码中的 Log.Information(用户 {UserId} ({UserName}) 已登录, userId, userName);。Serilog 会将 UserId 和 UserName 作为单独的列存储在 Properties 字段的 JSON 中保存而不是简单的字符串拼接。这使得后续在数据库中查询特定用户的日志变得非常容易。4最后运行程序查看运行结果如下图为成功的结果数据库中的数据1.2 问题处理代码运行没有看到报错但是日志信息未写入1最常见原因autoCreateTable: false 但表不存在或结构不匹配若在代码中设置了 autoCreateTable: false。这意味着 Serilog 不会尝试创建表也不会检查表结构是否正确。如果表不存在或者表中的列名与 Serilog 预期的不一致例如列名大小写敏感、缺少Message列等写入操作会在内部静默失败或者抛出异常被吞掉导致没有数据。解决方法确认表是否存在登录 MySQL执行 SHOW TABLES; 查看是否有 Logs 表。验证表结构Serilog.Sinks.MariaDB 对列名有严格要求默认区分大小写取决于 MySQL 配置但通常建议完全匹配。2内部异常被吞没Silent FailureSerilog 的默认行为是“尽力而为”如果写入数据库失败它通常只会在内部记录一个 SelfLog 消息而不会抛出异常中断主程序。关键排查步骤开启 SelfLog。在 Main 函数的第一行配置 Logger 之前加入以下代码将内部错误输出到控制台// 在 new LoggerConfiguration() 之前添加Serilog.Debugging.SelfLog.Enable(msg Console.WriteLine($[Serilog Internal Error]: {msg}));重新运行程序观察控制台输出。如果看到类似Failed to emit a log event...或MySqlException: Table loggingdb.Logs doesnt exist的错误就能直接定位问题。如果看到Connection error...则是网络或账号密码问题。如下图表中字段名不匹配的错误提示回到顶部二、Serilog.Sinks.MSSqlServer2.1 简介Serilog.Sinks.MSSqlServer 是 Serilog 的一个官方支持的接收器Sink它允许将结构化日志直接写入 Microsoft SQL Server 数据库中的指定表。该 Sink 支持 .NET 6包括 .NET 8并提供丰富的配置选项如自定义列、批量写入、自动建表、使用连接字符串等。核心特点允许自动创建日志表若目标表不存在可自动创建默认结构。自定义列映射可以将日志属性如 Level、Message、Timestamp、Exception 等映射到数据库列。支持结构化日志字段通过 LogEvent 的 Properties 自动序列化为 JSON 或拆分为独立列。批量写入Batching提高性能减少数据库连接次数。支持异步写入避免阻塞主线程。兼容 Azure SQL Database 和本地 SQL Server。2.2 简单示例将日志信息写入本地的 MSSQL1添加必要的包dotnet add package Serilogdotnet add package Serilog.Sinks.Consoledotnet add package Microsoft.Extensions.Hostingdotnet add package Serilog.Extensions.Hostingdotnet add package Serilog.Sinks.MSSqlServer2修改 Program.csusing Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using Microsoft.Extensions.Logging;using Serilog;using Serilog.Context;using Serilog.Events;using Serilog.Sinks.MSSqlServer;using System.Data;var builder Host.CreateApplicationBuilder(args);// 配置 SerilogLog.Logger new LoggerConfiguration().MinimumLevel.Debug().MinimumLevel.Override(Microsoft, LogEventLevel.Information).Enrich.FromLogContext().Enrich.WithProperty(MachineName, Environment.MachineName) // 全部日志记录都带上参数MachineName// 注意.Enrich.WithProperty(...) 是一个一次性 enricher适用于静态值// 如果需要动态或更复杂的 enricher可以实现 ILogEventEnricher.WriteTo.Console() // 可选同时输出到控制台.WriteTo.MSSqlServer(connectionString: Serverlocalhost;DatabaseSerilogDemo;Trusted_ConnectionTrue;TrustServerCertificateTrue;,sinkOptions: new Serilog.Sinks.MSSqlServer.MSSqlServerSinkOptions{TableName Logs,AutoCreateSqlTable true // 自动建表首次运行时},columnOptions: new ColumnOptions{TimeStamp { ConvertToUtc true },AdditionalColumns new ListSqlColumn // 自定义列存储当前机器名{new SqlColumn{DataType SqlDbType.NVarChar,DataLength 255, // 对于字符串类型建议指定长度ColumnName MachineName}},}).CreateLogger();builder.Services.AddLogging(loggingBuilder {loggingBuilder.ClearProviders();loggingBuilder.AddSerilog(dispose: true);});var host builder.Build();// 示例日志var logger host.Services.GetRequiredServiceILoggerProgram();logger.LogInformation(Test {User}, new { Name Alice });logger.LogInformation(应用程序启动);using (LogContext.PushProperty(MachineName, Environment.MachineName)){logger.LogWarning(这是一条带自定义属性的日志);}try{throw new InvalidOperationException(测试异常日志);}catch (Exception ex){logger.LogError(ex, 发生了一个异常);}logger.LogInformation(应用程序结束);host.RunAsync().Wait();3在启动测试项目之前需要先手动创建数据库CREATE DATABASE SerilogDemo;最后启动项目日志信息就可以自动写入到数据库并且数据库的表 Log在程序首次启动时自动创建的。注意Log 表中的 Properties 字段默认输出为 XML博主尝试了更换 Serilog.Sinks.MSSqlServer 的版本和写入到比较新的 MSSQL2022 版本依然没打输出 json 格式有大佬知道原因的烦请留言。2.3 其他用法使用 Serilog.Sinks.MSSqlServer 将日志写入 Microsoft SQL Server 数据库除了基础的自动建表和默认字段写入外还支持多种高级用法可满足企业级日志管理、结构化分析、性能优化等需求。2.3.1 自定义数据表列排除标准列Excluding Standard Columns默认情况下Sink 会创建一系列标准列。如果你不需要某些列例如 MessageTemplate 或 Properties 的 XML/JSON 列可以将其从 Store 集合中移除以节省存储空间和提高写入性能。var columnOptions new ColumnOptions();columnOptions.Store.Remove(StandardColumn.MessageTemplate);columnOptions.Store.Remove(StandardColumn.Properties);// 甚至可以去掉 Id如果不需要自增主键// columnOptions.Store.Remove(StandardColumn.Id);添加自定义列Adding Custom Columns你可以将日志事件中的特定属性Properties提升到独立的数据库列中。这对于需要频繁查询、索引或聚合的字段如 UserId, OrderId, TenantId, RequestId非常有用避免了每次都去解析 JSON/XML 属性列。如下代码是基于上一章节的示例代码进行的优化新增了 UserName、RequestId 两列using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using Microsoft.Extensions.Logging;using Serilog;using Serilog.Context;using Serilog.Events;using Serilog.Sinks.MSSqlServer;using System.Data;var builder Host.CreateApplicationBuilder(args);// 配置 SerilogLog.Logger new LoggerConfiguration().MinimumLevel.Debug().MinimumLevel.Override(Microsoft, LogEventLevel.Information).Enrich.FromLogContext().Enrich.WithProperty(MachineName, Environment.MachineName) // 全部日志记录都带上参数MachineName// 注意.Enrich.WithProperty(...) 是一个一次性 enricher适用于静态值// 如果需要动态或更复杂的 enricher可以实现 ILogEventEnricher.WriteTo.Console() // 可选同时输出到控制台.WriteTo.MSSqlServer(connectionString: Serverlocalhost;DatabaseSerilogDemo;Trusted_ConnectionTrue;TrustServerCertificateTrue;,sinkOptions: new Serilog.Sinks.MSSqlServer.MSSqlServerSinkOptions{TableName Logs,AutoCreateSqlTable true // 自动建表首次运行时},columnOptions: new ColumnOptions{TimeStamp { ConvertToUtc true },AdditionalColumns new ListSqlColumn // 自定义列存储当前机器名{new SqlColumn{DataType SqlDbType.NVarChar,DataLength 255, // 对于字符串类型建议指定长度ColumnName MachineName},new SqlColumn{DataType SqlDbType.NVarChar,DataLength 100,ColumnName UserName, // 新增一个动态列用户名AllowNull true},new SqlColumn{DataType SqlDbType.NVarChar,DataLength 50,ColumnName RequestId, // 新增一个动态列请求 IDAllowNull true}},}).CreateLogger();builder.Services.AddLogging(loggingBuilder {loggingBuilder.ClearProviders();loggingBuilder.AddSerilog(dispose: true);});var host builder.Build();// 示例日志var logger host.Services.GetRequiredServiceILoggerProgram();logger.LogInformation(Test {User}, new { Name Alice });logger.LogInformation(应用程序启动);using (LogContext.PushProperty(MachineName, Environment.MachineName)){logger.LogWarning(这是一条带自定义属性的日志);}// 测试写入自定义列的内容string currentUserId User_Alice_88;string currentRequestId Guid.NewGuid().ToString().Substring(0, 8);using (LogContext.PushProperty(UserName, currentUserId))using (LogContext.PushProperty(RequestId, currentRequestId)){logger.LogInformation(2. 嵌套作用域当前用户 {UserName} 的操作请求ID {RequestId}, currentUserId, currentRequestId);// 即使不显式在消息模板中写 {UserName}只要 LogContext 里有且列名匹配就会存入数据库logger.LogWarning(3. 警告日志自动捕获上下文中的 UserName 和 RequestId);}try{throw new InvalidOperationException(测试异常日志);}catch (Exception ex){logger.LogError(ex, 发生了一个异常);}logger.LogInformation(应用程序结束);host.RunAsync().Wait();运行代码用户名和请求 ID 就会写入数据库如下图更改属性列的存储格式Properties Column Format默认的 Properties 列通常存储为 XML 或 NVARCHAR(MAX) 格式的 JSON。可以配置它存储为 SQL Server 的原生 JSON 类型如果数据库版本支持或者调整其长度和名称。columnOptions.Properties.ColumnName LogContext;columnOptions.Properties.DataType SqlDbType.NVarChar;columnOptions.Properties.DataLength 2048; // 限制长度防止过大// 注意若要利用 SQL Server 的 JSON 函数通常只需确保存储的是有效 JSON 字符串即可配置主键和索引Primary Keys and Indexes虽然 Sink 主要负责写入但你可以通过 ColumnOptions 指定哪一列作为主键默认是 Id并在建表时自动创建。对于高性能查询通常建议在数据库层面手动为常用的自定义列如 UserId 或 TimeStamp添加索引。2.3.2 性能优化Performance Optimization批量写入Batch Posting为了减少数据库连接开销和事务日志压力可以配置批量写入。设置 BatchPostingLimit每次批处理的日志数量和 Period触发批处理的时间间隔。var sinkOptions new MSSqlServerSinkOptions(){TableName Logs,BatchPostingLimit 100, // 每 100 条提交一次Period TimeSpan.FromSeconds(5) // 或每 5 秒提交一次};Log.Logger new LoggerConfiguration().WriteTo.MSSqlServer(connectionString: ...,sinkOptions: sinkOptions,columnOptions: columnOptions).CreateLogger();异步写入Asynchronous Logging虽然 MSSqlServer Sink 本身有一定的缓冲机制但在高并发场景下推荐结合 Serilog.Sinks.Async 包使用。它将日志写入操作封装在一个后台队列中由独立线程异步执行彻底避免日志 I/O 阻塞主业务线程。// 需要安装 Serilog.Sinks.AsyncLog.Logger new LoggerConfiguration().WriteTo.Async(a a.MSSqlServer(connectionString: ...,sinkOptions: sinkOptions,columnOptions: columnOptions)).CreateLogger();禁用自动建表Disable Auto Table Creation在生产环境中通常建议手动创建表并精确控制索引、分区和文件组而不是依赖 Sink 的自动建表功能。可以通过设置 AutoCreateSqlTable false 来禁用此功能。var sinkOptions new MSSqlServerSinkOptions(){AutoCreateSqlTable false};2.3.3 结构化与上下文增强 (Structuring Context)丰富日志上下文Enrichers结合 Serilog.Enrichers.Environment 或其他自定义 Enricher自动向每条日志添加机器名、进程ID、用户信息、请求ID等上下文数据。这些数据可以被映射到上述的“自定义列”中。// 自动添加 MachineName, UserName 等.Enrich.FromLogContext().Enrich.WithMachineName().Enrich.WithThreadId()结构化对象日志Serilog 的核心优势是结构化日志。在记录对象时如 Log.Information(User {User} logged in, userObj)对象会被序列化为 JSON 存入 Properties 列或拆分到自定义列。这使得在 SQL Server 中使用 OPENJSON 或 value() 方法进行复杂查询成为可能。2.3.4 安全与连接管理Security Connection使用托管标识或集成认证在 Azure 环境或域环境中可以使用 Windows 身份验证或 Managed Identity避免在连接字符串中硬编码密码。// 连接字符串示例 (Integrated Security)Server...;Database...;Integrated Securitytrue;自定义 SQL 客户端配置可以通过 SqlConnection 的高级设置如 Connect Timeout, Encrypt 等来增强连接的安全性和稳定性。2.3.5 故障转移与可靠性Reliability备用 SinkFallback Sink如果数据库不可用日志不应导致应用程序崩溃。可以配置一个备用 Sink如文件或控制台当 MSSQL Server 写入失败时自动切换。// 伪代码概念实际需使用 Fallback 包装器或自定义逻辑.WriteTo.MSSqlServer(...).WriteTo.File(logs/fallback-.txt) // 作为备份

更多文章