2011年10月13日 星期四

使用NLog - Advanced .NET Logging (1)


介紹如何在網站專案中使用NLog的基本安裝以及設定

在前面的幾篇文章已經介紹過如何在網站中使用 ELMAH 來記錄與追蹤各種的訊息與錯誤,ELMAH 已經可以達到Logging 的功能要求,但是如果需要對各種記錄去做分級處理的話,使用 ELMAH 就不是那麼方便,一般而言,網站中最好除了使用 ELMAH 外,最好再使用另一套 Logging 的套件,ELMAH 是用來作為日誌記錄,主要工作是捕捉 UnHandler Exception(未處理的異常),所以有處理的(try…catch..)異常就無法捕捉。

NLog就是讓我們可以在程式裡去處理異常時,給予不同的等級後,NLog再依據使用者所定義各等級的不同處理方式做後續的操作,看是要Email通知還是記錄在系統的Event Log還是多種方式傳送訊息等等,一且都可以讓使用者去依照需求而定義。

NLog的功能相當齊全,設定也相當簡單、容易上手,所以接下來將會介紹如何在網站專案中安裝與設定NLog。

 



NLog

官網:http://nlog-project.org/

官網Documentation:http://nlog-project.org/wiki/Documentation

CodePlex:http://nlog.codeplex.com/

github:https://github.com/jkowalski/NLog

NuGet Gallery:http://nuget.org/List/Packages/NLog

PM> Install-Package NLog



這邊還是一樣是介紹在 Visual Studio 2010 中使用 NuGet Packages Manger 安裝 NLog,以下的範例是以一個ASP.NET MVC的網站方案來說明,其中將Model層與共用層都切分出來為獨立的專案。

 

安裝

於VS2010開啟NuGet Packages Manager後,搜尋「NLog」的packages,我們先安裝三個NLog相關的Packages,

NLog

NLog Configuration

NLog Schema for IntelliSense(TM)

image

如果的你Solution中友多個專案時,「NLog」是每個專案都需要加入,而其他兩個packages則只需要安裝在網站專案就可以。

 

方案在安裝後的修改

首先可以看到網站專案的目錄下有增加了NLog.Config的設定檔案,

image

開啟內容:

image

雖然說 NLog.Config 的內容也是可以存放在 Web.Config 中,但是如果日後 NLog 的設定越來越多也越來越複雜的時候,Web.Config 就會顯得雜亂,而且 Web.Config 也一定還會有其他的設定資料,所以最好的方式還是將其獨立存放為 NLog.Config,如果是透過 NuGet 安裝 NLog Configuration 的話,預設就會另外建立 NLog.Config 檔案。

或者你可以先到NLog @ CodePlex去下載NLog 2.0 的安裝檔,

image

安裝的時候,安裝檔會安裝 Templates,

image

安裝完成後,重新開啟VS2010,在網站專案去新增項目,「加入新項目」的視窗中就可以看到NLog所加入的範本。

image

 

修改NLog.Config的內容

首先你需要先了解兩個部分,一個是「targets」另一個是「rules」,這兩個設定缺一不可,

targets,是用來讓我們定義Log要存放的媒體為何,定義Log的格式內容。

rules,用來定義各種Level的Log的處理方式,依據各種level所定義要使用哪一個taget來存放資料。

