VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#中关于中间件——请求日志记录

第三部分:Web应用开发
4. 中间件——请求日志记录
实例介绍
之前做电商后台时,排查问题全靠猜:用户说“提交订单失败”,但不知道他请求了什么URL、传了什么参数、服务器返回了什么状态;接口响应慢,也不知道是哪个环节卡了。后来加了请求日志中间件,所有请求的方法、路径、参数、响应状态、耗时都自动记录到vb.net教程C#教程python教程SQL教程access 2010教程文件,排查问题时搜一下请求ID,整个流程一目了然,定位问题时间从几小时缩短到几分钟。这节就带你给电商后台加请求日志,彻底搞懂中间件的工作原理。
需求分析
请求日志要解决“全链路追踪、问题快速定位、性能监控”的问题,具体需求如下:
1.核心日志内容:请求ID、请求时间、请求方法、URL、查询参数、表单数据、响应状态码、响应耗时;
2.环境区分:开发环境日志输出到控制台(带颜色),生产环境输出到文件(结构化JSON);
3.路径过滤:排除静态文件(CSS/JS/图片)、健康检查接口(/health)的日志,减少冗余;
4.敏感信息过滤:密码、token等敏感字段自动脱敏,避免泄露;
5.性能监控:记录响应耗时,超过1秒的请求标记为慢请求;
6.结构化日志:生产环境用JSON格式输出,方便ELK等日志系统分析;
7.异常捕获:请求过程中出现异常时,记录异常堆栈信息;
8.可配置性:允许通过配置文件调整日志级别、过滤规则、输出格式。
代码实现
前置条件:.NET 6+、ASP.NET Core MVC/API;需掌握HTTP请求流程、日志框架(默认ILogger或Serilog);推荐集成Serilog(结构化日志更友好)。
场景1:基础请求日志中间件(控制台输出)
实现一个简单的中间件,记录请求的核心信息到控制台,适合开发环境调试。
步骤1:创建RequestLoggingMiddleware(Middleware/RequestLoggingMiddleware.cs)
csharp

	using Microsoft.AspNetCore.Http;
	using Microsoft.Extensions.Logging;
	using System.Diagnostics;
	using System.Text;
	
	namespace ECommerceWeb.Middleware
	{
	public class RequestLoggingMiddleware
	{
	private readonly RequestDelegate _next; // 下一个中间件的委托
	private readonly ILogger<RequestLoggingMiddleware> _logger;
	
	// 构造函数注入:RequestDelegate是必须的(管道的下一个环节),ILogger用于记录日志
	public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
	{
	_next = next;
	_logger = logger;
	}
	
	// 中间件核心方法:处理请求,调用下一个中间件,记录日志
	public async Task InvokeAsync(HttpContext context)
	{
	// 1. 记录请求开始时间
	var stopwatch = Stopwatch.StartNew();
	
	try
	{
	// 2. 记录请求信息(方法、URL、参数)
	await LogRequest(context);
	
	// 3. 调用下一个中间件(管道的核心:把请求传递给后续处理)
	await _next(context);
	
	// 4. 记录响应信息(状态码、耗时)
	stopwatch.Stop();
	await LogResponse(context, stopwatch.ElapsedMilliseconds);
	}
	catch (Exception ex)
	{
	// 5. 捕获异常,记录堆栈信息
	stopwatch.Stop();
	_logger.LogError(ex, "请求处理异常 | 请求ID: {TraceId} | 耗时: {ElapsedMs}ms",
	context.TraceIdentifier, stopwatch.ElapsedMilliseconds);
	throw; // 重新抛出异常,让后续中间件(比如异常处理中间件)处理
	}
	}
	
	// 记录请求信息
	private async Task LogRequest(HttpContext context)
	{
	var request = context.Request;
	
	// 重置请求流指针(因为Body只能读一次,后续中间件可能还要用)
	request.EnableBuffering();
	
	// 读取请求Body(只读取非文件上传的请求,避免大文件占用内存)
	string requestBody = string.Empty;
	if (request.ContentLength.HasValue && request.ContentLength > 0 && !IsMultipartContent(request))
	{
	using var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true);
	requestBody = await reader.ReadToEndAsync();
	// 重置指针到开头,让后续中间件能读取Body
	request.Body.Position = 0;
	}
	
	// 脱敏敏感字段(比如password、token)
	requestBody = DesensitizeSensitiveData(requestBody);
	
	// 记录请求日志
	_logger.LogInformation(
	"请求开始 | 请求ID: {TraceId} | 方法: {Method} | URL: {Url} | 查询参数: {Query} | Body: {Body}",
	context.TraceIdentifier,
	request.Method,
	$"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}",
	request.QueryString.ToString(),
	requestBody);
	}
	
	// 记录响应信息
	private Task LogResponse(HttpContext context, long elapsedMs)
	{
	var response = context.Response;
	
	// 标记慢请求(耗时超过1秒)
	var isSlowRequest = elapsedMs > 1000;
	var logLevel = isSlowRequest ? LogLevel.Warning : LogLevel.Information;
	
	_logger.Log(logLevel,
	"请求结束 | 请求ID: {TraceId} | 状态码: {StatusCode} | 耗时: {ElapsedMs}ms | 慢请求: {IsSlow}",
	context.TraceIdentifier,
	response.StatusCode,
	elapsedMs,
	isSlowRequest);
	
	return Task.CompletedTask;
	}
	
	// 辅助方法:判断是否是文件上传请求
	private bool IsMultipartContent(HttpRequest request)
	{
	return !string.IsNullOrEmpty(request.ContentType) && request.ContentType.StartsWith("multipart/form-data");
	}
	
	// 辅助方法:脱敏敏感数据
	private string DesensitizeSensitiveData(string data)
	{
	if (string.IsNullOrEmpty(data)) return data;
	
	// 替换password字段的值为***
	data = System.Text.RegularExpressions.Regex.Replace(data, "\"password\":\"[^\"]*\"", "\"password\":\"***\"");
	// 替换token字段的值为***
	data = System.Text.RegularExpressions.Regex.Replace(data, "\"token\":\"[^\"]*\"", "\"token\":\"***\"");
	
	return data;
	}
	}
	}

