使用NLog把日誌寫入數據庫並按天自動分表

前言

最近用Asp.net Core開發程序的時候
因爲時間的關係,就沒有過多的去關注日誌方面的功能
都是直接用系統的ILogger先記錄着,然後看日誌的時候就先在命令行看日誌
在開發階段沒有什麼問題,但是到了系統上線後
總不能一直在命令行看日誌。總要把日誌輸出到一個方便查看的地方

開始

直接引用NLog.Web.AspNetCore組件
然後編寫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"
      internalLogFile="${basedir}\..\Log\${date:format=yyyyMM}\WMSAPI-internal.txt">
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>
  <!-- 定義變量當前應用程序名稱 -->
  <variable name="AppName" value="WMSAPI" />
  <!-- 日誌輸出目標 -->
  <targets>
    <!-- 把日誌記錄到文件(通用) -->
    <target xsi:type="File" name="allfile" fileName="${basedir}\..\Log\${date:format=yyyyMM}\${var:AppName}-all-${shortdate}.txt" encoding="UTF-8"
            archiveFileName="${basedir}\..\Log\${date:format=yyyyMM}\${var:AppName}-all-${shortdate}.{#}.txt" archiveAboveSize="10485760"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />

    <!-- 把日誌輸出到文件 (Asp.Net Core) -->
    <target xsi:type="File" name="ownFile-web" fileName="${basedir}\..\Log\${date:format=yyyyMM}\${var:AppName}-own-${shortdate}.txt" encoding="UTF-8"
            archiveFileName="${basedir}\..\Log\${date:format=yyyyMM}\${var:AppName}-own-${shortdate}.{#}.txt" archiveAboveSize="10485760"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}| body: ${aspnet-request-posted-body}" />

    <!--把日誌輸出到控制檯 -->
    <target xsi:type="Console" name="lifetimeConsole" layout="${level:truncate=4:tolower=true}: ${logger}[0]${newline}      ${message}${exception:format=tostring}" />

    <!--把日誌輸出到數據庫 -->
    <target xsi:type="Database" name="database" dbProvider="MySqlConnector.MySqlConnection, MySqlConnector">
      <connectionString>${configsetting:item=ConnectionStrings.GDbContext}</connectionString>
      <install-command ignoreFailures="true">
        <text>
          <!-- NOTE: call LogManager.Configuration.Install(new InstallationContext()); to execute this query. -->
          CREATE TABLE IF NOT EXISTS `Sys_Log${date:format=yyyyMMdd}`  (
          `Id` bigint NOT NULL AUTO_INCREMENT,
          `CreateTime` datetime NOT NULL,
          `AppName` varchar(50) NOT NULL,
          `Level` varchar(50) NOT NULL,
          `Logger` varchar(1024) NULL DEFAULT NULL,
          `Msg` text NULL,
          `Exception` text NULL,
          `UserId` varchar(50) NULL DEFAULT NULL,
          `Url` varchar(1024) NULL DEFAULT NULL,
          `IP` varchar(255) NULL DEFAULT NULL,
          PRIMARY KEY (`Id`) USING BTREE
          );
        </text>
      </install-command>
      <commandText>
        INSERT INTO `Sys_Log${date:format=yyyyMMdd}`(`CreateTime`, `AppName`, `Level`, `Logger`, `Msg`, `Exception`, `UserId`, `Url`, `IP`) VALUES (@CreateTime, @AppName, @Level, @Logger, @Msg, @Exception, @UserId, @Url, @IP);
      </commandText>
      <parameter name="@CreateTime" layout="${longdate}" />
      <parameter name="@AppName" layout="${var:AppName}" />
      <parameter name="@Level" layout="${level}" />
      <parameter name="@Logger" layout="${logger}" allowDbNull="true" />
      <parameter name="@Msg" layout="${message}" allowDbNull="true" />
      <parameter name="@Exception" layout="${exception:format=tostring}" allowDbNull="true" />
      <parameter name="@UserId" layout="${aspnet-user-claim:userId}" allowDbNull="true" />
      <parameter name="@Url" layout="${aspnet-request-url}" allowDbNull="true" />
      <parameter name="@IP" layout="${aspnet-request-ip}" allowDbNull="true" />
    </target>
  </targets>

  <!-- 日誌輸出規則 -->
  <rules>
    <!--All logs, including from Microsoft-->
    <!--<logger name="*" minlevel="Trace" writeTo="allfile" />-->

    <!--Output hosting lifetime messages to console target for faster startup detection -->
    <logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web, database" final="true" />
    <logger name="Microsoft.EntityFrameworkCore.Model.Validation" maxlevel="Error" final="true" />

    <!--Skip non-critical Microsoft logs and so log only own logs (BlackHole) -->
    <logger name="Microsoft.*" maxlevel="Info" final="true" />
    <logger name="System.Net.Http.*" maxlevel="Info" final="true" />

    <logger name="*" minlevel="Trace" writeTo="ownFile-web, database" />
  </rules>
</nlog>

可以看到我們定義了4個輸出目標,前2個是文件,一個是控制檯,一個是數據庫

輸出到文件基本定義

