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

如何在.NET Serilog中将接口执行耗时与服务响应合并到同一条日志?

如何将Serilog的接口响应与执行耗时合并到同一条JSON日志中

当然可以实现,下面提供几种实用方案,基于Serilog生态和ASP.NET Core原生机制:

方案一:利用IDiagnosticContext传递数据(推荐)

Serilog.AspNetCore的默认请求日志通过IDiagnosticContext收集上下文信息,我们可以直接把服务响应数据存入这个上下文,让请求日志自动包含该字段。

修改你的PageModel代码:

public class PrivacyModel : PageModel
{
    private readonly ILogger<PrivacyModel> _logger;
    private readonly MyService _service;
    private readonly IDiagnosticContext _diagnosticContext;

    public PrivacyModel(ILogger<PrivacyModel> logger, MyService service, IDiagnosticContext diagnosticContext)
    {
        _logger = logger;
        _service = service;
        _diagnosticContext = diagnosticContext;
    }

    public void OnGet(string question)
    {
        var answer = _service.Execute(question);
        // 将响应数据存入诊断上下文,会被后续的请求日志自动包含
        _diagnosticContext.Set("ServiceResponse", answer);
        
        // 若不需要单独的响应日志行,可以删除下面这行
        // _logger.LogInformation("{@a}", answer);
    }
}

配置后,原来的请求日志行(比如HTTP GET / responded 200 in 95.0581 ms)会自动扩展为包含ServiceResponse字段的JSON日志,同时保留耗时信息,完美满足需求。

方案二:自定义请求日志中间件

如果需要更灵活的控制(比如捕获原始响应体、自定义日志格式),可以自己实现中间件,在请求结束时统一记录合并后的日志。

1. 编写中间件代码

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

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

    public async Task InvokeAsync(HttpContext context)
    {
        var startTime = DateTimeOffset.UtcNow;
        var originalResponseBody = context.Response.Body;
        
        // 替换响应流以捕获响应内容
        using var responseStream = new MemoryStream();
        context.Response.Body = responseStream;

        try
        {
            await _next(context);
            
            // 读取并重置响应流
            responseStream.Seek(0, SeekOrigin.Begin);
            var responseBody = await new StreamReader(responseStream).ReadToEndAsync();
            var elapsedMs = (DateTimeOffset.UtcNow - startTime).TotalMilliseconds;

            // 记录合并后的日志
            _logger.LogInformation(
                "HTTP {Method} {Path} responded {StatusCode} in {ElapsedMs:F4} ms. ServiceResponse: {@ResponseBody}",
                context.Request.Method,
                context.Request.Path,
                context.Response.StatusCode,
                elapsedMs,
                responseBody);
        }
        finally
        {
            // 将捕获的响应内容写回原始响应流
            responseStream.Seek(0, SeekOrigin.Begin);
            await responseStream.CopyToAsync(originalResponseBody);
            context.Response.Body = originalResponseBody;
        }
    }
}

// 注册中间件的扩展方法
public static class CustomRequestLoggingMiddlewareExtensions
{
    public static IApplicationBuilder UseCustomRequestLogging(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<CustomRequestLoggingMiddleware>();
    }
}

2. 注册中间件

在Program.cs中,替换默认的Serilog请求日志中间件,添加自定义中间件:

var app = builder.Build();

// 替换掉 app.UseSerilogRequestLogging()
app.UseCustomRequestLogging();

// 其他中间件配置...

这种方式完全由你控制日志内容和格式,适合复杂场景。

方案三:自定义Enricher结合AsyncLocal传递上下文

通过AsyncLocal存储请求级别的响应数据,再用自定义Enricher将数据注入到请求日志事件中。

1. 创建请求上下文容器

public static class RequestLoggingContext
{
    private static readonly AsyncLocal<Dictionary<string, object>> _contextStore = new AsyncLocal<Dictionary<string, object>>();

    public static void Set(string key, object value)
    {
        _contextStore.Value ??= new Dictionary<string, object>();
        _contextStore.Value[key] = value;
    }

    public static object Get(string key)
    {
        return _contextStore.Value?.TryGetValue(key, out var value) == true ? value : null;
    }
}

2. 实现自定义Enricher

public class RequestContextEnricher : ILogEventEnricher
{
    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var serviceResponse = RequestLoggingContext.Get("ServiceResponse");
        if (serviceResponse != null)
        {
            logEvent.AddOrUpdateProperty(
                propertyFactory.CreateProperty("ServiceResponse", serviceResponse, destructureObjects: true));
        }
    }
}

3. 配置Serilog和使用上下文

在Program.cs的Serilog配置中添加Enricher:

Log.Logger = new LoggerConfiguration()
    .Enrich.With<RequestContextEnricher>()
    // 其他配置(如输出到Elasticsearch)...
    .CreateLogger();

在PageModel中存储响应数据:

public void OnGet(string question)
{
    var answer = _service.Execute(question);
    RequestLoggingContext.Set("ServiceResponse", answer);
}

这样每次请求日志事件都会被自动添加ServiceResponse字段,实现数据合并。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 23:00:57