.NET 8 WebAPI 集成 Serilog 与 SqlSugar 将日志保存到 SQL Server 完整教程

6 阅读5分钟

.NET 8 WebAPI 集成 Serilog 与 SqlSugar 将日志保存到 SQL Server 完整教程

本文档将指导你从零开始创建一个 .NET 8 WebAPI 项目,并集成 Serilog 日志框架,通过自定义 SqlSugar Sink 将日志持久化到本地 SQL Server 数据库。最终实现业务数据与日志数据统一使用 SqlSugar ORM 管理,同时保持日志写入的异步与非阻塞特性。


1. 项目目标

  • 创建一个名为 SerilogWebAPI 的 .NET 8 WebAPI 项目
  • 使用 Serilog 作为日志记录器
  • 通过自定义 SqlSugar Sink 将日志写入 SQL Server 数据库
  • 利用 SqlSugar 的 CodeFirst 功能自动创建日志表
  • 提供测试接口验证日志是否成功写入

2. 环境准备

  • .NET 8 SDK下载地址
  • SQL Server 数据库(本地开发可使用 Visual Studio 自带的 LocalDB,或安装 SQL Server Express)
  • 代码编辑器(推荐 Visual Studio 2022、VS Code 或 Rider)

3. 创建项目

打开终端(命令提示符、PowerShell 或 Visual Studio 的包管理器控制台),执行以下命令:

dotnet new webapi -n SerilogWebAPI
cd SerilogWebAPI

这将在当前目录下创建名为 SerilogWebAPI 的 WebAPI 项目,并进入项目文件夹。


4. 安装 NuGet 包

我们需要两个核心包:

  • Serilog.AspNetCore:Serilog 与 ASP.NET Core 的集成
  • SqlSugarCore:SqlSugar ORM,用于操作 SQL Server

在项目目录下运行:

dotnet add package Serilog.AspNetCore
dotnet add package SqlSugarCore

如果使用 Visual Studio,可以通过“管理 NuGet 程序包”搜索并安装这两个包。


5. 配置数据库连接字符串

打开 appsettings.json 文件,添加 ConnectionStrings 节点,指定本地 SQL Server 的连接字符串。这里以 SQL Server LocalDB 为例:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\mssqllocaldb;Database=SerilogWebAPIDb;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

说明:如果你使用的是独立 SQL Server 实例,请替换为对应的连接字符串,例如: "Server=localhost;Database=SerilogWebAPIDb;User Id=sa;Password=your_password;" 开发环境推荐使用 Windows 身份验证(Trusted_Connection=True)。


6. 创建日志实体类

在项目根目录下新建文件夹 Models,然后创建 SysLog.cs 文件,用于映射数据库中的日志表。

using SqlSugar;
using System;
​
namespace SerilogWebAPI.Models
{
    [SugarTable("SysLogs")]  // 指定数据库表名
    public class SysLog
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
​
        [SugarColumn(IsNullable = false)]
        public DateTime Timestamp { get; set; }
​
        [SugarColumn(Length = 50, IsNullable = false)]
        public string Level { get; set; }
​
        [SugarColumn(Length = int.MaxValue, IsNullable = true)]
        public string Message { get; set; }
​
        [SugarColumn(Length = int.MaxValue, IsNullable = true)]
        public string Exception { get; set; }
​
        [SugarColumn(Length = int.MaxValue, IsNullable = true)]
        public string Properties { get; set; } // 存储结构化日志的 JSON
    }
}
  • [SugarTable] 指定表名,不指定则使用类名。
  • [SugarColumn] 中的 IsPrimaryKeyIsIdentity 用于配置主键自增。
  • Properties 字段用于存储日志的附加属性(如请求路径、用户等),以 JSON 格式保存。

7. 创建自定义 Serilog Sink

自定义 Sink 需要实现 Serilog.Core.ILogEventSink 接口。在项目中新建文件夹 Sinks,添加类 SqlSugarSink.cs

