You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ASP.NET Core中间件中实现请求响应部分属性的数据库日志记录?

实现仅记录指定属性的请求响应日志中间件

嘿,我懂你刚接触请求响应日志中间件的迷茫——很多示例一股脑记录完整的请求响应,但你只需要部分属性对吧?别担心,我来帮你一步步实现符合需求的中间件。

首先你的构造函数写法是没问题的,接下来我们要处理请求和响应的读取,还要注意ASP.NET Core里请求响应流不可重读的特性,得用内存流包装才能安全读取内容。

核心思路拆解

  • 请求侧:直接从HttpContext提取基础属性(比如请求方法、路径),如果需要读取请求体的特定字段,得先启用流缓冲再解析。
  • 响应侧:先把原始响应流替换成内存流,等后续中间件处理完响应后,读取内存流里的内容提取目标属性,最后再把内容复制回原始流返回给客户端。

完整可运行代码示例

public class RequestResponseLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestResponseLoggingMiddleware> _logger;

    public RequestResponseLoggingMiddleware(RequestDelegate next, ILogger<RequestResponseLoggingMiddleware> logger)
    {
        _next = next ?? throw new ArgumentNullException(nameof(next));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 1. 记录请求的指定属性
        var requestLog = new
        {
            Method = context.Request.Method,
            Path = context.Request.Path,
            // 示例:提取自定义请求头的Correlation ID
            CorrelationId = context.Request.Headers.TryGetValue("X-Correlation-ID", out var correlationId) 
                ? correlationId.ToString() 
                : "N/A",
            // 示例:提取Query参数里的用户ID
            UserId = context.Request.Query.TryGetValue("userId", out var userId) 
                ? userId.ToString() 
                : "N/A"
        };

        _logger.LogInformation("Incoming Request: {Method} {Path} | CorrelationId: {CorrelationId} | UserId: {UserId}",
            requestLog.Method, requestLog.Path, requestLog.CorrelationId, requestLog.UserId);

        // 如果需要读取请求体的特定字段(比如JSON格式)
        if (context.Request.ContentLength > 0 && context.Request.ContentType?.Contains("application/json") == true)
        {
            context.Request.EnableBuffering(); // 启用缓冲,让流可以被多次读取
            var requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync();
            context.Request.Body.Position = 0; // 重置流位置,确保后续中间件能正常读取

            // 解析JSON,只提取需要的字段(用JsonDocument避免序列化整个对象,节省性能)
            using var jsonDoc = JsonDocument.Parse(requestBody);
            var requestPayload = new
            {
                Action = jsonDoc.RootElement.TryGetProperty("action", out var action) 
                    ? action.GetString() 
                    : "N/A",
                RequestTimestamp = jsonDoc.RootElement.TryGetProperty("timestamp", out var ts) 
                    ? ts.GetDateTime() 
                    : DateTime.MinValue
            };

            _logger.LogInformation("Request Payload: Action={Action} | Timestamp={RequestTimestamp}",
                requestPayload.Action, requestPayload.RequestTimestamp);
        }

        // 2. 捕获并记录响应的指定属性
        var originalResponseBody = context.Response.Body;
        using var responseBodyStream = new MemoryStream();
        context.Response.Body = responseBodyStream;

        try
        {
            await _next(context); // 调用后续中间件处理请求

            // 记录响应基础属性
            var responseLog = new
            {
                StatusCode = context.Response.StatusCode,
                ContentType = context.Response.ContentType,
                ResponseTime = DateTime.UtcNow
            };

            _logger.LogInformation("Outgoing Response: StatusCode={StatusCode} | ContentType={ContentType} | ResponseTime={ResponseTime}",
                responseLog.StatusCode, responseLog.ContentType, responseLog.ResponseTime);

            // 读取响应体的特定字段(如果是JSON格式)
            responseBodyStream.Seek(0, SeekOrigin.Begin);
            var responseBody = await new StreamReader(responseBodyStream).ReadToEndAsync();
            responseBodyStream.Seek(0, SeekOrigin.Begin);

            if (!string.IsNullOrEmpty(responseBody) && context.Response.ContentType?.Contains("application/json") == true)
            {
                using var jsonDoc = JsonDocument.Parse(responseBody);
                var responsePayload = new
                {
                    Result = jsonDoc.RootElement.TryGetProperty("result", out var result) 
                        ? result.GetString() 
                        : "N/A",
                    Message = jsonDoc.RootElement.TryGetProperty("message", out var msg) 
                        ? msg.GetString() 
                        : "N/A"
                };

                _logger.LogInformation("Response Payload: Result={Result} | Message={Message}",
                    responsePayload.Result, responsePayload.Message);
            }

            // 将内存流的内容复制回原始响应流,返回给客户端
            await responseBodyStream.CopyToAsync(originalResponseBody);
        }
        finally
        {
            // 确保恢复原始响应流,避免资源泄漏
            context.Response.Body = originalResponseBody;
        }
    }
}

// 扩展方法,方便在Startup/Program里注册中间件
public static class RequestResponseLoggingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestResponseLoggingMiddleware>();
    }
}

关键注意事项

  • 流处理细节:请求流要调用EnableBuffering()才能重读,响应流要替换成MemoryStream才能捕获内容,处理完后一定要重置流位置或者恢复原始流,否则后续中间件会报错。
  • 自定义属性调整:你可以完全根据自己的需求修改requestLog、requestPayload、responseLog、responsePayload里的字段,比如添加特定的请求头、响应头,或者业务相关的字段。
  • 性能优化:如果请求/响应体很大,尽量用JsonDocument只解析需要的字段,不要序列化整个对象,避免不必要的内存开销。

内容的提问来源于stack exchange,提问作者mitali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 10:08:34