[Package] 記錄檔利器NLog (Advanced .NET Logging)

[Package] 記錄檔利器NLog (Advanced .NET Logging)

前言


筆者剛開始工作時,只要遇到要寫Log時往往自行實作(練功),但大家都知道客戶的需求千奇百怪,不僅僅只想在文字檔上看到歷史的痕跡,更想在DB中記載所發生過的一切,更想在嚴重錯誤發生時windows事件中也能記下一筆,甚至需要發送E-Mail來招喚維護工程師的到來;想想看如果要實作這一切,我想正事都不用辦了吧! 好在市面上有不少工具可以使用,像是筆者最先接觸的log4net就很不錯,但我認為NLog在使用上比較人性化,所以就以此工具做個使用全紀錄,以便後續專案參考(複製貼上)之用。

 

簡易介紹


簡單來說就是透過NLog.config來設定一切資訊,包括Log寫入目標(文字檔、系統事件、資料庫...)與Log層級設定,使用者可自由搭配不同Log層級與寫入目標的關聯性,實現特定訊息特定記載目標的呈現方式。

 

事前準備


假設目前有2個專案,其中一個是Web網站,而另一個是共用函式庫專案;首先,因為我們將於此專案進行NLog的設定,所以需要在Web專案中透過NuGet取得NLog Configration(自動相依下載NLog)套件。

image

會產生NLog.config檔案,而後續將對此檔案進行設定

image

而共用函式庫專案專案就下載NLog套件即可,設定的部分將套用Web專案中的定義。

image

 

 

設定檔介紹


設定檔中一般我們會依序設定幾樣資訊,如下:

1. 文字樣板

    NLog提供了許多的資訊標籤(如: 事件等級、發出訊息所在類別名稱...等)供開發者依需求自行組裝

    https://github.com/nlog/NLog/wiki/Layout-Renderers

 

2. 寫入目標

    可以是文字檔、Windows事件、資料庫...等許多不同位置,當然目標不同所需的設定也不同。

    https://github.com/nlog/NLog/wiki/File-target

 

3. 紀錄規則

    自行搭配事件等級與寫入目標,如: Error等級寫入文字檔、Fatal等級寫入Windows事件...

 

 

設定檔範例


以下將就不同寫入目標之設定檔進行範例介紹

 

寫入目標: 文字檔(每日產生)

這是最基本的文字紀錄檔設定,只要在寫入目標中設定型態為檔案(File),並設定文字樣板及檔案路徑位置(需以時間為參數),NLog就會自動以時間為區隔產出相對應檔案,達到每日產出當日紀錄檔案之需求。

image


<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true">

  <!--[變數] 文字樣板 -->
  <variable name="Layout" value="${longdate} | ${level:uppercase=true} | ${logger} | ${message} ${newline}"/>
  <variable name="LayoutFatal" value="${longdate} | ${level:uppercase=true} | ${logger} | ${message} | ${exception:format=tostring} ${newline}"/>

  <!--[變數] 檔案位置 -->
  <variable name="LogTxtLocation" value="${basedir}/App_Data/Logs/${shortdate}/${logger}.txt"/>
  <variable name="LogTxtLocationFatal" value="${basedir}/App_Data/Logs/${shortdate}/FatalFile.txt"/>

  <!--[設定] 寫入目標-->
  <targets>
    <target name="File" xsi:type="File" fileName="${LogTxtLocation}" layout="${Layout}" />  
	<target name="FileFatal" xsi:type="File" fileName="${LogTxtLocationFatal}" layout="${LayoutFatal}"/>
  </targets>

  <!--[設定] 紀錄規則-->
  <rules>
    <logger name="*" levels="Trace,Debug,Info,Warn" writeTo="File" />
    <logger name="*" levels="Error,Fatal" writeTo="FileFatal" />
  </rules>
  
</nlog>

 

 

寫入目標: 文字檔(僅保留特定周期內的資料)

若對記錄檔的需求只需保留特定天數內資料時,NLog也可以幫你實現這個願望。記錄檔可依照archiveEvery參數定義/Logs/logfile.txt中資料範圍(Minute、Hour、Day、Month、Year),如下設定表示/Logs/logfile.txt中只會包含今日紀錄的資料,若時間來到明日,則會將/Logs/logfile.txt更名移動至/Logs/archives/log.0.txt;另外,此設定中的maxArchiveFiles定義檔案數量為7,表示在/Logs/archives/中只有7個檔案,亦表示最近7天內的資料才會被保留,而超出7天的資料就會被刪除,因此/Logs/archives/中僅有 log.0.txt(昨日), log.1.txt(前日), ... log.6.txt(6日前) 資料備查。

image