fileName:輸出的文件名
archiveFileName,archiveAboveSize這兩個參數是當文件超過archiveAboveSize大小的時候
就對文件進行分割,然後分割的文件名是用archiveFileName來定義
layout就是日誌文件內容,其中以${}閉合的內容就是NLog提供的參數
具體可以參考
https://nlog-project.org/config/?tab=layout-renderers

<target xsi:type="File" name="ownFile-web" fileName="${basedir}\..\Log\${date:format=yyyyMM}\${var:AppName}-own-${shortdate}.txt" encoding="UTF-8"
            archiveFileName="${basedir}\..\Log\WMSAPI-own-${shortdate}.{#}.txt" archiveAboveSize="10485760"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}| body: ${aspnet-request-posted-body}" />

輸出到數據庫基本定義

dbProvider:使用數據庫組件
connectionString:連接字符串
install-command:安裝腳本(用這個來自動創建表)
commandText:日誌插入到數據表的腳本
parameter:插入腳本的參數

    <target xsi:type="Database" name="database" dbProvider="MySqlConnector.MySqlConnection, MySqlConnector">
      <connectionString>${configsetting:item=ConnectionStrings.GDbContext}</connectionString>
      <install-command ignoreFailures="true">
        <text>
          <!-- NOTE: call LogManager.Configuration.Install(new InstallationContext()); to execute this query. -->
          CREATE TABLE IF NOT EXISTS `Sys_Log${date:format=yyyyMMdd}`  (
          `Id` bigint NOT NULL AUTO_INCREMENT,
          `CreateTime` datetime NOT NULL,
          `AppName` varchar(50) NOT NULL,
          `Level` varchar(50) NOT NULL,
          `Logger` varchar(1024) NULL DEFAULT NULL,
          `Msg` text NULL,
          `Exception` text NULL,
          `UserId` varchar(50) NULL DEFAULT NULL,
          `Url` varchar(1024) NULL DEFAULT NULL,
          `IP` varchar(255) NULL DEFAULT NULL,
          PRIMARY KEY (`Id`) USING BTREE
          );
        </text>
      </install-command>
      <commandText>
        INSERT INTO `Sys_Log${date:format=yyyyMMdd}`(`CreateTime`, `AppName`, `Level`, `Logger`, `Msg`, `Exception`, `UserId`, `Url`, `IP`) VALUES (@CreateTime, @AppName, @Level, @Logger, @Msg, @Exception, @UserId, @Url, @IP);
      </commandText>
      <parameter name="@CreateTime" layout="${longdate}" />
      <parameter name="@AppName" layout="${var:AppName}" />
      <parameter name="@Level" layout="${level}" />
      <parameter name="@Logger" layout="${logger}" allowDbNull="true" />
      <parameter name="@Msg" layout="${message}" allowDbNull="true" />
      <parameter name="@Exception" layout="${exception:format=tostring}" allowDbNull="true" />
      <parameter name="@UserId" layout="${aspnet-user-claim:userId}" allowDbNull="true" />
      <parameter name="@Url" layout="${aspnet-request-url}" allowDbNull="true" />
      <parameter name="@IP" layout="${aspnet-request-ip}" allowDbNull="true" />
    </target>

可以看到我們這裏通過install-command編寫的建表SQL腳本
表名是Sys_Log${date:format=yyyyMMdd},這樣我們創建出來的表名就是Sys_Log20211103(根據日間格式化)
但是NLog不會自動幫我們運行這個建表腳本,要我們在代碼裏調用
LogManager.Configuration.Install(new InstallationContext());
這個方法,他纔會運行install-command裏面的腳本。

因爲我們是按天來進行分表的,那就相當於我每天要運行一次
LogManager.Configuration.Install(new InstallationContext());

所以我就寫了一個定時的HostedService。來每天自動運行NLog的Install方法

    public class LogHostedService : IHostedService, IAsyncDisposable
    {
        private Timer RunTimer { get; set; }
        public Task StartAsync(CancellationToken cancellationToken)
        {
            LogManager.Configuration.Install(new NLog.Config.InstallationContext());//啓動後執行一次
            this.RunTimer = new Timer(LogInstall, null, DateTime.Now.AddDays(1).Date - DateTime.Now, TimeSpan.FromDays(1));
            return Task.CompletedTask;
        }
        public void LogInstall(object state)
        {
            LogManager.Configuration.Install(new NLog.Config.InstallationContext());//每天0點執行一次
        }
        public Task StopAsync(CancellationToken cancellationToken)
        {
            this.RunTimer?.Change(Timeout.Infinite, 0);
            return Task.CompletedTask;
        }

        public ValueTask DisposeAsync()
        {
            return this.RunTimer.DisposeAsync();
        }
    }

這樣就會在系統啓動時和每天的0點的時候,創建當天的日誌表
然後我們的插入語句INSERT INTO Sys_Log${date:format=yyyyMMdd}
就會自動插入到每天的日誌表裏面

代碼啓用NLog

我們在Program.cs文件裏使用UseNLog()啓用NLog組件

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                .UseNLog();

然後在Startup.cs裏啓用HostedService來定時創建每天的日誌表

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHostedService<LogHostedService>();//每天自動創建日誌表
        }

成果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章