我有一个托管在 Azure 应用服务 (Windows) 上的 ASP.NET Core 8 Web API。
我使用 NLog 进行日志记录,并使用 Pomelo 8.0.2 访问 MySql 服务器的 Azure 数据库。
目前,API 正在记录所有异常,无论是否已处理。我想知道是否可以不记录已处理的异常?
我将提供我认为相关的课程如下:
Program.cs
:
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog;
using NLog.Web;
using System;
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
namespace PropWorx.API
{
public class Program
{
public static void Main(string[] args)
{
Logger logger = NLog.LogManager
.Setup()
.SetupExtensions(s => s.RegisterLayoutRenderer<NLogLayoutRenderer>("websiteDeploymentId"))
.LoadConfigurationFromAppSettings()
.GetCurrentClassLogger();
// I'm attempting three ways here to turn off logging when the app is running in debug mode:
// 1. The first line sets a value in the Global Diagnostics Context (GDC) of NLog.
// In the nlog.config file, I retrieve this value using the ${gdc:item=debugLogLevel} syntax.
// 2. Directly sets a configuration variable in NLog's current configuration.
// In the nlog.config file, you would retrieve this value using the ${var:debugLogLevel} syntax.
// 3. The third line sets a Global Threshold.
// Only logs that have the same or higher severity than this threshold will be logged.
#if DEBUG
NLog.GlobalDiagnosticsContext.Set("debugLogLevel", "Off");
LogManager.Configuration.Variables["debugLogLevel"] = "Off";
NLog.LogManager.GlobalThreshold = NLog.LogLevel.Off;
#else
NLog.GlobalDiagnosticsContext.Set("debugLogLevel", "Warn");
LogManager.Configuration.Variables["debugLogLevel"] = "Warn";
NLog.LogManager.GlobalThreshold = NLog.LogLevel.Warn;
#endif
// Explicit refresh of Layouts and updates active Logger-objects
LogManager.ReconfigExistingLoggers();
try
{
CreateHostBuilder(args).Build().Run();
}
catch (Exception exception)
{
logger.Error(exception, "Stopped program because of exception");
throw;
}
finally
{
NLog.LogManager.Shutdown();
}
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
})
.UseNLog(); // NLog: setup NLog for Dependency injection
}
}
}
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="true"
internalLogLevel="Info"
throwExceptions="true"
internalLogFile="${basedir}\logs\internal-nlog-AspNetCore.txt">
<!--debugLogLevel is being set in Program.cs depending on whether the app is running in debug mode or not-->
<variables name="debugLogLevel" value="${gdc:item=debugLogLevel:whenEmpty=Warn}" />
<!-- enable asp.net core layout renderers -->
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
<add assembly="Microsoft.ApplicationInsights.NLogTarget" />
</extensions>
<!-- the targets to write to -->
<targets>
<target xsi:type="ApplicationInsightsTarget" name="aiTarget">
<!-- <instrumentationKey>d1e109db-435f-434c-94be-d934ea201690</instrumentationKey> -->
<instrumentationKey>14bd5b2b-3e4e-40fb-9d40-981c68e3be54</instrumentationKey>
<!-- Only required if not using ApplicationInsights.config -->
<contextproperty name="threadid" layout="${threadid}" />
<!-- Can be repeated with more context -->
</target>
<!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection -->
<target xsi:type="Console" name="lifetimeConsole" layout="${newline}${level:truncate=4:tolower=true}\: ${logger}[0]${newline} ${message}${exception:format=tostring}" />
<target name="db"
xsi:type="Database"
dbProvider="MySqlConnector.MySqlConnection, MySqlConnector"
connectionString="${environment:variable=ConnectionStrings__LogDataContext}"
commandType="StoredProcedure"
commandText="`propworx_log`.`InsertLog`">
<parameter name="machineName" layout="${machinename}" />
<parameter name="logged" layout="${date}" />
<parameter name="logLevel" layout="${level}" />
<parameter name="message" layout="${message}" />
<parameter name="logger" layout="${logger}" />
<parameter name="properties" layout="${all-event-properties:separator=|}" />
<parameter name="callsite" layout="${callsite}" />
<parameter name="exception" layout="${exception:tostring}" />
<parameter name="callsiteLineNumber" layout="${callsite-linenumber}" />
<parameter name="stackTrace" layout="${stacktrace}" />
<parameter name="websiteDeploymentId" layout="${websiteDeploymentId}" />
<parameter name="requestHeaders" layout="${aspnet-request-headers}" />
<parameter name="requestBody" layout="${aspnet-request-posted-body}" />
<parameter name="mvcAction" layout="${aspnet-mvc-action}" />
<parameter name="mvcController" layout="${aspnet-mvc-controller}" />
<parameter name="requestMethod" layout="${aspnet-request-method}" />
<parameter name="requestUrl" layout="${aspnet-request-url}" />
<parameter name="requestRouteParameters" layout="${aspnet-request-routeparameters}" />
</target>
<!--${event-properties:item=EventId_Id} | ${uppercase:${level}} | ${logger} | ${message} ${exception:format=tostring} | url: ${aspnet-request-url} | action: ${aspnet-mvc-action}"-->
</targets>
<!-- rules to map from logger name to target -->
<rules>
<!--Skip non-critical Microsoft logs and so log only own logs (BlackHole) -->
<logger name="System.*" finalMinLevel="Warn" />
<logger name="Microsoft.*" finalMinLevel="Warn" />
<!--Output hosting lifetime messages to console target for faster startup detection -->
<logger name="Microsoft.Hosting.Lifetime*" finalMinLevel="Info" />
<logger name="*" minlevel="${var:debugLogLevel}" writeTo="db,aiTarget" />
</rules>
appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
正如您在 Program.cs 中看到的,当我在调试模式下运行 API 时,我会关闭日志记录。这部分似乎正在发挥作用。但是,正如前面提到的,它还记录处理的异常,这是我不想要的。举个例子,这里是我的一个控制器中的一个操作方法,我正在处理异常,因此不希望记录它,因为当它只是错误的用户输入时,它只会使日志变得混乱:
// POST: api/Properties
[HttpPost]
public async Task<ActionResult<Property>> PostProperty(PropertyEditDto propertyDto)
{
Property property = _mapper.Map<Property>(propertyDto);
await _context.Properties.AddAsync(property);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (ex.InnerException.Message.StartsWith("Duplicate entry") && ex.InnerException.Message.EndsWith("'unit_UNIQUE'"))
{
return StatusCode(StatusCodes.Status400BadRequest, $"The selected complex or scheme already has a unit number {property.UnitNumber}");
}
}
return CreatedAtAction("GetProperty", new { id = property.Id }, prop);
}
以上导致记录以下异常:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> MySqlConnector.MySqlException (0x80004005): Duplicate entry '1-1' for key 'unit_UNIQUE'
at MySqlConnector.Core.ServerSession.ReceiveReplyAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/ServerSession.cs:line 894
at MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync(IOBehavior ioBehavior) in /_/src/MySqlConnector/Core/ResultSet.cs:line 37
at MySqlConnector.MySqlDataReader.ActivateResultSet(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 130
at MySqlConnector.MySqlDataReader.NextResultAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 90
at MySqlConnector.MySqlDataReader.NextResultAsync(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 49
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.<>c__DisplayClass30_0`2.<<ExecuteAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func`4 operation, Func`4 verifySucceeded, TState state, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func`4 operation, Func`4 verifySucceeded, TState state, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
顺便说一下,我没有任何自定义异常处理程序中间件。
任何想法将不胜感激。谢谢你。
在 try-catch 块中运行整个程序是一个坏主意,因为它会对性能产生严重影响。您将需要停止这样做并切换到全局异常处理程序方法。这是我个人使用的代码:
if (app.Environment.IsDevelopment())
{
// show detailed exception information in development
app.UseDeveloperExceptionPage();
}
else
{
// use custom exception handling in production
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Run(async context =>
{
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
if (exceptionHandlerFeature != null)
{
var errorCode = Guid.NewGuid().ToString();
var exception = exceptionHandlerFeature.Error;
#region try to get current user
var userName = "Anonymous";
if (context != null && context.User != null && context.User.Identity != null && context.User.Identity.IsAuthenticated)
userName = context.User.Identity.Name;
#endregion
Log.Information("---------------------------------------------------------------------------------------------------------------------------------------------------");
Log.Error(exception, "GLOBAL EXCEPTION HANDLED. Error Code: {ErrorCode}, User: {UserName}, Time: {Time}", errorCode, userName, DateTime.Now);
// redirect to an error screen
context.Response.Redirect("/error?errorCode=" + errorCode);
}
});
});
app.UseHsts();
}
顺便说一句,当您执行“.UseNLog();”时,您并不是在向服务容器添加服务,而是正在注册一个中间件(只是想澄清一下,因为您在该行中写入了注释)