使用 Serilog ASP.NET Core 重复错误消息
Duplicate error messages with Serilog ASP.NET Core
ASP.NET 使用 Serilog 的核心 5 Razor 页面
UseStatusCodePagesWithReExecute 按预期工作并在进入我的 /CustomError 页面后重新执行页面。
如何抑制对重新执行页面的第二次调用的 Serilog 日志记录?
password-postgres 完整样本
// program.cs
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
// if only do warning, then will get duplicate error messages when an exception is thrown, then again when re-executed
// we do get 2 error message per single error, but only 1 stack trace
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Fatal)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
try
{
Log.Information("Starting up");
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog() // <- Add this line
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
然后
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// snip
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/CustomError");
}
app.UseStaticFiles();
// https://khalidabuhakmeh.com/handle-http-status-codes-with-razor-pages
// https://andrewlock.net/retrieving-the-path-that-generated-an-error-with-the-statuscodepages-middleware/
app.UseStatusCodePagesWithReExecute("/CustomError", "?statusCode={0}");
app.UseRouting();
// don't want request logging for static files so put this serilog middleware here in the pipeline
app.UseSerilogRequestLogging(); // <- add this
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Strict });
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
然后
// CustomError.cshtml.cs
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class CustomErrorModel : PageModel
{
public int? CustomStatusCode { get; set; }
public void OnGet(int? statusCode = null)
{
var feature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
// a non 500 eg 404
// this can be non page requests eg /js/site-chart.js
// feature can be null when a 500 is thrown
if (feature != null)
{
//Log.Warning($"Http Status code {statusCode} on {feature.OriginalPath}");
CustomStatusCode = statusCode;
return;
}
// a 500
// relying on serilog to output the error
//var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
// integration tests can call a page where the exceptionHandlerPathFeature can be null
CustomStatusCode = 500;
// somewhere else is emitting the Log.Error stacktracke
//Log.Error($"Exception is {exceptionHandlerPathFeature.Error}");
//OriginalPath = exceptionHandlerPathFeature.Path;
//Exception exception = exceptionHandlerPathFeature.Error;
}
public void OnPost()
{
Log.Warning( "ASP.NET failure - maybe antiforgery. Caught by OnPost Custom Error. Sending a 400 to the user which is probable");
Log.Warning("Need to take off minimumlevel override in Program.cs for more information");
CustomStatusCode = 400;
}
}
错误的重复日志条目,例如 404 - 理想情况下只需要 1
更新
感谢 Alan 在下方的回答,我已将 SerilogRequestLogging 放在配置的开头。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSerilogRequestLogging();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/CustomError");
}
app.UseStaticFiles();
app.UseStatusCodePagesWithReExecute("/CustomError", "?statusCode={0}");
// snip..
}
这在日志中给出了 2 ERR
条消息:
我很满意。
可能有一种方法可以合并 2 个 ERR
条目,但这很简单。这 2 个条目也适用于不同的概念。请求和例外。
可以给每个日志条目一个 RequestId
,就像样板 Error.cshtml.cs 给出的那样。
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
但是,嘿,这个解决方案对我来说已经足够好了。谢谢艾伦!
简答:
为了防止在这种情况下重复记录,您可以在 Configure
方法中将 UseSerilogRequestLogging()
放在 UseStatusCodePagesWithReExecute()
之前:
app.UseSerilogRequestLogging();
app.UseStatusCodePagesWithReExecute("/CustomError", "?statusCode={0}");
长答案:
根据 ASP.NET documentation,用于构建中间件的顺序很重要:
The order that middleware components are added in the Startup.Configure
method defines the order in which the middleware components are invoked on requests and the reverse order for the response. The order is critical for security, performance, and functionality.
现在,根据 Serilog documentation,UseSerilogRequestLogging
将仅处理在中间件管道中 之后出现的组件。
考虑到这一点,我注意到在 Startup
class 上,您添加了 UseSerilogRequestLogging
中间件 after UseStatusCodePagesWithReExecute
.
UseStatusCodePagesWithReExecute
documentation 说它做了两件事:
- Returns the original status code to the client.
- Generates the response body by re-executing the request pipeline using an alternate path.
换句话说,当你出错时,Serilog 似乎不知道第二个请求是由之前的中间件在内部生成的,所以它会记录两个:
- 原始请求
- 第二个,通过执行备用路径创建(
/CustomError
)
逐步说明,当 GET
请求到达 StatusCode400
端点时,请求管道中会发生什么:
请求流程:
UseStatusCodePagesWithReExecute -> Serilog -> StatusCode400 endpoint (error happens here)
响应流程:
UseStatusCodePagesWithReExecute (error status code, so re-execution kicks in) <- Serilog (logs request) <- StatusCode400 endpoint
重新执行请求流程:
UseStatusCodePagesWithReExecute -> Serilog -> CustomError endpoint (no error now, but re-execution preserves the HTTP status code)
重新执行响应流程:
UseStatusCodePagesWithReExecute <- Serilog (logs request) <- CustomError endpoint
因此,如果你不想有重复的日志信息,你可以将Serilog中间件放在之前重新执行一个,这样它只会处理一个请求到来来自它(第二个):
请求流程:
Serilog -> UseStatusCodePagesWithReExecute -> StatusCode400 endpoint (error happens here)
响应流程:
Serilog <- UseStatusCodePagesWithReExecute (error status code, so re-execution kicks in) <- StatusCode400 endpoint
重新执行请求流程:
Serilog -> UseStatusCodePagesWithReExecute -> CustomError endpoint (no error now, but re-execution preserves the HTTP status code)
重新执行响应流程:
Serilog (logs request) <- UseStatusCodePagesWithReExecute <- CustomError endpoint
ASP.NET 使用 Serilog 的核心 5 Razor 页面
UseStatusCodePagesWithReExecute 按预期工作并在进入我的 /CustomError 页面后重新执行页面。
如何抑制对重新执行页面的第二次调用的 Serilog 日志记录?
password-postgres 完整样本
// program.cs
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
// if only do warning, then will get duplicate error messages when an exception is thrown, then again when re-executed
// we do get 2 error message per single error, but only 1 stack trace
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Fatal)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
try
{
Log.Information("Starting up");
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog() // <- Add this line
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
然后
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// snip
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/CustomError");
}
app.UseStaticFiles();
// https://khalidabuhakmeh.com/handle-http-status-codes-with-razor-pages
// https://andrewlock.net/retrieving-the-path-that-generated-an-error-with-the-statuscodepages-middleware/
app.UseStatusCodePagesWithReExecute("/CustomError", "?statusCode={0}");
app.UseRouting();
// don't want request logging for static files so put this serilog middleware here in the pipeline
app.UseSerilogRequestLogging(); // <- add this
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Strict });
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
然后
// CustomError.cshtml.cs
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class CustomErrorModel : PageModel
{
public int? CustomStatusCode { get; set; }
public void OnGet(int? statusCode = null)
{
var feature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
// a non 500 eg 404
// this can be non page requests eg /js/site-chart.js
// feature can be null when a 500 is thrown
if (feature != null)
{
//Log.Warning($"Http Status code {statusCode} on {feature.OriginalPath}");
CustomStatusCode = statusCode;
return;
}
// a 500
// relying on serilog to output the error
//var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
// integration tests can call a page where the exceptionHandlerPathFeature can be null
CustomStatusCode = 500;
// somewhere else is emitting the Log.Error stacktracke
//Log.Error($"Exception is {exceptionHandlerPathFeature.Error}");
//OriginalPath = exceptionHandlerPathFeature.Path;
//Exception exception = exceptionHandlerPathFeature.Error;
}
public void OnPost()
{
Log.Warning( "ASP.NET failure - maybe antiforgery. Caught by OnPost Custom Error. Sending a 400 to the user which is probable");
Log.Warning("Need to take off minimumlevel override in Program.cs for more information");
CustomStatusCode = 400;
}
}
错误的重复日志条目,例如 404 - 理想情况下只需要 1
更新
感谢 Alan 在下方的回答,我已将 SerilogRequestLogging 放在配置的开头。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSerilogRequestLogging();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/CustomError");
}
app.UseStaticFiles();
app.UseStatusCodePagesWithReExecute("/CustomError", "?statusCode={0}");
// snip..
}
这在日志中给出了 2 ERR
条消息:
我很满意。
可能有一种方法可以合并 2 个 ERR
条目,但这很简单。这 2 个条目也适用于不同的概念。请求和例外。
可以给每个日志条目一个 RequestId
,就像样板 Error.cshtml.cs 给出的那样。
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
但是,嘿,这个解决方案对我来说已经足够好了。谢谢艾伦!
简答:
为了防止在这种情况下重复记录,您可以在 Configure
方法中将 UseSerilogRequestLogging()
放在 UseStatusCodePagesWithReExecute()
之前:
app.UseSerilogRequestLogging();
app.UseStatusCodePagesWithReExecute("/CustomError", "?statusCode={0}");
长答案:
根据 ASP.NET documentation,用于构建中间件的顺序很重要:
The order that middleware components are added in the
Startup.Configure
method defines the order in which the middleware components are invoked on requests and the reverse order for the response. The order is critical for security, performance, and functionality.
现在,根据 Serilog documentation,UseSerilogRequestLogging
将仅处理在中间件管道中 之后出现的组件。
考虑到这一点,我注意到在 Startup
class 上,您添加了 UseSerilogRequestLogging
中间件 after UseStatusCodePagesWithReExecute
.
UseStatusCodePagesWithReExecute
documentation 说它做了两件事:
- Returns the original status code to the client.
- Generates the response body by re-executing the request pipeline using an alternate path.
换句话说,当你出错时,Serilog 似乎不知道第二个请求是由之前的中间件在内部生成的,所以它会记录两个:
- 原始请求
- 第二个,通过执行备用路径创建(
/CustomError
)
逐步说明,当 GET
请求到达 StatusCode400
端点时,请求管道中会发生什么:
请求流程:
UseStatusCodePagesWithReExecute -> Serilog -> StatusCode400 endpoint (error happens here)
响应流程:
UseStatusCodePagesWithReExecute (error status code, so re-execution kicks in) <- Serilog (logs request) <- StatusCode400 endpoint
重新执行请求流程:
UseStatusCodePagesWithReExecute -> Serilog -> CustomError endpoint (no error now, but re-execution preserves the HTTP status code)
重新执行响应流程:
UseStatusCodePagesWithReExecute <- Serilog (logs request) <- CustomError endpoint
因此,如果你不想有重复的日志信息,你可以将Serilog中间件放在之前重新执行一个,这样它只会处理一个请求到来来自它(第二个):
请求流程:
Serilog -> UseStatusCodePagesWithReExecute -> StatusCode400 endpoint (error happens here)
响应流程:
Serilog <- UseStatusCodePagesWithReExecute (error status code, so re-execution kicks in) <- StatusCode400 endpoint
重新执行请求流程:
Serilog -> UseStatusCodePagesWithReExecute -> CustomError endpoint (no error now, but re-execution preserves the HTTP status code)
重新执行响应流程:
Serilog (logs request) <- UseStatusCodePagesWithReExecute <- CustomError endpoint