步骤2:注册中间件(Program.cs)
csharp

	var builder = WebApplication.CreateBuilder(args);
	
	// 1. 添加日志服务(默认控制台日志,开发环境启用详细日志)
	builder.Logging.ClearProviders();
	builder.Logging.AddConsole();
	builder.Logging.AddDebug();
	
	// 2. 添加MVC/API服务
	builder.Services.AddControllersWithViews();
	
	var app = builder.Build();
	
	// 3. 注册自定义中间件(注意顺序:要在UseRouting、UseEndpoints之前,否则无法捕获完整请求)
	app.UseMiddleware<RequestLoggingMiddleware>();
	
	// 4. 内置中间件(顺序很重要:异常处理→HTTPS重定向→静态文件→路由→授权→端点)
	if (app.Environment.IsDevelopment())
	{
	app.UseDeveloperExceptionPage();
	}
	else
	{
	app.UseExceptionHandler("/Home/Error");
	app.UseHsts();
	}
	
	app.UseHttpsRedirection();
	app.UseStaticFiles();
	app.UseRouting();
	app.UseAuthorization();
	
	app.MapControllerRoute(
	name: "default",
	pattern: "{controller=Home}/{action=Index}/{id?}");
	
	app.Run();

场景2:生产环境结构化日志(Serilog集成)
开发环境用控制台日志足够,但生产环境需要结构化JSON日志,方便日志系统分析。这里集成Serilog,输出JSON格式到文件。
步骤1:安装Serilog NuGet包
bash

	dotnet add package Serilog.AspNetCore
	dotnet add package Serilog.Sinks.File
	dotnet add package Serilog.Formatting.Compact

步骤2:配置Serilog(Program.cs)
csharp

