2025年1月1日 星期三

在.NET9 Web API 專案使用NLog教學與紀錄Trace Id

在.NET9 Web API 專案使用NLog教學與紀錄Trace Id

在這篇文章中,我們將建立起一個.NET9 的ASP.NET Core Web API 項目,並且說明如何安裝與設定和使用NLog,由於這是一個Web API 項目,所以,將會透過Trace Id 來記錄下每次HTTP 請求的時候,與此有關的Log 有哪些,透過NLog 來記錄Trace Id 與Exception 的紀錄

建立測試專案

請依照底下的操作,建立起這篇文章需要用到的練習項目

  • 打開Visual Studio 2022 IDE 應用程式
  • 從[Visual Studio 2022] 對話窗中,點選右下方的[建立新的項目] 按鈕
  • 在[建立新項目] 對話窗右半部
    • 切換[所有語言(L)] 下拉選單控制項為[C#]
    • 切換[所有項目類型(T)] 下拉選單控制項為[Web API]
  • 在中間的項目模板清單中,找到並且點擊[ASP.NET Core Web API] 項目模板選項

    此專案模板可用於ASP.NET Core 控制器或最小API 建立RESTful Web API,並可選擇性支援OpenAPI 和驗證

  • 點擊右下角的[下一步] 按鈕
  • 在[設定新的項目] 對話窗
  • 找到[項目名稱] 字段,輸入csNlogTraceId作為項目名稱
  • 在剛剛輸入的[項目名稱] 欄位下方,確認沒有勾選[將解決方案與項目至於相同目錄中] 這個檢查盒控制項
  • 點擊右下角的[下一步] 按鈕
  • 現在將會看到[其他資訊] 對話窗
  • 在[架構] 欄位中,請選擇最新的開發框架,這裡選擇的[架構] 是 :.NET 9.0 (標準字詞支援)
  • 在[驗證類型] 選擇無
  • 在這個練習中,需要去勾選[不要使用最上層陳述式(T)] 這個檢查盒控制項

    這裡的這個操作,可以由讀者自行決定是否要勾選這個檢查盒控制項

  • 請點擊右下角的[建立] 按鈕

稍微等候一下,這個項目將會建立完成

安裝要用的NuGet 開發套件

因為開發此專案時會使用這些NuGet 套件,請依照底下說明,將需要用到的NuGet 套件安裝。

安裝NLog.Web.AspNetCore 套件

請依照底下說明操作步驟,將這個套件安裝到專案內

  • 鼠標右擊[方案總管] 視窗內的[專案節點] 下方的[依賴] 節點
  • 從彈出功能表清單中,點選[管理NuGet 套件] 這個功能選項清單
  • 此時,將會看到[NuGet: csNlogTraceId] 視窗
  • 切換此視窗的標籤頁次到名稱為[瀏覽] 這個標籤頁次
  • 在左上方找到一個搜尋文字輸入盒,在此輸入NLog.Web.AspNetCore
  • 在視窗右方,將會看到該套件詳細說明的內容,其中,右上方有的[安裝] 按鈕
  • 點擊這個[安裝] 按鈕,將這個套件安裝到專案內

建立一個中介軟件類別TraceIdMiddleware

在這個專案中,我們將建立一個中介軟件類別TraceIdMiddleware,這個中介軟件類別將會用來記錄下每次HTTP 請求的Trace Id,這個值將由[HttpContext.TraceIdentifier] 來取得,這個Trace Id 將會被NLog 用來記錄下每次HTTP 請求的Log 訊息,而這裡將會透過了NLog.MappedDiagnosticsLogicalContext 這個類別來設定TraceId 的值。

在[nlog.config] 內,若想要顯示TraceId,可以透過${mdlc:TraceId}這個格式來取得TraceId 的值

namespace csNlogTraceId;

public class TraceIdMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<TraceIdMiddleware> logger;

    public TraceIdMiddleware(RequestDelegate next,
        ILogger<TraceIdMiddleware> logger)
    {
        _next = next;
        this.logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            var traceId = context.TraceIdentifier;
            NLog.MappedDiagnosticsLogicalContext.Set("TraceId", traceId);
            await _next(context);
        }
        catch (Exception ex)
        {
            // 记录异常
            logger.LogError(ex, "發生不可預期的錯誤");
            // 将异常重新抛出
            //throw;
        }
    }
}

