如何排除在 ASP .NET Core 中记录已处理的异常(使用 NLog)

问题描述 投票:0回答:1

我有一个托管在 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)

顺便说一下,我没有任何自定义异常处理程序中间件。

任何想法将不胜感激。谢谢你。

c# asp.net-core logging asp.net-core-webapi nlog
1个回答
0
投票

在 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();”时,您并不是在向服务容器添加服务,而是正在注册一个中间件(只是想澄清一下,因为您在该行中写入了注释)

© www.soinside.com 2019 - 2024. All rights reserved.