例如以下的設定:

  <!-- 
  See http://nlog-project.org/wiki/Configuration_file 
  for information on customizing logging rules and outputs.
   -->
  <targets>
    <!-- add your targets here -->    
    <!--
    <target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
            layout="${longdate} ${uppercase:${level}} ${message}" />
    -->
  <target name="file" xsi:type="File"
            fileName="${basedir}/App_Data/Logs/${shortdate}/${logger}.txt"
            layout="${longdate} | ${level:uppercase=true} | ${logger} | ${message} ${newline}" />
  <target name="FatalFile" xsi:type="File"
      fileName="${basedir}/App_Data/Logs/${shortdate}/FatalFile.txt"
      layout="${longdate} | ${level:uppercase=true} | ${logger} | ${message} ${newline}" />
  <target name="eventlog" xsi:type="EventLog" 
      source="Lab_Of_NLog" log="Application" 
      layout="${date}: ${message} ${stacktrace}" />
    
  </targets>
  <rules>
    <!-- add your logging rules here -->    
    <!--
    <logger name="*" minlevel="Trace" writeTo="f" />
    -->
    <!--
    <logger name="*" level="Trace" writeTo="file" />
    <logger name="*" level="Debug" writeTo="file" />
    <logger name="*" level="Info" writeTo="file" />
    <logger name="*" level="Warn" writeTo="file" />
    -->
    <!-- 上面的logger如果都使用一樣的target,也可以寫成這樣的方式 -->
    <logger name="*" levels="Trace, Debug, Info, Warn" writeTo="file" />
    
    <logger name="*" level="Fatal" writeTo="FatalFile" />
    <logger name="*" level="Fatal" writeTo="eventlog" />
  </rules>

 

<targets>

上面的targets設定裡定義了三個target,其中有兩個是把Log給寫入到位於「~/App_Data/Logs」目錄下,而每天會新增一個當日日期的目錄,
名稱為 file 的target,記錄的檔名是依據logger的名稱,資料則會依據自訂的layout格式來寫入文字檔中。

名稱為 FatalFile 的target,記錄的名稱為固定的FatalFile.txt,因為Fatal屬於使系統足以致命的錯誤,足以影響系統的運作,所以用一個固定的名稱,讓開發者或是維護人員在檢視log時可以馬上看到,資料則會依據自訂的layout格式來寫入文字檔中。

名稱為 eventlog 的target,這是要把Log給記錄到作業系統的Event Log(事件檢視器)中,

所以要設定好來源名稱(source)、記錄檔名稱(log = “Application”)以及lauoyt內容,如下圖,就是一個記錄在Evelt Log的訊息內容。

image

在<targets>中最需要了解的就是一堆$符號外加大括號{ … } 的變數,每個變數名稱都有其定義,不要自己隨便輸入,這部份就必須參考 NLog的Wiki Layout renderers,文件中對於每個變數名稱有詳細的說明,

例如:

  • ${basedir}:代表網站根目錄 (或應用程式所在目錄);The current application domain's base directory.
  • ${shortdate}:短日期格式,The short date in a sortable format yyyy-MM-dd.
  • ${longdate}:長日期格式,The date and time in a long, sortable format yyyy-MM-dd HH:mm:ss.mmm.
  • ${logger}:The logger name.記錄器名稱.
  • ${level:uppercase=true}:The log level,Log的等級,而中間uppercase=true就是把等級的文字轉為大寫.
  • ${message}:Log的訊息內容,The formatted log message.
  • ${newline}:新行符號,A newline literal.
  • ${stacktrace}:Stack trace renderer,如錯誤的堆疊訊息內容。

參考:

 

<rules>

則是依據NLog提供的六種不同等級來各自定義處理方式,六種等級的說明如下:

  1. Trace:用於追蹤,可以在程式裡需要追蹤的地方將訊息以Trace傳出。
  2. Debug:用於開發,於開發時將一些需要特別關注的訊息以Debug傳出。
  3. Info:訊息,記錄不影響系統執行的訊息,通常會記錄登入登出或是資料的建立刪除、傳輸等。
  4. Warn:警告,用於需要提示的訊息,例如庫存不足、貨物超賣、餘額即將不足等。
  5. Error:錯誤,記錄系統實行所發生的錯誤,例如資料庫錯誤、遠端連線錯誤、發生例外等。
  6. Fatal:致命,用來記錄會讓系統無法執行的錯誤,例如資料庫無法連線、重要資料損毀等。

程式中要小心謹慎Log等級,因為不同的Log等級可以有不同的處理方式,而一種Log等級的處理不限定只能使用一種,像上面的設定資料裡,Trace~Warn都是把Log存放到target:file中,而Fatal的Log則是另外使用target:FatalFile,而且會另外將Log給記錄到作業系統的Event Log中。

