2011年10月17日 星期一

使用NLog - Advanced .NET Logging (4)

 

擴展自訂的Layout Renderer來記錄所有的Request.ServerVariable變數資料

雖然NLog提供的Layout Renderers已經相當多種了,

而且如果要記錄ASP.NET Request ServerVariable的資料,可以使用:${aspnet-request : serverVariable=String}

但是Request.ServerVariable有相當多種,除非你相當有耐心,不然一個一個變數去加入NLog.Config中也是一件累人的事情,

所以如果可以像ELMAH一般,預設就是把所有的Request.ServerVariable給記錄下來,

image

我們可以擴展自定義的Layout Renderer來記錄這所有的Request.ServerVariable資料。

 

如何擴展自訂Layout Renderer

首先這邊我介紹一篇教學文章,這篇文章是教我們如何去設計自己的Layout Renderer,

Dflying Chen @ cnblogs - NLog文章系列——如何寫自定義佈局生成器(Layout Renderer)

相信瀏覽過這篇文章之後,應該對擴展自訂的Layout Renderer就有了初步的概念。

 

設計${web_variables}

而我們的需求是要去記錄所有的Request.ServerVariable,而因為變數資料有相當多,最好的方式就是將資料以XML文件格式給記錄起來,

這樣日後追查資料的內容就可以很方便的查看資料內容了,

於是在網路上找到了另一篇文章,說明如何設計一個Layout Renderer去記錄Request.ServerVariable並存成XML格式,

Darren’s Blog - Logging in MVC Part 3 – NLog

WebVariablesRender.cs的程式碼如下:

  1: [LayoutRenderer("web_variables")]
  2:  public class WebVariablesRenderer : LayoutRenderer
  3:  {
  4: 
  5:  ///
  6:  /// Initializes a new instance of the  class.
  7:  ///
  8:  public WebVariablesRenderer()
  9:  {
 10:  this.Format = "";
 11:  this.Culture = CultureInfo.InvariantCulture;
 12:  }
 13: 
 14:  protected override int GetEstimatedBufferSize(LogEventInfo ev)
 15:  {
 16:  // This will be XML of an unknown size
 17:  return 10000;
 18:  }
 19: 
 20:  ///
 21:  /// Gets or sets the culture used for rendering.
 22:  ///
 23:  ///
 24:  public CultureInfo Culture { get; set; }
 25: 
 26:  ///
 27:  /// Gets or sets the date format. Can be any argument accepted by DateTime.ToString(format).
 28:  ///
 29:  ///
 30:  [DefaultParameter]
 31:  public string Format { get; set; }
 32: 
 33:  ///
 34:  /// Renders the current date and appends it to the specified .
 35:  ///
 36:  /// <param name="builder">The  to append the rendered data to.
 37:  /// <param name="logEvent">Logging event.
 38:  protected override void Append(StringBuilder builder, LogEventInfo logEvent)
 39:  {
 40:  StringBuilder sb = new StringBuilder();
 41:  XmlWriter writer = XmlWriter.Create(sb);
 42: 
 43:  writer.WriteStartElement("error");
 44: 
 45:  // -----------------------------------------
 46:  // Server Variables
 47:  // -----------------------------------------
 48:  writer.WriteStartElement("serverVariables");
 49: 
 50:  foreach (string key in HttpContext.Current.Request.ServerVariables.AllKeys)
 51:  {
 52:  writer.WriteStartElement("item");
 53:  writer.WriteAttributeString("name", key);
 54: 
 55:  writer.WriteStartElement("value");
 56:  writer.WriteAttributeString("string", HttpContext.Current.Request.ServerVariables[key].ToString());
 57:  writer.WriteEndElement();
 58: 
 59:  writer.WriteEndElement();
 60:  }
 61: 
 62:  writer.WriteEndElement();
 63: 
 64:  // -----------------------------------------
 65:  // Cookies
 66:  // -----------------------------------------
 67:  writer.WriteStartElement("cookies");
 68: 
 69:  foreach (string key in HttpContext.Current.Request.Cookies.AllKeys)
 70:  {
 71:  writer.WriteStartElement("item");
 72:  writer.WriteAttributeString("name", key);
 73: 
 74:  writer.WriteStartElement("value");
 75:  writer.WriteAttributeString("string", HttpContext.Current.Request.Cookies[key].Value.ToString());
 76:  writer.WriteEndElement();
 77: 
 78:  writer.WriteEndElement();
 79:  }
 80: 
 81:  writer.WriteEndElement();
 82:  // -----------------------------------------
 83: 
 84:  writer.WriteEndElement();
 85:  // -----------------------------------------
 86: 
 87:  writer.Flush();
 88:  writer.Close();
 89: 
 90:  string xml = sb.ToString();
 91: 
 92:  builder.Append(xml);
 93:  }
 94: 
 95:  }

