在ASP.NET Core WebApi中使用日志系统(Serilog)

发布于:2025-06-25 ⋅ 阅读:(21) ⋅ 点赞:(0)

一.引言

日志是构建健壮 Web API 的重要组成部分,能够帮助我们追踪请求、诊断问题、记录关键事件。在 .Net 中,日志系统由内置的 Microsoft.Extensions.Logging 抽象提供统一接口,并支持多种第三方日志框架(如 Serilog、NLog 等)。

环境为.net8,配合第三方框架Serilog(因为微软只提供了统一的接口,和一些实现包,如将日志打印到控制台,但是不提供写日志到文件的内建实现,所以通常会引入第三方的流行库,例如Serilog,Serilog是基于微软的统一接口实现的,可以无缝接入)


二.环境配置

1环境搭建

创建.Net8 Asp.Net WebApi


我们先试试将日志打印到控制台的功能,主要是使用这两个包,前者是日志系统的基础包,后者是关于控制台的基础实现,在开发过程中我们会常用这个功能.框架已经内置这两个包了,我们无需引入.

2基本使用

我们在使用的时候直接使用注入的日志服务,然后调用打印日志,这里面有些没有打印,是引入默认配置的打印级别最低到Information,更低的级别不打印了,这个是可配置的.

3日志级别

级别 LogLevel 值 说明
Trace 0 最详细的日志,通常只在开发调试中启用。记录非常底层的信息(例如方法进入/退出)
Debug 1 调试信息,开发时常用,记录变量值、分支判断等辅助信息
Information 2 应用的正常流程事件,比如用户登录成功、请求开始/结束等
Warning 3 表示可能的问题,例如“找不到缓存,使用默认值”,但程序还能继续运行
Error 4 发生了错误,功能失败,例如数据库异常、请求失败等
Critical 5 致命错误,通常会导致应用崩溃或服务不可用,需要立刻处理

4框架的默认配置

创建了 WebApi的模版项目,我什么服务也没注册,直接就可以使用日志服务,这是引入框架默认已经将这个服务注入进去了.只是这段没有显示的出现.

builder.Services.AddLogging(loggingBuilder =>
{
    loggingBuilder.AddConsole();
});

5日志接口和各个提供者的关系

.NET 日志系统(Microsoft.Extensions.Logging)的一大亮点:通过抽象接口与具体实现(提供者)解耦

优雅的解耦设计可以让我们的控制器不关心具体的实现,想用哪个只需要在Program里面配置一下就行,我们的控制器代码只需要注入日志服务,打印日志就够了(甚至这些日志被输出到哪里都不关心,可以是控制台,文件,云等等).

前面的例子就是使用了内置的控制器提供者,但是因为我们要使用第三方的Serilog,所以我们要介绍一下Serilog.

三.Serilog

我们的重点是Serilog,所以前面的部分有一个概念上的理解就可以.

我们要使用Serilog接管原生的日志系统.在此之前我们需要对其有个基本的了解.

1.控制台牛刀小试

$ dotnet add package Serilog
$ dotnet add package Serilog.Sinks.Console

创建一个控制台,在控制台中安装这两个包.

using Serilog;
var log = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();

使用LoggerConfiguration可以创建一个日志记录者,使用WriteTo.Console()意味着我的日志将会被记录到控制台上,显然这里可以继续配置记录到文件,云等等

log.Information("Hello, Serilog!");

使用这个对象就可以记录日志了.

Log.Logger = log;
Log.Information("The global logger has been configured");

可以将这个对象交给全局的静态引用,以后就可以很方便的使用这个静态引用.这不是必须的,必要的时候你可以随时使用LoggerConfiguration来构建一个新的日志对象,使用这个新对象来记录日志!


 2.Sinks

如何加一个日志接收者(可以称呼为Sinks)呢?

$ dotnet add package Serilog
$ dotnet add package Serilog.Sinks.Console
$ dotnet add package Serilog.Sinks.File

在引入这个Sinks的实现包,比如想记录到文件,就再引入第三个包.一个接受者对应一个Nuget包,

https://github.com/serilog/serilog/wiki/Provided-Sinks,所有实现都在,应该能满足多数项目了!

using System;
using Serilog;

class Program
{
    static async Task Main()
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Debug()
            .WriteTo.Console()
            .WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day)
            .CreateLogger();

        Log.Information("Hello, world!");

        int a = 10, b = 0;
        try
        {
            Log.Debug("Dividing {A} by {B}", a, b);
            Console.WriteLine(a / b);
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Something went wrong");
        }
        finally
        {
            await Log.CloseAndFlushAsync();
        }
    }
}
WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day)

只需要配置要记录到的文件路径日志滚动(日志文件自动按时间拆分)策略.

RollingInterval.Day:表示 每天生成一个新的日志文件