using Serilog.Core;
using Serilog.Events;
using SqlSugar;
using System;
using System.Text.Json;
using System.Threading.Tasks;
using SerilogWebAPI.Models;
​
namespace SerilogWebAPI.Sinks
{
    public class SqlSugarSink : ILogEventSink
    {
        private readonly ISqlSugarClient _db;
        private readonly IFormatProvider _formatProvider;
​
        public SqlSugarSink(ISqlSugarClient db, IFormatProvider formatProvider = null)
        {
            _db = db;
            _formatProvider = formatProvider;
        }
​
        public void Emit(LogEvent logEvent)
        {
            // 将 LogEvent 转换为实体
            var logEntry = new SysLog
            {
                Timestamp = logEvent.Timestamp.DateTime,
                Level = logEvent.Level.ToString(),
                Message = logEvent.RenderMessage(_formatProvider),
                Exception = logEvent.Exception?.ToString(),
                Properties = JsonSerializer.Serialize(logEvent.Properties)
            };
​
            // 异步插入,避免阻塞主线程
            Task.Run(async () =>
            {
                try
                {
                    await _db.Insertable(logEntry).ExecuteCommandAsync();
                }
                catch (Exception ex)
                {
                    // 日志写入失败时,将错误输出到控制台(避免递归)
                    Console.WriteLine($"Failed to write log to database: {ex.Message}");
                }
            });
        }
    }
}
  • Emit 方法会在每条日志被记录时调用。
  • 我们使用 Task.Run 进行异步插入,确保日志写入不会影响业务请求的响应时间。
  • 异常被捕获并输出到控制台,防止因数据库问题导致应用程序崩溃。

8. 配置 Program.cs

修改 Program.cs,完成以下关键步骤:

  1. 读取连接字符串
  2. 创建 SqlSugar 客户端(单例)
  3. 自动创建日志表(CodeFirst)
  4. 配置 Serilog 全局日志,使用自定义 Sink
  5. 注册 SqlSugar 到 DI 容器
  6. 启用 Serilog

完整代码如下:

using Serilog;
using SqlSugar;
using SerilogWebAPI.Models;
using SerilogWebAPI.Sinks;
​
var builder = WebApplication.CreateBuilder(args);
​
// 1. 获取连接字符串
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
​
// 2. 创建 SqlSugar 客户端
ISqlSugarClient db = new SqlSugarClient(new ConnectionConfig
{
    ConnectionString = connectionString,
    DbType = DbType.SqlServer,
    IsAutoCloseConnection = true,
    // 可在此配置更多参数
});
​
// 3. 自动创建日志表(如果不存在)
db.CodeFirst.InitTables(typeof(SysLog));
​
// 4. 配置 Serilog
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()                     // 最低日志级别
    .WriteTo.Sink(new SqlSugarSink(db))             // 使用自定义 Sink
    .Enrich.FromLogContext()                         // 允许从日志上下文中添加属性
    .CreateLogger();
​
// 5. 使用 Serilog 作为日志提供程序
builder.Host.UseSerilog();
​
// 6. 注册 SqlSugar 到 DI 容器
builder.Services.AddSingleton<ISqlSugarClient>(db);
​
// 添加控制器和 Swagger 支持
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
​
var app = builder.Build();
​
// 确保应用停止时刷新 Serilog 日志
app.Lifetime.ApplicationStopped.Register(Log.CloseAndFlush);
​
// 配置 HTTP 请求管道
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
​
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
​
app.Run();

9. 创建测试 Controller

为了验证日志写入是否生效,在 Controllers 文件夹下创建 TestController.cs

using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using SerilogWebAPI.Models;
​
namespace SerilogWebAPI.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class TestController : ControllerBase
    {
        private readonly ILogger<TestController> _logger;
        private readonly ISqlSugarClient _db;
​
        public TestController(ILogger<TestController> logger, ISqlSugarClient db)
        {
            _logger = logger;
            _db = db;
        }
​
        [HttpGet("log-test")]
        public IActionResult LogTest()
        {
            _logger.LogTrace("Trace 日志 (不会被记录,级别太低)");
            _logger.LogDebug("Debug 日志");
            _logger.LogInformation("Information 日志,当前时间:{Now}", DateTime.Now);
            _logger.LogWarning("Warning 日志");
            _logger.LogError("Error 日志,错误详情:{Error}", "示例错误");
​
            // 模拟异常日志
            try
            {
                throw new InvalidOperationException("手动抛出的异常");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "捕获到异常");
            }
​
            return Ok("日志已写入,请检查数据库 SysLogs 表。");
        }