因為我們的NLog版本已經使用到2.0,所以在上面的程式中Line:14 ~ 18 的部份,原本需要覆寫GetEstimatedBufferSize()的地方,

NLog 2.0已經不需要去override了,原本GetEstimatedBufferSize()這個方法是用來設定Layout Renderer的緩衝區數,

就是看你所記錄的資料大小,於建立的時候先給一個估計的量,

NLog Logging Library - LayoutRenderer.GetEstimatedBufferSize方法

目前不需要去覆寫GetEstimatedBufferSize()方法

image

以下則是整理過後的程式碼:

  [LayoutRenderer("web_variables")]
  public class WebVariablesRenderer : LayoutRenderer
  {
    /// <summary>
    /// Initializes a new instance of the <see cref="WebVariablesRenderer"/> class.
    /// </summary>
    public WebVariablesRenderer()
    {
      this.Format = string.Empty;
      this.Culture = CultureInfo.InvariantCulture;
    }
    /// <summary>
    /// Gets or sets the culture used for rendering..
    /// </summary>
    /// <value>
    /// The culture.
    /// </value>
    public CultureInfo Culture { get; set; }
    /// <summary>
    /// Gets or sets the date format. Can be any argument accepted by DateTime.ToString(format).
    /// </summary>
    /// <value>
    /// The format.
    /// </value>
    [DefaultParameter]
    public string Format { get; set; }
    /// <summary>
    /// Renders the current date and appends it to the specified .
    /// </summary>summary>
    /// <param name="builder">The  to append the rendered data to.
    /// <param name="logEvent">Logging event.
    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
      StringBuilder sb = new StringBuilder();
      XmlWriter writer = XmlWriter.Create(sb);
      writer.WriteStartElement("error");
      // -----------------------------------------
      // Server Variables
      // -----------------------------------------
      writer.WriteStartElement("serverVariables");
      foreach (string key in HttpContext.Current.Request.ServerVariables.AllKeys)
      {
        writer.WriteStartElement("item");
        writer.WriteAttributeString("name", key);
        writer.WriteStartElement("value");
        writer.WriteAttributeString("string", HttpContext.Current.Request.ServerVariables[key].ToString());
        writer.WriteEndElement();
        writer.WriteEndElement();
      }
      writer.WriteEndElement();
      // -----------------------------------------
      // Cookies
      // -----------------------------------------
      writer.WriteStartElement("cookies");
      foreach (string key in HttpContext.Current.Request.Cookies.AllKeys)
      {
        writer.WriteStartElement("item");
        writer.WriteAttributeString("name", key);
        writer.WriteStartElement("value");
        writer.WriteAttributeString("string", HttpContext.Current.Request.Cookies[key].Value.ToString());
        writer.WriteEndElement();
        writer.WriteEndElement();
      }
      writer.WriteEndElement();
      // -----------------------------------------
      writer.WriteEndElement();
      // -----------------------------------------
      writer.Flush();
      writer.Close();
      string xml = sb.ToString();
      builder.Append(xml);
    }
  }

 

加入Global.asax中

在「Darren’s Blog - Logging in MVC Part 3 – NLog」中也有說到,