有關Rules的各種屬性說明如下:

  • name – source/logger name (may include wildcard characters *)
  • minlevel – minimal log level for this rule to match
  • maxlevel – maximum log level for this rule to match
  • level – single log level for this rule to match
  • levels - comma separated list of log levels for this rule to match
  • writeTo – comma separated list of target that should be written to when this rule matches.
  • final – make this rule final. No further rules are processed when any final rule matches.

參考:

 

在程式中使用NLog

上面的NLog設定完成後,接下來就是實際在程式中去使用,首先我們在要使用NLog的類別中先定義記錄器「Logger」,然後就可以在程式之中使用looger去記錄訊息了,例如:

  public class HomeController : Controller
  {
    private static Logger logger = NLog.LogManager.GetCurrentClassLogger();
    public ActionResult Index()
    {
      logger.Trace("我是Trace");
      logger.Debug("我是Debug");
      logger.Info("我是Info");
      logger.Warn("我是Warn");
      logger.Error("我是Error");
      logger.Fatal("我是Fatal");
      return View();
    }
    public ActionResult About()
    {
      try
      {
        int a = 6;
        int b = 0;
        int result = a / b;
      }
      catch (Exception ex)
      {
        logger.Fatal(ex);
      }
      
      return View();
    }
  }

修改好程式、重新建置專案後,先執行Home/Index,就會在存放log文字檔的目錄下產生記錄檔,會產生兩個文字記錄檔是因為上面的NLog.Config所設定的,Fatal等級的log會記錄到另一個「FatalFile.txt」檔案中,而其他等級的log則是記錄到logger的文字檔案中。

image

先來看看「Test.Web.Controllers.HomeController.txt」的內容:

只會記錄Trace ~ Error 等級的log。

image

再來看看「FatalFile.txt」的內容:

這裡就只會記錄Fatal等級的log。

image

 

接著繼續執行 Home/About 看看,因為這裡是實際去產生一個 Exception,而這裡的 log 是記錄為 Fatal 等級,FatalFile.txt 就把發生異常的資訊給記錄下來,包含異常類別、異常所發生的 Method、異常發生的程式碼行數。

image

 

如果說你認為這樣記錄異常的資訊太過於精簡的話,其實你可以自己去定義一個方法,然後這個方法的功能就是把異常的詳細資料給輸出為字串,然後再將這個異常詳細資料的字串傳給logger去記錄,我就參考了「Darren’s Blog - Logging in MVC Part 3 – NLog」其中的LogUtility.BuildExceptionMessage()方法,做出一個產出字串的方法:

  public class LogUtility
  {
    /// <summary>
    /// Builds the exception message.
    /// </summary>
    /// <param name="x">The x.</param>
    /// <returns></returns>
    public static string BuildExceptionMessage(Exception x)
    {
      Exception logException = x;
      if (x.InnerException != null)
      {
        logException = x.InnerException;
      }
      StringBuilder message = new StringBuilder();
      message.AppendLine();
      message.AppendLine("Error in Path : " + System.Web.HttpContext.Current.Request.Path);
      // Get the QueryString along with the Virtual Path
      message.AppendLine("Raw Url : " + System.Web.HttpContext.Current.Request.RawUrl);
      // Type of Exception
      message.AppendLine("Type of Exception : " + logException.GetType().Name);
      // Get the error message
      message.AppendLine("Message : " + logException.Message);
      // Source of the message
      message.AppendLine("Source : " + logException.Source);
      
      // Stack Trace of the error
      message.AppendLine("Stack Trace : " + logException.StackTrace);
      // Method where the error occurred
      message.AppendLine("TargetSite : " + logException.TargetSite);
      return message.ToString();
    }
  }

而實際的使用方式如下:

    public ActionResult About()
    {
      try
      {
        int a = 6;
        int b = 0;
        int result = a / b;
      }
      catch (Exception ex)
      {
        logger.Fatal(LogUtility.BuildExceptionMessage(ex));
      }
      
      return View();
    }

重新建置網站後接著執行Home/About,看看FatalFile.txt的紀錄:

image

StackTrace:取得呼叫堆疊上立即框架的字串表示。