枚举值 含义
RollingInterval.Infinite 永远写一个文件(不滚动)
RollingInterval.Year 每年一个文件
RollingInterval.Month 每月一个文件
RollingInterval.Day ✅ 每天一个文件
RollingInterval.Hour 每小时一个文件
RollingInterval.Minute 每分钟一个文件(较少用)

 Log.CloseAndFlushAsync();在程序的最后需要调用一下释放资源,比如可能占用着文件句柄.

最后你可能注意到,记录日志的时候只需要按级别记录日志就行了,根本不关心日志被记录到了哪里!

3.日志级别

和刚才提到的日志级别类似,但是Serilog和原生的还是有区别的.

Serilog 的日志级别(从低到高):

日志等级 说明 场景 日志量
Verbose 最详细,几乎所有日志信息都会输出 极限调试、诊断底层问题 🌊 最大
Debug 仅内部调试信息,用于了解系统内部状态变化 调试开发时使用 📘 很多
Information 系统的正常行为,业务操作的主要流程日志 用户登录、订单完成等 📗 常用
Warning 警告,有潜在问题或轻微异常,但系统还能正常运行 配置缺失、请求重试、服务降级等 ⚠️ 警觉性
Error 真正发生错误,功能失败但系统仍运行 异常捕获、数据库连接失败等 ❌ 严重
Fatal 致命错误,系统必须停止/立即处理的重大问题 启动失败、内存泄露导致崩溃等 ☠️ 最高

.MinimumLevel.Debug(),配置 Serilog 的日志“最低输出等级”, Debug 及以上的所有日志 都会被输出,而比它低的等级 Verbose 不会被输出。

 如果没有配置MinimumLevel ,那么会使用默认级别Information 

不仅如此,还可以进行更细致的控制!

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()  // Logger整体是Debug及以上才会“生成日志事件”
    .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Information)
    .WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day,
                  restrictedToMinimumLevel: LogEventLevel.Debug)
    .CreateLogger();

关注restrictedToMinimumLevel参数,使用该参数可以让Sinks进一步的筛选日志等级.先配置最小等级为Debug,但是我可以单独配置控制台只接收到更高的Information,要注意的是只能配置高于最小等级的级别,就比如我最小级别是Debug,但是不存在配置Verbose级别而让控制台能接收Verbose.

我们在依赖注入使用ILogger来记录日志,所以记录方面使用原生的日志级别,但是Serilog配置代码要使用Serilog级别,Serilog会自动映射到原生日志级别.

4.WebApi中使用

dotnet add package Serilog.AspNetCore

在WebApi项目中一般只需要引入这个包就够了

 public static void Main(string[] args)
 {
     var builder = WebApplication.CreateBuilder(args);

     // Add services to the container.

     builder.Services.AddControllers();
     // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
     builder.Services.AddEndpointsApiExplorer();
     builder.Services.AddSwaggerGen();

     
     Log.Logger = new LoggerConfiguration()
         .MinimumLevel.Information()                                 
         .WriteTo.Console()
         .WriteTo.File(
             "Logs/log-.txt",
             rollingInterval: RollingInterval.Day)      
    
         .CreateLogger();

     builder.Host.UseSerilog(dispose: true);

 先配置记录对象交给静态引用,然后使用

 builder.Host.UseSerilog(dispose: true);

    public static IHostBuilder UseSerilog(
        this IHostBuilder builder,
        ILogger? logger = null,
        bool dispose = false,
        LoggerProviderCollection? providers = null)
    {
        if (builder == null) throw new ArgumentNullException(nameof(builder));

        builder.ConfigureServices((_, collection) =>
        {
            collection.AddSerilog(logger, dispose, providers);
        });

        return builder;
    }
参数名 类型 说明
builder IHostBuilder 主机构建器,ASP.NET Core 启动的入口点。
logger Serilog.ILogger? 可选的 Serilog 日志实例,如果不传,将使用静态类 Serilog.Log
dispose bool 如果为 true,当应用关闭时,自动调用 Dispose()(或 Log.CloseAndFlush())释放资源。
providers LoggerProviderCollection? 用于桥接其他 ILoggerProvider(比如 NLog、Console 等),使得其他提供者也能接收到日志(通过 WriteTo.Providers() 使用)。

如果你看过官方文档会发现文档的示例一般都是使用 AddSerilog(...) 方法完成真正的注册工作。

但我推荐使用UseSerilog方法,但其实内部也是使用了AddSerilog方法.


using Serilog;
using Serilog.Events;
using Serilog.Formatting.Compact;

namespace Serilog_Study
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.

            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();
            
            
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Information()                                              
                .WriteTo.Console()
                .WriteTo.File(
                    "Logs/log-.txt",
                    rollingInterval: RollingInterval.Day)               
                .CreateLogger();

            builder.Host.UseSerilog(dispose: true);

            var app = builder.Build();
         
            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();

            app.UseSerilogRequestLogging();
            app.MapControllers();

            app.Run();
        }
    }
}