如何在Global.asax的Application_Start()方法中去註冊我們所擴展的自訂Layout Renderer方法,

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ControllerBuilder.Current.SetControllerFactory(new ErrorHandlingControllerFactory());
    // Register custom NLog Layout renderers
    LayoutRendererFactory.AddLayoutRenderer("utc_date", typeof(MySampleApp.Services.Logging.NLog.UtcDateRenderer));
    LayoutRendererFactory.AddLayoutRenderer("web_variables", typeof(MySampleApp.Services.Logging.NLog.WebVariablesRenderer));
 }

但使用 NLog 2.0 會遇到一個問題,那就是 LayoutRendererFactory 這個類別不見了,而官方文件中也不知道哪裡有說明替代的方案為何?

最後是在 NLog Fourm 裡面找到一篇討論「NLog Fourm - NLogFactories not found」,NLog作者的回答如下:

image

所以我就改成了以下的方式:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
    ConfigurationItemFactory.Default.LayoutRenderers.RegisterDefinition("web_variables", typeof(WebVariablesRenderer));
}

 

一般的文字檔要儲存XML文件也是可以,但是我會建議你要使用 ${web_variables} ,Target 建議使用Database,在日後的維護與查詢Log資料時會比較方便。

而存在資料庫的話,欄位名稱你可以自訂,而資料類別我則是使用「nvarchar(MAX)」,

有些人會在SQL Server中存放大量文字資料會使用ntext的資料類型,但是未來的MS SQL Server版本將會移除這個資料類型,

所以就改用「nvarchar(MAX)」來存放我們所記錄到的Request.ServerVariable XML資料。

參考連結:MSDN - ntext、text 和 image (Transact-SQL)

 

NLog.Config的修改

看看Database Target的修改情況:

  <target name="database" type="Database">
    <dbprovider>mssql</dbprovider>
    <connectionString>Data Source=localhost;Initial Catalog=NLogDatabase;Persist Security Info=True;User ID=nloguser;Password=nlogpassword;MultipleActiveResultSets=True;</connectionString>
    <commandText>
      insert into NLog (time_stamp, level, host, url, type, source, logger, message, stacktrace, Detail, allxml) Values(@time_stamp, @level, @host, @url, @type, @source, @logger, @message, @stacktrace, @detail, @allxml);
    </commandText>
    <parameter name="@time_stamp" layout="${date}" />
    <parameter name="@level" layout="${level}" />
    <parameter name="@host" layout="${machinename}" />
    <parameter name="@url" layout="${aspnet-request:serverVariable=url}" />
    <parameter name="@type" layout="${exception:format=type}" />
    <parameter name="@source" layout="${callsite:className=true}" />
    <parameter name="@logger" layout="${logger}" />
    <parameter name="@message" layout="${message}" />
    <parameter name="@stacktrace" layout="${exception:stacktrace}" />
    <parameter name="@detail" layout="${exception:format=tostring}" />
    <parameter name="@allxml" layout="${web_variables}" />
    
  </target>

看起來沒有什麼變化,除了說我增加一個parameter其名稱為「@allxml」,而${web_variables}資料將會存放到欄位[allxml]中。

 

實際執行網站後,來看看資料庫的紀錄結果,

image

可以看到Request.ServerVariables資料都已經存成XML的格式了,

為了檢查是否是記錄完整,所以將allxml的資料複製起來後貼到XML Editor來看看,

image

 

所以在這篇文章中介紹了如何擴展自訂的Layout Renderer,並且因應NLog 2.0的改變而作一些必要的修改,

並且參考網路上的既有範例,增加了 ${web_variables} 的Layout Renderer來記錄Request.ServerVariables,

以方便日後的偵錯與資料追蹤。

 

參考連結:

Dflying Chen @ cnblogs - NLog文章系列——如何寫自定義佈局生成器(Layout Renderer)

Darren’s Blog - Logging in MVC Part 3 – NLog

NLog Fourm - NLogFactories not found :NLog 2.0 要改使用ConfigurationItemFactory而非LayoutRendererFactory.

 

以上

沒有留言:

張貼留言

提醒

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