【发布时间】:2019-03-30 16:58:57
【问题描述】:
我想使用 Serilog 记录带有任何未处理异常的 HTTP 请求的详细信息(例如完整的请求路径、所有 HTTP 标头、任何表单字段等)。因此,我按照本教程将当前 HttpContext.Request 中的信息添加到已记录的 Serilog 日志中:https://blog.getseq.net/smart-logging-middleware-for-asp-net-core/
这是我的SerilogMiddleware 版本;
/// <summary>This class logs Request Headers of any failed request.</summary>
public class SerilogMiddleware
{
private static readonly ILogger _log = global::Serilog.Log.ForContext<SerilogMiddleware>();
private readonly RequestDelegate next;
public SerilogMiddleware( RequestDelegate next )
{
this.next = next ?? throw new ArgumentNullException( nameof( next ) );
}
public async Task Invoke( HttpContext httpContext )
{
if( httpContext == null ) throw new ArgumentNullException( nameof( httpContext ) );
try
{
await this.next( httpContext );
// TODO: Log certian HTTP 4xx responses?
if( httpContext.Response?.StatusCode >= 500 )
{
GetLogForErrorContext( httpContext ).Warning( _MessageTemplateForHttp500 );
}
}
catch( Exception ex ) when( LogException( httpContext, ex ) )
{
// LogException returns false, so this catch block will never be entered.
}
}
const String _MessageTemplateForException = "Unhandled exception in {RequestResource}";
const String _MessageTemplateForHttp500 = "Handled HTTP 500 in {RequestResource}";
private static Boolean LogException( HttpContext httpContext, Exception ex )
{
GetLogForErrorContext( httpContext ).Error( ex, _MessageTemplateForException );
return false; // return false so the exception is not caught and continues to propagate upwards. (I understand this is cheaper than `throw;` inside catch).
}
private static ILogger GetLogForErrorContext( HttpContext httpContext )
{
HttpRequest req = httpContext.Request;
String resource = "{0} {1}{2} {3}".FormatInvariant( req.Method, req.Path, req.QueryString.ToString(), req.Protocol );
// re: `ForContext`: https://nblumhardt.com/2016/08/context-and-correlation-structured-logging-concepts-in-net-5/
ILogger result = _log
.ForContext( "RequestHeaders" , req.Headers.ToDictionary( h => h.Key, h => h.Value.ToString() /* Returns all values, comma-separated */ ), destructureObjects: true )
.ForContext( "RequestResource", resource )
.ForContext( "ResponseStatus", httpContext.Response?.StatusCode )
;
if( req.HasFormContentType )
result = result.ForContext( "RequestForm", req.Form.ToDictionary( v => v.Key, v => v.Value.ToString() ) );
return result;
}
}
不过,我的IWebHostBuilder 代码中也有 Serilog:
IWebHostBuilder webHostBuilder = WebHost
.CreateDefaultBuilder( args )
.ConfigureLogging( (ctx, cfg ) =>
{
cfg.ClearProviders();
cfg.AddSerilog(); // it's unclear if this is required or not
} )
.UseStartup<Startup>()
.UseSerilog();
webHostBuilder.Build().Run();
简而言之:
- 这是一个 ASP.NET Core 中间件类,它将
await next( context )包装在try/catch中,使用Log.ForContext( ... )获取ILogger以向记录器添加新属性(例如请求路径、响应代码等)。 - 因为此代码实际上调用了
ILogger.Error,所以会立即记录事件。 - 但是
try/catch让异常继续向上传播调用堆栈(通过使用catch( Exception ex ) when ( LogExceptionThenReturnFalse( httpContext, ex ) )。 - ...这意味着 Serilog 使用默认扩充再次记录异常和 HTTP 请求。
我希望 Serilog 只记录一次异常,并增加了丰富内容。快速修复是完全捕获我的SerilogMiddleware 中的异常以防止进一步传播,但这意味着它不会命中我的IWebHostBuilder 中配置的Serilog ILogger。如果我让异常传播并且不将其记录在我的中间件中,那么我将无法记录来自 HttpContext 的数据。
如何将信息“附加”到当前的 Serilog“上下文”,以便当异常最终被 IWebHostBuilder Serilog 记录器捕获并记录时,它包含额外的 HttpContext 数据?
【问题讨论】:
-
ConfigureLogging()不需要与UseSerilog()结合使用 - HTH -
@NicholasBlumhardt 你知道为什么我还不喜欢使用扩展方法的 Fluent API 吗? :D
-
我有这个确切的问题,还没有解决方案?
-
@sianabanana 不,抱歉 :( 因此,我的应用程序仍然记录了重复的事件。
标签: asp.net-core serilog