using Serilog;
	
	// 1. 初始化Serilog(要在WebApplication.CreateBuilder之前)
	Log.Logger = new LoggerConfiguration()
	.MinimumLevel.Information() // 日志级别:只记录Information及以上
	.MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning) // 过滤Microsoft的日志(只记录Warning及以上)
	.MinimumLevel.Override("System", Serilog.Events.LogEventLevel.Warning) // 过滤System的日志
	.Enrich.FromLogContext() // 从日志上下文添加属性(比如请求ID)
	.WriteTo.Console() // 开发环境输出到控制台
	.WriteTo.File(
	path: "logs/log-.json", // 日志文件路径,每天生成一个文件
	rollingInterval: RollingInterval.Day, // 按天滚动
	formatter: new CompactJsonFormatter(), // 输出结构化JSON
	retainedFileCountLimit: 30, // 保留30天的日志
	fileSizeLimitBytes: 1024 * 1024 * 100) // 单个文件最大100MB
	.CreateLogger();
	
	try
	{
	Log.Information("启动电商后台服务");
	
	var builder = WebApplication.CreateBuilder(args);
	
	// 2. 替换默认日志为Serilog
	builder.Host.UseSerilog();
	
	// 3. 其他服务注册...
	builder.Services.AddControllersWithViews();
	
	var app = builder.Build();
	
	// 4. 注册中间件(和场景1一样)
	app.UseMiddleware<RequestLoggingMiddleware>();
	
	// 5. 内置中间件...
	app.UseHttpsRedirection();
	app.UseStaticFiles();
	app.UseRouting();
	app.UseAuthorization();
	
	app.MapControllerRoute(
	name: "default",
	pattern: "{controller=Home}/{action=Index}/{id?}");
	
	app.Run();
	}
	catch (Exception ex)
	{
	Log.Fatal(ex, "服务启动失败");
	}
	finally
	{
	Log.CloseAndFlush(); // 关闭日志,确保所有日志都写入文件
	}

场景3:路径过滤与可配置性
通过配置文件指定要过滤的路径(比如静态文件、健康检查),避免冗余日志。
步骤1:添加配置(appsettings.json)
json

	{
	"RequestLogging": {
	"ExcludePaths": [ "/css/", "/js/", "/images/", "/health" ], // 要排除的路径前缀
	"SlowRequestThreshold": 1000, // 慢请求阈值(毫秒)
	"LogLevel": "Information" // 日志级别
	}
	}
步骤2:创建RequestLoggingOptions(Models/RequestLoggingOptions.cs)
csharp 
	namespace ECommerceWeb.Models
	{
	public class RequestLoggingOptions
	{
	public const string SectionName = "RequestLogging"; // 配置文件中的节点名称
	
	public List<string> ExcludePaths { get; set; } = new();
	public int SlowRequestThreshold { get; set; } = 1000;
	public string LogLevel { get; set; } = "Information";
	}
	}

步骤3:修改中间件,支持配置过滤(RequestLoggingMiddleware.cs)
csharp

	// 新增:注入IOptions<RequestLoggingOptions>
	private readonly RequestLoggingOptions _options;
	
	// 修改构造函数
	public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger, IOptions<RequestLoggingOptions> options)
	{
	_next = next;
	_logger = logger;
	_options = options.Value;
	}
	
	// 在InvokeAsync开头添加过滤逻辑
	public async Task InvokeAsync(HttpContext context)
	{
	// 过滤指定路径的请求
	var path = context.Request.Path.ToString().ToLower();
	if (_options.ExcludePaths.Any(p => path.StartsWith(p.ToLower())))
	{
	// 直接调用下一个中间件,不记录日志
	await _next(context);
	return;
	}
	
	// ...后续逻辑和场景1一样
	}

步骤4:注册配置(Program.cs)
csharp

	// 绑定配置文件中的RequestLogging节点到RequestLoggingOptions
	builder.Services.Configure<RequestLoggingOptions>(
	builder.Configuration.GetSection(RequestLoggingOptions.SectionName));