现在已经可以和刚才一样,通过依赖注入的形式,通过构造器注入ILogger服务.

 你可以得到这样的日志,同时还会保留到文本文件.

5.请求记录

现代日志系统通常会记录每一次HTTP请求.

原因 说明
🔍 排查错误 出现异常或 Bug 时,需要知道调用了哪个接口、用了什么方法、返回了什么状态码
📈 性能分析 可以看到每个请求的耗时(比如哪个接口慢)
📦 审计记录 某些系统(如金融、医疗)需要记录访问历史
🧩 统一结构化 每条请求日志都含有标准字段(方法、路径、耗时、状态码),方便日志平台分析

Serilog提供了中间件来帮助实现该功能.

只需要加上下面一行就能使用该中间件,但是在此之前 

.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning)

我们要 配置这三行,因为ASP.NET Core 框架本身(如 MVC、Routing、Kestrel)会产生日志,且十分啰嗦,一次请求会将很多细节被展示出来(比如中间件执行、路由匹配、控制器执行、模型绑定、请求开始/结束……),日志碎片非常多,一个请求可能产生 5~10 条日志,而且你无法集中看到这次请求的整体表现。

 但是如果你配置了上述三个,则只会产生一行,非常的简洁.

[16:30:55 INF] HTTP GET /WeatherForecast responded 200 in 1.5273 ms

 要注意是:该中间件只能记录它后面发生的事。所以要想记录 Web API 请求,你要把它放在 app.MapControllers() 之前。

6.Output templates

    .WriteTo.File("log.txt",
        outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")

文本类的日志接收器(sinks)使用输出模板来控制日志的格式, 你或许注意到outputTemplate参数,输出格式就是这个参数来配置的

默认就是这个样子的,当然你可以按需配置. 

占位符 含义 示例输出
{Timestamp} 日志时间戳 2025-06-23 16:50:30.123 +08:00
{Level} 日志等级(Information / Warning 等) Information
{Level:u3} 日志等级,3个字母大写 INF, WRN, ERR
{Level:w3} 日志等级,3个字母小写 inf, wrn, err
{Message} 最终渲染出的日志消息 用户登录成功
{Message:lj} 日志消息,同时保留结构化数据 (见下)
{Properties} 所有结构化字段(除了上面显示的) {UserId=123, IP="127.0.0.1"}
{Properties:j} 上面这些字段以 JSON 格式输出 {"UserId":123,"IP":"127.0.0.1"}
{NewLine} 换行 \n
{Exception} 如果有异常,会打印异常堆栈 System.NullReferenceException: xxx...

7.结构化日志

上面我们输出的日志只是一个简单的字符串,人读起来比较方便,但是日志信息是海量的,人读效率显然不高,但是如果将日志结构化输出

{
  "MessageTemplate": "User {User} updated student {StudentId}. Changes: {Changes}",
  "Properties": {
    "User": "admin",
    "StudentId": 1001,
    "Changes": "{\"Name\":\"Alice\",\"Age\":20}"
  }
}

比如输出成一个json,那么计算机也能读了,哪怕是海量的日志计算机都能帮我们快速分析.

Serilog提供了这样强大的功能,并且推荐我们这么做.

_logger.LogInformation("User {User} updated student {StudentId}. Changes: {Changes}",
    currentUser, studentId, JsonConvert.SerializeObject(changes));

只需要这样输出日志,日志的结构就被保留了下来.

不仅如此,还为我们提供了Seq,Seq 不仅有一个强大的 可视化界面(Web UI),还提供了 专用 NuGet 包,用于从你的 .NET 应用中发送结构化日志到它的日志收集服务中。

Seq安装

 Seq官网:Seq — centralized structured logs 

 Nuget包安装

dotnet add package Serilog.Sinks.Seq

https://github.com/datalust/serilog-sinks-seq

Seq是一个日志接受者(是一个独立的服务),而引入这个包可以将日志发送过去.使用下面的方法.

using Serilog;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .WriteTo.Seq("http://localhost:5341") // Seq 默认监听端口
    .CreateLogger();

builder.Host.UseSerilog();

┌────────────┐
│ ASP.NET App│
│ (Serilog)  │
└────┬───────┘
     │
     ▼
Serilog.Sinks.Seq (NuGet 包)
     │
     ▼
┌─────────────┐
│   Seq Server│ ← 启动后访问 http://localhost:5341
│ (实时日志 + 搜索 + 图表) 
└─────────────┘
 

 再写就太长了,Seq的使用可以在官网学习.

 END


网站公告

今日签到

点亮在社区的每一天
去签到