網頁

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.

 

以上

沒有留言:

張貼留言