在[InvokeAsync] 方法內,將會使用try...catch 敘述將其捕捉起來,並且透過NLog 來記錄下這個Exception 的訊息,這樣,當發生Exception 的時候,將會記錄下這個Exception 的訊息,並且該Exception 的訊息將包含TraceId 的值

修改Program.cs 類別內容

  • 找到並且打開[Program.cs] 這個檔案
  • 使用底下程式碼替換掉這個檔案的內容
using NLog;
using NLog.Web;

namespace csNlogTraceId
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
            try
            {
                var builder = WebApplication.CreateBuilder(args);

                builder.Logging.ClearProviders();
                builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
                builder.Host.UseNLog();

                // Add services to the container.

                builder.Services.AddControllers();
                // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
                builder.Services.AddOpenApi();

                var app = builder.Build();

                app.UseMiddleware<TraceIdMiddleware>();
                
                // Configure the HTTP request pipeline.
                if (app.Environment.IsDevelopment())
                {
                    app.MapOpenApi();
                }

                app.UseHttpsRedirection();

                app.UseAuthorization();


                app.MapControllers();

                app.Run();
            }
            catch (Exception ex)
            {
                logger.Error(ex, "Stopped program because of an exception");
                throw;
            }
            finally
            {
                LogManager.Shutdown();
            }
        }
    }
}

這裡先使用[NLogBuilder.ConfigureNLog("nlog.config")] 設定NLog 的設定文件,接著,透過[GetCurrentClassLogger()] 來取得Logger 的實例,這樣,就可以透過這個Logger 來記錄下Log 訊息

接著,透過[builder.Logging.ClearProviders()] 來清除原本的Log 訊息提供者,接著,透過[builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace)] 來設定最低的Log 等級為Trace,這樣,就可以記錄所有的Log 訊息

接著,透過[builder.Host.UseNLog()] 設定使用NLog 來記錄Log 訊息

接著,透過[app.UseMiddleware()] 來設定使用TraceIdMiddleware 這個中間軟件類別,這樣,每次HTTP 請求的時候,將會記錄下TraceId 的值

