抛出未处理的异常时阻止 Microsoft Logging 从日志中记录
Prevent Microsoft Logging from log when Unhandled exception thrown
我有 ASP.NET Core 3.0 网站。
我在项目上安装了NLog
,这里是配置
public static void Main(string[] args)
{
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
})
.UseNLog()
.UseStartup<Startup>()
.UseUrls("http://*:8080")
.Build();
}
public class Startup
{
// some constructors
public void ConfigureServices(IServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();
var logger = serviceProvider.GetService<ILogger<object>>();
services.AddSingleton<ILogger>(logger);
}
}
nlog.config 文件是
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="false"
throwExceptions="true"
internalLogLevel="Off">
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<variable name="sperator" value="-----------------------------------" />
<targets>
<target name="allfile"
xsi:type="File"
fileName="${basedir}/Logs/${date:format=yyyy-MM-dd}.log"
archiveEvery="Day"
archiveFileName="${basedir}/Logs/Log${shortdate}-{#}.log"
archiveNumbering="Sequence"
layout="eventId : ${event-properties:item=EventId_Name}${newline}${message} ${newline} ${sperator} ${newline}" />
</targets>
<!-- rules to map from logger name to target -->
<rules>
<logger name="*" minlevel="Trace" writeTo="allfile" />
</rules>
</nlog>
appsettings.config
"Logging": {
"LogLevel": {
"Default": "Trace",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
最后一件事我有一个中间件来处理从应用程序各处抛出的所有异常
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
context.Response.Clear();
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature == null)
return;
string jsonResult = "";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
EventId eventId = new EventId(0, Guid.NewGuid().ToString());
_logger.LogError(eventId, message: $"Date : {DateTime.Now.ToString()} \nPath : {context.Request.Path} \nStackTrace: {contextFeature.Error.ToString()}");
jsonResult = JsonConvert.SerializeObject(new
{
ErrorMessage = contextFeature.Error.Message,
StackTrace = contextFeature.Error.StackTrace
});
await context.Response.WriteAsync(jsonResult);
}
}
问题是
当我抛出异常时,我在日志文件中得到两条日志(即使我只记录一次)
我现在确信第一个是由 Asp.NET CORE 自动完成的,因为异常被认为是未处理的异常(即使存在处理异常的中间件)
这是我得到的日志
eventId : UnhandledException
An unhandled exception has occurred while executing the request.
-----------------------------------
eventId : 88e05695-fc66-4d99-8537-aba8f0fa211b
Date : 1/1/2020 5:09:17 PM
Path : /api/AppParameter/ThrowException
StackTrace: System.Exception: this exception has been throw for testing the NLog
at BT_IQM.Services.WebApi.Controllers.AppParameterController.ThrowException() in C:\Users\BitegPC\Source\Repos\BT_Backend_Architecture\BT_IQM.Services.WebApi\Controllers\AppParameterController.cs:line 80
at lambda_method(Closure , Object , Object[] )
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at BT_IQM.Services.WebApi.Utility.LanguageMiddleware.Invoke(HttpContext context) in C:\Users\BitegPC\Source\Repos\BT_Backend_Architecture\BT_IQM.Services.WebApi\Utility\LanguageMiddlewareExtensions.cs:line 27
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
-----------------------------------
第二个日志正是我在中间件中记录的内容,但我的问题在第一个日志中(我根本不想显示),第二个日志对我来说就足够了
我认为我的问题出在日志记录的配置中(特别是在 appsettings.config 文件中)
更新
这里是注册异常中间件的扩展方法
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureExceptionHandler(this IApplicationBuilder app, ILogger logger)
{
app.UseExceptionHandler(appError => appError.UseMiddleware<ExceptionMiddleware>());
}
}
这是整个配置方法
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger
{
app.ConfigureExceptionHandler(logger);
app.ConfigureLanguageMiddleware();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
app.UseExceptionHandler(appError => appError.UseMiddleware<ExceptionMiddleware>());
好的,这里的问题是您正在使用 ASP.NET Core 附带的 ExceptionHandlerMiddleware
。这个中间件负责捕获异常,然后按照您配置的方式处理它。
您使用它的方式是将一个异常处理程序应用程序管道传递给它,您可以在其中 运行 您自己的中间件,然后生成结果。
现在,如果您查看 the source of the ExceptionHandlerMiddleware
,您会发现这就是它最终捕获异常并调用其异常处理程序的方式:
ExceptionDispatchInfo edi;
try
{
var task = _next(context);
// …
return Task.CompletedTask;
}
catch (Exception exception)
{
edi = ExceptionDispatchInfo.Capture(exception);
}
return HandleException(context, edi);
所以它 运行 是中间件管道,只捕获任何异常,最后 运行 是 HandleException
如果到目前为止。这是 HandleException
做的第一件事:
private async Task HandleException(HttpContext context, ExceptionDispatchInfo edi)
{
_logger.UnhandledException(edi.SourceException);
// …
// much later then:
await _options.ExceptionHandler(context);
// …
}
因此它会记录它捕获了一个未处理的异常,甚至在进一步查看异常并最终调用配置的异常处理程序之前,该异常处理程序随后将调用您的自定义中间件。
最后,您将无法阻止此日志记录,因此您现在有三个选择:
通过Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware
过滤键显式过滤掉日志。这可以在不更改 appsettings.json
中的代码的情况下完成。有关详细信息,请参阅 log filtering 上的文档:
"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "None"
}
}
从您的中间件中删除日志并仅使用现有日志。
- 不要将您的中间件作为
ExceptionHandlerMiddleware
的一部分调用,而是使其成为一个普通的中间件,它本身会捕获异常,然后可以做任何您想做的事情。
我有 ASP.NET Core 3.0 网站。
我在项目上安装了NLog
,这里是配置
public static void Main(string[] args)
{
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
})
.UseNLog()
.UseStartup<Startup>()
.UseUrls("http://*:8080")
.Build();
}
public class Startup
{
// some constructors
public void ConfigureServices(IServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();
var logger = serviceProvider.GetService<ILogger<object>>();
services.AddSingleton<ILogger>(logger);
}
}
nlog.config 文件是
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="false"
throwExceptions="true"
internalLogLevel="Off">
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<variable name="sperator" value="-----------------------------------" />
<targets>
<target name="allfile"
xsi:type="File"
fileName="${basedir}/Logs/${date:format=yyyy-MM-dd}.log"
archiveEvery="Day"
archiveFileName="${basedir}/Logs/Log${shortdate}-{#}.log"
archiveNumbering="Sequence"
layout="eventId : ${event-properties:item=EventId_Name}${newline}${message} ${newline} ${sperator} ${newline}" />
</targets>
<!-- rules to map from logger name to target -->
<rules>
<logger name="*" minlevel="Trace" writeTo="allfile" />
</rules>
</nlog>
appsettings.config
"Logging": {
"LogLevel": {
"Default": "Trace",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
最后一件事我有一个中间件来处理从应用程序各处抛出的所有异常
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
context.Response.Clear();
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature == null)
return;
string jsonResult = "";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
EventId eventId = new EventId(0, Guid.NewGuid().ToString());
_logger.LogError(eventId, message: $"Date : {DateTime.Now.ToString()} \nPath : {context.Request.Path} \nStackTrace: {contextFeature.Error.ToString()}");
jsonResult = JsonConvert.SerializeObject(new
{
ErrorMessage = contextFeature.Error.Message,
StackTrace = contextFeature.Error.StackTrace
});
await context.Response.WriteAsync(jsonResult);
}
}
问题是
当我抛出异常时,我在日志文件中得到两条日志(即使我只记录一次)
我现在确信第一个是由 Asp.NET CORE 自动完成的,因为异常被认为是未处理的异常(即使存在处理异常的中间件)
这是我得到的日志
eventId : UnhandledException
An unhandled exception has occurred while executing the request.
-----------------------------------
eventId : 88e05695-fc66-4d99-8537-aba8f0fa211b
Date : 1/1/2020 5:09:17 PM
Path : /api/AppParameter/ThrowException
StackTrace: System.Exception: this exception has been throw for testing the NLog
at BT_IQM.Services.WebApi.Controllers.AppParameterController.ThrowException() in C:\Users\BitegPC\Source\Repos\BT_Backend_Architecture\BT_IQM.Services.WebApi\Controllers\AppParameterController.cs:line 80
at lambda_method(Closure , Object , Object[] )
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at BT_IQM.Services.WebApi.Utility.LanguageMiddleware.Invoke(HttpContext context) in C:\Users\BitegPC\Source\Repos\BT_Backend_Architecture\BT_IQM.Services.WebApi\Utility\LanguageMiddlewareExtensions.cs:line 27
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
-----------------------------------
第二个日志正是我在中间件中记录的内容,但我的问题在第一个日志中(我根本不想显示),第二个日志对我来说就足够了
我认为我的问题出在日志记录的配置中(特别是在 appsettings.config 文件中)
更新
这里是注册异常中间件的扩展方法
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureExceptionHandler(this IApplicationBuilder app, ILogger logger)
{
app.UseExceptionHandler(appError => appError.UseMiddleware<ExceptionMiddleware>());
}
}
这是整个配置方法
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger
{
app.ConfigureExceptionHandler(logger);
app.ConfigureLanguageMiddleware();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
app.UseExceptionHandler(appError => appError.UseMiddleware<ExceptionMiddleware>());
好的,这里的问题是您正在使用 ASP.NET Core 附带的 ExceptionHandlerMiddleware
。这个中间件负责捕获异常,然后按照您配置的方式处理它。
您使用它的方式是将一个异常处理程序应用程序管道传递给它,您可以在其中 运行 您自己的中间件,然后生成结果。
现在,如果您查看 the source of the ExceptionHandlerMiddleware
,您会发现这就是它最终捕获异常并调用其异常处理程序的方式:
ExceptionDispatchInfo edi;
try
{
var task = _next(context);
// …
return Task.CompletedTask;
}
catch (Exception exception)
{
edi = ExceptionDispatchInfo.Capture(exception);
}
return HandleException(context, edi);
所以它 运行 是中间件管道,只捕获任何异常,最后 运行 是 HandleException
如果到目前为止。这是 HandleException
做的第一件事:
private async Task HandleException(HttpContext context, ExceptionDispatchInfo edi)
{
_logger.UnhandledException(edi.SourceException);
// …
// much later then:
await _options.ExceptionHandler(context);
// …
}
因此它会记录它捕获了一个未处理的异常,甚至在进一步查看异常并最终调用配置的异常处理程序之前,该异常处理程序随后将调用您的自定义中间件。
最后,您将无法阻止此日志记录,因此您现在有三个选择:
通过
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware
过滤键显式过滤掉日志。这可以在不更改appsettings.json
中的代码的情况下完成。有关详细信息,请参阅 log filtering 上的文档:"Logging": { "LogLevel": { "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "None" } }
从您的中间件中删除日志并仅使用现有日志。
- 不要将您的中间件作为
ExceptionHandlerMiddleware
的一部分调用,而是使其成为一个普通的中间件,它本身会捕获异常,然后可以做任何您想做的事情。