​
        [HttpGet("check-db")]
        public async Task<IActionResult> CheckDb()
        {
            var count = await _db.Queryable<SysLog>().CountAsync();
            return Ok($"当前日志表共有 {count} 条记录。");
        }
    }
}
  • LogTest 方法会记录不同级别的日志,并故意抛出一个异常进行捕获记录。
  • CheckDb 方法用于快速查看日志表中的记录总数,验证 SqlSugar 是否正常工作。

10. 运行并验证

启动项目

在终端中执行:

dotnet run

项目启动后,控制台会显示监听地址(如 https://localhost:5001),Swagger 页面也会自动打开(如果配置了 Swagger)。

测试日志写入

  1. 打开浏览器访问 Swagger UI(例如 https://localhost:5001/swagger)。
  2. 找到 Test 控制器的 GET /Test/log-test 接口,点击 Try it out 然后 Execute
  3. 看到响应返回 "日志已写入..." 后,说明接口调用成功。

检查数据库

  • 使用 SQL Server Management Studio (SSMS)Visual Studio 的 SQL Server 对象资源管理器,连接到 (localdb)\mssqllocaldb(或你的数据库实例)。
  • 找到数据库 SerilogWebAPIDb,展开表,应该能看到自动创建的 SysLogs 表。
  • 打开表查看数据,应该包含多条记录,包括 InformationWarningError 级别的日志,以及异常堆栈信息。Properties 字段会存储类似 {"Now":"2025-03-03T14:30:00.123Z"} 的 JSON 字符串。

也可以调用 /Test/check-db 接口查看日志总数,确认数据已写入。


11. 补充说明

日志级别

Program.cs 中,我们设置了 MinimumLevel.Information(),因此只有 Information 及以上级别(Warning、Error、Fatal)的日志会被记录。如需记录 DebugTrace,可改为:

.MinimumLevel.Debug()   // 或 .Verbose()

自动建表

db.CodeFirst.InitTables(typeof(SysLog)); 会在数据库不存在 SysLogs 表时自动创建,但不会更新已有表的结构。如果后续需要添加字段,可以手动修改表,或使用迁移工具。SqlSugar 也支持 SplitTable 等高级功能,可按需学习。

异常处理

自定义 Sink 中的 try-catch 确保了日志写入失败不会影响主业务流程,错误信息输出到控制台。生产环境中,可以考虑将此类错误记录到 Windows 事件日志或其他可靠位置。

丰富日志内容

Serilog 支持通过 Enrichers 添加更多上下文信息,例如:

.Enrich.WithMachineName()      // 添加机器名
.Enrich.WithThreadId()         // 添加线程 ID
.Enrich.WithEnvironmentName()   // 添加环境名

需要安装对应的 NuGet 包(如 Serilog.Enrichers.EnvironmentSerilog.Enrichers.Thread)并在配置中调用相应方法。

性能考虑

当前实现中,每条日志都触发一次异步数据库插入。对于高并发场景,建议使用批量写入或队列机制。SqlSugar 支持批量插入,可以通过修改 Sink 内部逻辑,积累一定数量的日志后批量写入,以减少数据库压力。


12. 结语

至此,你已经成功在 .NET 8 WebAPI 项目中集成了 Serilog,并使用自定义 SqlSugar Sink 将日志保存到 SQL Server。这种方法既保持了数据库访问层的统一,又允许你灵活控制日志表结构。你可以根据实际需求调整日志实体、添加更多 Enricher 或优化写入性能。

如果你有任何问题或改进建议,欢迎在评论区留言讨论。Happy coding!