ASP.NET MVC NLog With Exception Filter

2023-01-27

筆記如何透過 NLog 並且在 ASP.NET MVC 使用 Exception Filter 的方式達成發生例外情形時,儲存為日誌資訊,讓系統維護人員能夠主動出擊處理問題。

logo

說明

需要日誌紀錄的原因:

一、依照系統設計的安全性需求,紀錄管理者行為、身分驗證行為、存取資源行為與重要資料異動等留下稽核紀錄。

二、將例外情形發生時,儲存相關資訊為日誌紀錄,讓系統維護人員在處理問題時能夠得到更詳盡甚至是還原使用者所碰到的問題。


Nuget Install

注意在安裝以下套件的時候,可能會要求 EntityFramework 要符合 6.2 以上的版本。

<package id="NLog" version="5.1.4" targetFramework="net48" />
<package id="NLog.Database" version="5.1.4" targetFramework="net48" />
<package id="NLog.Extensions.Logging" version="5.2.3" targetFramework="net48" />
<package id="NLog.Schema" version="5.1.4" targetFramework="net48" />
<package id="NLog.Web" version="5.2.3" targetFramework="net48" />

Database Create Table Script

CREATE TABLE [dbo].[Log](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[MachineName] [nvarchar](50) NOT NULL,
	[Logged] [datetime] NOT NULL,
	[Level] [nvarchar](50) NOT NULL,
	[Message] [nvarchar](max) NOT NULL,
	[Exception] [nvarchar](max) NULL,
	[UserName] [nvarchar](250) NULL,
	[IP] [nvarchar](128) NULL,
  [RequestUrl] [nvarchar](200) NULL,
	[QueryString] [nvarchar](max) NULL,
	[Payload] [nvarchar](max) NULL,  
 CONSTRAINT [PK_dbo.Log] PRIMARY KEY CLUSTERED 
 (
	[Id] ASC
 )
)

不透過安裝 NLog.config Nuget Packages,而是直接在專案當中新增與寫入以下 Config。

/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"
      xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
      autoReload="true"
      throwExceptions="true"
      internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">

  <!-- optional, add some variables
  https://github.com/nlog/NLog/wiki/Configuration-file#variables
  -->
  <variable name="myvar" value="myvalue"/>

  <!--
  See https://github.com/nlog/nlog/wiki/Configuration-file
  for information on customizing logging rules and outputs.
   -->
  <extensions>
    <add assembly="NLog.Web"/>
  </extensions>

  <targets>
    <target name="database" xsi:type="Database" connectionStringName="ERSEntities">
      <commandText>
        insert into dbo.Log
        (
        MachineName, Logged, Level, Message, Exception, UserName, IP, RequestUrl, QueryString, Payload
        )
        values (
        @MachineName, @Logged, @Level, @Message, @Exception, @UserName, @IP, @RequestUrl, @QueryString, @Payload
        );
      </commandText>

      <parameter name="@MachineName" layout="${machinename}" />
      <parameter name="@Logged" layout="${date}" />
      <parameter name="@Level" layout="${level}" />
      <parameter name="@Message" layout="${message}" />
      <parameter name="@Exception" layout="${exception:tostring}" />
      <parameter name="@UserName" layout="${aspnet-user-identity}" />
      <parameter name="@IP" layout="${aspnet-request-ip}" />
      <parameter name="@RequestUrl" layout="${aspnet-request-url}" />
      <parameter name="@QueryString" layout="${aspnet-request-querystring}" />
      <parameter name="@Payload" layout="${aspnet-request-posted-body}" />
    </target>
  </targets>

  <rules>
    <logger name="*" minlevel="Debug" writeTo="database" />
  </rules>
</nlog>

除了使用 NLog.web 的方式來使用 aspnet-user-identityaspnet-request-ip 以及 aspnet-request-url 等方式來分別取得使用者名稱、來源 IP 以及目標路徑外,也可以使用 NLog 的 WithProperty 來自行傳遞資料:

NLog.web With Property
aspnet-user-identity context.HttpContext.User.Identity.Name
aspnet-request-ip context.HttpContext.Request.UserHostAddress
aspnet-request-url context.HttpContext.Request.Url

可以直接在 ASP.NET MVC 5 的 Controller 驗證功能:

public class HomeController : Controller
{
    private NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    public ActionResult Index()
    {
        logger.Info($"The message I want to record.");
        return View();
    }
}

Setup in ASP.NET MVC 5

Application_Error

Global.asax.cs

void Application_Error(object sender, EventArgs e)
{
    Exception exception = Server.GetLastError();

    var logger = LogManager.GetCurrentClassLogger();
    logger.Error(exception);
}

Application_Error in global.asax not catching errors in WebAPI

ExceptionFilter

HandleErrorAttribute

HandleErrorAttribute 是 ASP.NET MVC 預設實作的例外處理機制,會透過 FilterConfig.cs 註冊的全域中,讓所有發生的 ASP.NET Http Status Code 500 Error (對 🙂 只有 500,404 等其他 Http Status Code 並不會) 都會經由 HandleErrorAttribute 處理。

HandleErrorAttribute 的處理就是以 /Views/Shared/Error.cshtml 作為回應結果。

所以光是依靠 Application_Error 來處理 Exception 不夠,還要搭配擴充 HandleErrorAttribute 才能夠在使用 Custom Error 的情況下保持 Exception 的紀錄。

using NLog;
using System.Web.Mvc;

public class NLogExceptionFilter : HandleErrorAttribute
{
    private readonly Logger logger = LogManager.GetCurrentClassLogger();

    public override void OnException(ExceptionContext context)
    {
        logger.Error(context.Exception);

        base.OnException(context);
    }
}
[NLogExceptionFilter]
public class HomeController : Controller
{
    // ...
}

參考連結

ASP.NET MVC 監測監控與LOG記錄
ASP.NET MVC全局異常處理和捕獲的思路
ASP.NET MVC實現IExceptionFilter接口編寫自定義異常處理過濾器