<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <!--[變數] 文字樣板 -->
  <variable name="Layout" value="${longdate} | ${level:uppercase=true} | ${logger} | ${message} ${onexception:${newline}${exception:format=tostring}}"/>

  <!--[設定] 紀錄位置-->
  <targets>
    <target name="TimeBasedFileArchival" xsi:type="File"
            layout="${Layout}"
            fileName="${basedir}/App_Data/Logs/logfile.txt"
            archiveFileName="${basedir}/App_Data/Logs/archives/log.{#}.txt"
            archiveEvery="Day"
            archiveNumbering="Rolling"
            maxArchiveFiles="7"
            concurrentWrites="true"
            keepFileOpen="false"
            encoding="UTF-8" />
  </targets>
  
  <!--[設定] 紀錄規則-->
  <rules>
    <logger name="*" levels="Trace,Debug,Info,Warn,Error,Fatal" writeTo="TimeBasedFileArchival" />
  </rules>

</nlog>

 

 

寫入目標: Windows事件集


<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true">

  <!--[變數] 文字樣板 -->
  <variable name="LayoutEvent" value="${date}: ${message} ${stacktrace}"/>

   <!--[設定] 寫入目標-->
  <targets>
    <target name="Event" xsi:type="EventLog" source="MY WEB" log="Application" layout="${LayoutEvent}" />
  </targets>

  <!--[設定] 紀錄規則-->
  <rules>
    <logger name="*" levels="Error,Fatal" writeTo="Event" />
  </rules>

</nlog>

 

 

寫入目標: 資料庫

將資料寫入DB需要幾項必要資訊,在設定完連線字串(名稱)及寫入資料的SQL語法,一切就搞定了。


<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true">

   <!--[設定] 寫入目標-->
  <targets>
    <target name="Db" xsi:type="Database"
            connectionStringName="connStrNameInWebConfig"
            commandText="INSERT INTO NLog_Error(ThreadId, MachineName, LogName, LogLevel, LogMessage, CallSite, Exception, Stacktrace) VALUES (@ThreadId, @MachineName, @LogName, @LogLevel, @LogMessage, @CallSite, @Exception, @Stacktrace);">
      <parameter name="@ThreadId" layout="${threadid}"/>
      <parameter name="@MachineName" layout="${machinename}"/>
      <parameter name="@LogName" layout="${logger}"/>
      <parameter name="@LogLevel" layout="${level}"/>
      <parameter name="@LogMessage" layout="${message}"/>
      <parameter name="@CallSite" layout="${callsite:filename=true}"/>
      <parameter name="@Exception" layout="${exception}"/>
      <parameter name="@stacktrace" layout="${stacktrace}"/>
    </target>
  </targets>

  <!--[設定] 紀錄規則-->
  <rules>
    <logger name="*" levels="Error,Fatal" writeTo="Db" />
  </rules>

</nlog>

DB中需要有對應的Log Table如下


DROP TABLE NLog_Error
GO

CREATE TABLE NLog_Error
(
 LogId   INT    NOT NULL IDENTITY(1,1) PRIMARY KEY,
 ThreadId  INT    NOT NULL,
 MachineName  VARCHAR(255) NOT NULL,
 LogName   VARCHAR(255) NOT NULL,
 LogLevel  VARCHAR(5)  NOT NULL,
 LogMessage  VARCHAR(MAX) NOT NULL,
 CallSite  VARCHAR(1024) NOT NULL,
 Exception  VARCHAR(1024) NOT NULL,
 Stacktrace  VARCHAR(1024) NOT NULL,
 CreateDateTime DATETIME NOT NULL DEFAULT(GETDATE())
)
GO

 

自行定義專屬 Layout Renderer


透過以上介紹可以發現輸出 Log 文字都是以 Layout Renderer 識別 [ ex. $(message) ],當然 Nlog 已經幫我們定義了許多好用的 Layout Renderer ,但總是會有需要建立自己特殊資料的時候,這時可以使用 Event Context Layout Renderer 來加入自定義欄位資訊;透過傳入 LogEventInfo 物件的 Properties 設定值來對應 config 中 ${event-properties:item=MyValue} 標籤,加入自行定義的資料至指定資料庫欄位or文字layout。

 

程式中紀錄LOG方式


使用方式相當地人性化,我們只需透過NLog提供的GetCurrentClassLogger方法取得Logger實體即可,再依照所需寫入的層級來呼叫相對應方法,另外如需記載Exception時可直接代入Exception實體。



class UserService 
{
	// Logger
	private static Logger logger = NLog.LogManager.GetCurrentClassLogger();

	public Result CreateUser(User user)
	{
		Result result = new Result(false);
		
		try
		{
			// log here
			logger.Trace("Trace");
			logger.Debug("Debug");
			logger.Info("Info");
			logger.Warn("Warn");
			logger.Error("Error");
			logger.Fatal("Fatal");
			
			int foo = 0;
			foo /= foo;
			
			result.Success = true;
		}
		catch (Exception ex)
		{
			result.Exception = ex;
			
			// log with exception here
			logger.Trace("Trace", ex);
			logger.Debug("Debug", ex);
			logger.Info("Info", ex);
			logger.Warn("Warn", ex);
			logger.Error("Error", ex);
			logger.Fatal("Fatal", ex);

		}
	
		return result;
	}
	
}

 

 

參考資訊


http://nlog-project.org/

http://blog.developer.idv.tw/2012/12/nlog_30.html


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !