一、环境概述

  • SDK:.NET Core 8.0

  • IDE:Visual Studio 2022

  • 项目骨架:标准ASP.NET Core Web API骨架

  • 日志实现:Serilog

二、项目依赖

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Serilog" Version="4.3.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
    <PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.8.0" />
    <PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
    <PackageReference Include="Serilog.Expressions" Version="5.0.0" />
    <PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
    <PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.0" />
  </ItemGroup>
</Project>

三、项目结构

Snipaste_2026-01-09_14-54-19

四、Serilog配置文件

这里没有把日志放在appsetting.json中统一配置,而是拆分为了serilog.json(用于不同环境通用配置)和serilog.Development.json(开发环境专属配置),以满足不同环境对日志的不同需要,比如开发需要DEBUG级别日志,但是生产可能只需要INFO级别就可以了,以下是示例配置,具体请参考Serilog官方文档:Serilog — simple .NET logging with fully-structured events

serilog.json

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": {
          "path": "logs/app-.log",
          "rollingInterval": "Day",
          "retainedFileCountLimit": 30,
          "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] {Message:lj}{NewLine}{Exception}"
        }
      }
    ],
    "Enrich": [ "FromLogContext", "WithMachineName" ]
  }
}

serilog.Development.json

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft": "Debug",
        "Swashbuckle.AspNetCore": "Debug"
      }
    }
  }
}

五、Serilog配置

IHostBuilderExtension.cs

using Serilog;

namespace Online.Admin.Serilog.Extensions.Serilog
{
    public static class IHostBuilderExtension
    {
        public static WebApplicationBuilder AddAppSerilog(this WebApplicationBuilder builder)
        {
            builder.Configuration
                .AddJsonFile("serilog.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"serilog.{builder.Environment.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables()
                .Build();
            builder.Host.UseSerilog((hostingContext, services, loggerConfiguration) =>
            {
                loggerConfiguration
                    .ReadFrom.Configuration(hostingContext.Configuration)
                    .ReadFrom.Services(services);
            });
            return builder;
        }
    }
}

六、请求日志中间件

ASP.NET Core Web API其实内置了一个日志中间件,但是那个不满足需求,这里就自定义了一个,这里只是做一下示例,具体要看业务需求需要记录什么。

RequestLoggingMiddleware.cs

using Serilog.Context;

namespace Online.Admin.Serilog.MiddleWare
{
    public class RequestLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<RequestLoggingMiddleware> _logger;

        public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var startTime = DateTime.UtcNow;
            var requestId = Guid.NewGuid().ToString("N")[..8];
            using (LogContext.PushProperty("RequestId", requestId))
            using (LogContext.PushProperty("HttpMethod", context.Request.Method))
            using (LogContext.PushProperty("Url", context.Request.Path))
            using (LogContext.PushProperty("ClientIp", context.Connection.RemoteIpAddress?.ToString()))
            {
                try
                {
                    await _next(context);
                }
                finally
                {
                    var durationMs = (DateTime.UtcNow - startTime).TotalMilliseconds;
                    _logger.LogInformation(
                        "HTTP {HttpMethod} {Url} responded {StatusCode} in {DurationMs} ms",
                        context.Request.Method,
                        context.Request.Path,
                        context.Response.StatusCode,
                        durationMs);
                }
            }
        }
    }
}

七、启动日志

Program.cs

using Online.Admin.Serilog.Extensions.Serilog;
using Online.Admin.Serilog.MiddleWare;
using Serilog;

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("logs/bootstrap-.log", rollingInterval: RollingInterval.Day)
    .CreateLogger();

try
{
    var builder = WebApplication.CreateBuilder(args);
    builder.AddAppSerilog();
    builder.Services.AddControllers();
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    var app = builder.Build();
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    app.UseMiddleware<RequestLoggingMiddleware>();
    app.UseHttpsRedirection();
    app.UseAuthorization();
    app.MapControllers();
    app.Logger.LogInformation("{AppName} 启动成功,环境: {Env}", typeof(Program).Assembly.GetName().Name, app.Environment.EnvironmentName);
    app.Run();
    
}
catch (Exception ex) 
{
    Log.Fatal(ex, "应用启动失败");
}
finally 
{
    Log.CloseAndFlush();
}

八、运行效果

2026-01-09-15-18-58-image

2026-01-09-15-18-03-image