TargetSite:取得擲回目前例外狀況的方法。

 

好的,NLog的基本安裝與設定方法就說明到這裡,如果還需要更加詳細的說明,請參考NLog的官方網站的Wiki

往後的幾篇會再說明NLog的進階使用。

 


參考連結:

NLog – Advanced .NET Logging

NLog – Documentation

NLog – Documentation:Configuration file

NLog @ CodePlex

NLog @ github

NLog @ NuGet Gallery

Will 保哥 - 介紹好用函式庫:NLog - Advanced .NET Logging

Darren’s Blog – Logging in MVC Part3 – NLog

 

以上

11 則留言:

  1. These are really wonderful ideas in about blogging. You have
    touched some good factors here. Any way keep up wrinting.
    Here is my page forum

    回覆刪除
  2. 作者已經移除這則留言。

    回覆刪除
  3. 請問一下 利用GMAIL發信可以有附件嗎?

    回覆刪除
    回覆
    1. 抱歉,對於這個功能就沒有研究過。

      刪除
  4. 請問一下,我在本機跑記錄LOG都正常,但發行到IIS之後,下面這個訊息反而無法完整記錄下來,可以告訴我應該要怎麼做嗎??
    message.AppendLine("Stack Trace : " + logException.StackTrace);

    回覆刪除
    回覆
    1. 這個我就不清楚你所發行主機的設定為何,一般來說是不會限制,因為我這邊無論是使用 Windows 2003 or 2008 or 2012 都沒有這樣的狀況出現。

      刪除
  5. 請問Kevin兄,
    MVC如何使用共用方法呢...
    LogUtility.cs應該要放在哪個目錄下?
    我把它放在Controllers目錄下,可是HomeControllers的About()裡加上logger.Fatal(LogUtility.BuildExceptionMessage(ex));
    它會跟我說「使用名稱'LogUtility'不存在目前內容中」
    這該如何是好??
    初學者向您請教~

    回覆刪除
    回覆
    1. Hello, 你好
      其實不適合放在 Controllers 目錄下,我會建議你在專案裡另外建立一個目錄,一般我會使用「Infrastructure」取其基礎建設或基礎設施的意思,然後在 Infrastructure 下再建立一個 Logging 資料夾,而 LogUtility.cs 就會建立在這個資料夾內。
      雖然 LogUtility.BuildExceptionMessage() 是公開的靜態方法,所以要在 Controller 裡去使用的話,記得要加入 Namespace,
      照理說如果將 LogUtility.cs 放在 Controllers 目錄下,雖然不適合,但是只要程式沒有問題的話,在 Controller 裡應該可以正常使用,而且不需要另外加入 Namespace,除非你的 BuildExceptionMessage() 沒有加上 static 的修飾詞,因為你給我的訊息不是很多,所以我只能這樣推論。

      刪除
    2. Hi Kevin兄,
      我採您的建議把LogUtility.cs建立在Infrastructure 下的Logging 資料夾中
      然後在LogUtility.cs加入您提供的BuildExceptionMessage()範例
      再到HomeControllers的About()裡加上logger.Fatal(LogUtility.BuildExceptionMessage(ex));
      它會跟我說「使用名稱'LogUtility'不存在目前內容中」
      這時候我照您的吩咐在HomeControllers加上正確Namespace
      結果「使用名稱'LogUtility'不存在目前內容中」就消失了!
      原來是我之前加入Namespace時Namespace格式不正確導致
      好像是這樣的格式 using ProjectName.ClassName 才對...(是吧??)
      不好意思,初學者才可能有的疏忽...
      感謝您的解惑喔~~~

      刪除
    3. 其實好好善用你所用來開發的 Visual Studio,基本的語法檢查、編譯檢查等,不論什麼版本都會有提供足夠的資訊給我們知道,在編輯程式時有錯誤或是沒有引用到或是錯誤使用 Namespace 時都會有提示說明,編譯檢查也是一樣,所以這些基本的 Visual Studio 的操作與認識是一定要好好地瞭解。

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。

最近的留言