修改nlog.config 內容

  • 滑鼠右擊專案節點
  • 從彈出的功能表清單中,點選[新增項目] 功能選項
  • 在[新增項目] 對話窗中,點選對話窗左方的[已安裝] > [C#] > [ASP.NET Core] > [資料]
  • 在[新增項目] 對話窗右方,找到[XML 檔] 這個項目,並且點擊它
  • 在[名稱] 欄位中,輸入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"
      throwConfigExceptions="true"
      internalLogLevel="Info"
      internalLogFile="internal-nlog.log">
	<!-- 日志级别:Trace < Debug < Info < Warn < Error < Fatal -->
	<targets>
		<!-- 文件输出目标 -->
		<target xsi:type="AsyncWrapper" name="File_async"
				overflowAction="Discard"
				queueLimit="500000"
				batchSize="500">
			<target name="file" xsi:type="File" fileName="logs/logfile-${shortdate}.log"
				layout="${longdate}|${mdlc:TraceId}|${level:uppercase=true}|${logger}|${message}|${exception:format=toString,StackTrace}" />
		</target>
		<!-- 控制台输出目标 -->
		<target xsi:type="AsyncWrapper" name="Console_async"
		overflowAction="Discard"
		queueLimit="500000"
		batchSize="500">
			<target name="console" xsi:type="Console"
				layout="${longdate}|${mdlc:TraceId}|${level:uppercase=true}|${logger}|${message}|${exception:format=toString,StackTrace}" />
		</target>
	</targets>

	<rules>
		<!-- Suppress output from Microsoft framework when non-critical -->
		<logger name="System.*" finalMinLevel="Warn" />
		<logger name="Microsoft.*" finalMinLevel="Warn" />
		<!-- Keep output from Microsoft.Hosting.Lifetime to console for fast startup detection -->
		<logger name="Microsoft.Hosting.Lifetime*" finalMinLevel="Info" writeTo="console_async" />
		<!-- 最低级别为 Info,输出到文件和控制台 -->
		<logger name="*" minlevel="Info" writeTo="file_async,console_async" />
	</rules>
</nlog>

這裡設定了兩個Target,一個是File_async,另一個是Console_async,這樣,就可以將Log 訊息同時輸出到檔案與Console 上。

對於非同步的日誌寫入,這裡設定了[AsyncWrapper] 這個Target,這樣,就可以將Log 訊息寫入到檔案與Console 上,這樣,就可以提高Log 訊息的寫入效能,這裡用到的參數有[overflowAction] 這個參數,這個參數可以設定當Log 訊息寫入的速度超過了設定的速度時,該如何處理,這裡設定為[Discard],這樣,當Log當訊息寫入的速度超過了設定的速度時,就會捨棄掉這些Log 訊息,這樣,就可以避免Log 訊息寫入的速度過快,而導致系統的效能下降。對於[queueLimit] 這個參數,這個參數可以設定Log 訊息的隊列上限,這樣,就可以避免Log 訊息寫入的速度過快,而導致系統的效能下降,這裡設定為500000,這樣,就可以讓Log訊息的隊列上限為500000,這樣,就可以避免Log 訊息寫入的速度過快,而導致系統的效能下降。對於[batchSize] 這個參數,這個參數可以設定Log 訊息的批次寫入數量,這樣,就可以提高Log 訊息的寫入效能,這裡設定為500,這樣,就可以讓Log 訊息的批次寫入數量為500,這樣,就可以提高Log 訊息的寫入效能。

對於[layout] 這個參數,這個參數可以設定Log 訊息的格式,這裡設定為${longdate}|${mdlc:TraceId}|${level:uppercase=true}|${logger}|${message}|${exception:format=toString,StackTrace},這樣,就可以讓Log 訊息的格式為[日期時間]|TraceId|Log等級|Logger名稱|Log訊息|Exception訊息,這樣,就可以讓Log 訊息的格式更清晰易讀。

在[rules] 區段內,這裡設定了兩個Logger,一個是System.*,另一個是Microsoft.*,這樣,就可以讓這兩個Logger 的Log 訊息等級最低為Warn,這樣,就可以避免這兩個Logger 的Log訊息過多,而導致系統的效能下降。對於Microsoft.Hosting.Lifetime*這個Logger,這裡設定了Log 訊息的等級最低為Info,這樣,就可以讓這個Logger 的Log 訊息等級最低為Info,這樣,就可以讓這個Logger 的Log 訊息在Console 上顯示,這樣,就可以讓開發者在開發時,可以看到這個Logger 的Log 訊息,這樣,就可以讓開發者更容易了解這個Logger 的Log 訊息。對於*這個Logger,這裡設定了Log 訊息的等級最低為Info,這樣,就可以讓這個Logger 的Log 訊息等級最低為Info,這樣,就可以讓這個Logger 的Log 訊息在Console 上顯示,這樣,就可以讓開發者在開發時,可以看到這個Logger 的Log 訊息,這樣,就可以讓開發者更容易了解這個Logger 的Log 訊息。

建立會拋出例外異常的控制器

  • 在[Controllers] 資料夾內,建立一個新的C# 類別檔案,並命名為ExceptionController.cs
  • 使用底下的代碼替換掉這個檔案的內容
using Microsoft.AspNetCore.Mvc;
using NLog;

namespace csNlogTraceId.Controllers;

[ApiController]
[Route("[controller]")]
public class ThrowExceptionController : ControllerBase
{
    private readonly ILogger<ThrowExceptionController> _logger;

    public ThrowExceptionController(ILogger<ThrowExceptionController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        _logger.LogInformation("開始執行測試例外異常 API");

        throw new Exception("這是一個測試例外異常");
    }
}

啟動並執行這個項目

  • 在Visual Studio 2022 IDE 中,按下F5鍵,或是在功能表中選擇[調試] -> [開始調試],來執行這個程序

當項目啟動之後,並沒有看到任何瀏覽器出現

這裡是項目一啟動之後,Console顯示的Log 內容

開啟瀏覽器輸入https://localhost:7014/WeatherForecast這個網址,這時,將會看到瀏覽器顯示天氣預報資訊的消息

此時,在Console 中,將會看到底下內容,顯示的呼叫這個API 時候,所寫入的日誌內容

開啟瀏覽器輸入https://localhost:7014/ThrowException這個網址,這時,這個API 將會拋出例外異常到Log 內

此時,在Console 中,將會看到底下內容,顯示的呼叫這個API 時候,所寫入的例外異常日誌內容

 




沒有留言:

張貼留言