逐行讲解
场景1:基础中间件核心代码
1.RequestDelegate:中间件管道的核心,_next代表管道的下一个环节,必须通过构造函数注入;
2.InvokeAsync方法:中间件的入口,所有请求都会经过这里,返回Task支持异步;
3.Stopwatch:记录请求耗时,StartNew()开始计时,Stop()结束,ElapsedMilliseconds获取耗时;
4.EnableBuffering():请求Body是只读流,默认只能读一次,调用这个方法后可以重置指针,让后续中间件也能读取Body;
5.LogRequest/LogResponse:分别记录请求和响应的核心信息,context.TraceIdentifier是请求ID(全局唯一,用于追踪请求);
6.异常捕获:用try-catch捕获请求过程中的异常,记录堆栈信息后重新抛出,让异常处理中间件(比如UseDeveloperExceptionPage)处理;
7.脱敏处理:用正则表达式替换敏感字段的值,避免密码、token等信息泄露到日志中。
场景2:Serilog集成
1.LoggerConfiguration:配置Serilog的日志级别、输出目标(控制台+文件)、格式(CompactJsonFormatter是结构化JSON);
2.UseSerilog():替换ASP.NET Core的默认日志框架为Serilog,后续ILogger会使用Serilog的实现;
3.RollingInterval.Day:按天生成日志文件,避免单个文件过大;
4.CloseAndFlush():程序退出时关闭日志,确保所有缓存的日志都写入文件,避免丢失。
场景3:路径过滤与配置
1.IOptions:ASP.NET Core的配置绑定,把appsettings.json中的配置映射到强类型对象;
2.ExcludePaths:在InvokeAsync开头判断请求路径是否在排除列表中,如果是则直接调用下一个中间件,不记录日志;
3.可配置性:通过配置文件调整日志规则,不需要修改代码,适合生产环境动态调整。
基础知识拓展

  1. 中间件管道模型
    管道顺序:中间件的注册顺序决定了执行顺序,比如UseStaticFiles要在UseRouting之前,因为静态文件不需要路由匹配;
    短路中间件:如果中间件不调用_next(context),管道就会短路,后续中间件不会执行(比如授权中间件验证失败时,直接返回401,不会调用后续的控制器);
    内置中间件顺序:官方推荐的顺序是:
    异常处理 → HTTPS重定向 → 静态文件 → 路由 → CORS → 认证 → 授权 → 自定义中间件 → 端点;
  2. 中间件注册方式
方式 用途说明 示例
UseMiddleware() 注册自定义中间件(推荐) app.UseMiddleware()
Use() 内联中间件(简单逻辑) app.Use(async (context, next) => { ... })
Run() 终端中间件(管道的最后一步) app.Run(async context => { await context.Response.WriteAsync("Hello World"); })
Map() 分支管道(根据路径分支) app.Map("/api", apiApp => { apiApp.UseMiddleware(); })
  1. 日志级别与最佳实践
级别 用途说明 示例场景
Trace 最详细的日志(调试用) 记录变量值、循环次数
Debug 调试信息(开发环境) 记录请求参数、中间件执行步骤
Information 正常业务流程记录 用户登录、订单创建、请求完成
Warning 警告信息(不影响业务但需关注) 慢请求、资源不足、参数格式不规范
Error 错误信息(业务失败) 数据库连接失败、API调用错误、异常抛出
Fatal 致命错误(程序崩溃) 服务启动失败、数据库宕机

最佳实践:
开发环境用Debug级别,生产环境用Information级别;
避免在循环中记录Trace/Debug日志,会影响性能;
敏感信息必须脱敏,比如密码、token、手机号;
日志中要包含请求ID,方便全链路追踪。
总结
中间件是ASP.NET Core的核心,它的价值在于统一处理横切关注点(比如日志、认证、授权、异常处理),避免在每个控制器/动作中重复写相同的代码。请求日志中间件的关键要点:
1.管道顺序:注册顺序决定执行顺序,要确保中间件能捕获完整的请求/响应信息;
2.性能考虑:避免在中间件中做耗时操作,比如读取大文件Body时要限制大小;
3.敏感信息保护:必须对密码、token等敏感字段脱敏;
4.可配置性:通过配置文件调整日志规则,适应不同环境的需求;
5.结构化日志:生产环境推荐用Serilog等框架输出JSON格式日志,方便日志系统分析。
比如电商后台中,请求日志中间件能帮你快速定位用户登录失败、订单提交超时、API响应慢等问题,大大提升排查效率。掌握中间件的原理和实现,你可以轻松扩展ASP.NET Core的功能,处理各种横切关注点!

本站原创,转载请注明出处:


相关教程