2012-07-13 239 views
6

我使用AdoNetAppender進行數據庫日誌記錄。我想要做的是在每個日誌語句上記錄用戶身份。但是,我不想使用標準log4net%標識參數,原因有兩個:使用自定義參數log4net數據庫日誌記錄

  1. log4net警告說,它非常慢,因爲它必須查找上下文標識。
  2. 在一些服務組件中,標準標識是服務帳戶,但我們已經在變量中捕獲了用戶標識,我希望使用它。

我看過代碼,其中一些人使用log4net.ThreadContext來添加額外的屬性,但我知道這是'不安全的'由於線程交錯(它也是一個性能流失)。

我的方法是延長因此AdoNetAppenderParameter類:

public class UserAdoNetAppenderParameter : AdoNetAppenderParameter 
{ 

    public UserAdoNetAppenderParameter() 
    { 
     DbType = DbType.String; 
     PatternLayout layout = new PatternLayout(); 
     Layout2RawLayoutAdapter converter = new Layout2RawLayoutAdapter(layout); 
     Layout = converter; 
     ParameterName = "@username"; 
     Size = 255; 
    } 


    public override void Prepare(IDbCommand command) 
    {    
     command.Parameters.Add(this); 
    } 


    public override void FormatValue(IDbCommand command, LoggingEvent loggingEvent) 
    {    
     string[] data = loggingEvent.RenderedMessage.Split('~'); 
     string username = data[0]; 
     command.Parameters["@username"] = username; 
    } 

} 

,然後以編程內容添加到當前的appender像這樣:

ILog myLog = LogManager.GetLogger("ConnectionService"); 
IAppender[] appenders = myLog.Logger.Repository.GetAppenders(); 
AdoNetAppender appender = (AdoNetAppender)appenders[0];      

appender.AddParameter(new UserAdoNetAppenderParameter()); 

myLog.InfoFormat("{0}~{1}~{2}~{3}", userName, "ClassName", "Class Method", "Message"); 

這樣做的目的是使用標準格式用於消息並解析字符串的第一部分,該部分始終應該是用戶名。自定義appender參數的FormatValue()方法應該只使用該部分字符串,以便可以將其寫入日誌數據庫中的單獨字段。

我的問題是沒有日誌語句寫入數據庫。奇怪的是,當調試時,FormatValue()方法中的斷點僅在我停止服務時纔會被觸發。

我已經瀏覽了大量與此相關的內容,但還沒有找到任何答案。 有沒有人設法做到這一點,或者我在錯誤的軌跡上。 P.S.我也嘗試過擴展AdoNetAppender,但它不允許你設置參數值。

回答

4

經過一番實驗,我終於得到了這個工作。確保log4net的內部日誌記錄有助於識別錯誤並下載log4net源代碼並查看AdoNetAppenderParameter類,這顯示了應該如何使用FormatValue()方法。所以,這裏的修改後的自定義appender的參數:

public class UserAdoNetAppenderParameter : AdoNetAppenderParameter 
{   

    public override void FormatValue(IDbCommand command, LoggingEvent loggingEvent) 
    {    
     string[] data = loggingEvent.RenderedMessage.Split('~'); 
     string username = string.Empty; 
     if (data != null && data.Length >= 1) 
      username = data[0]; 

     // Lookup the parameter 
     IDbDataParameter param = (IDbDataParameter)command.Parameters[ParameterName]; 

     // Format the value 
     object formattedValue = username; 

     // If the value is null then convert to a DBNull 
     if (formattedValue == null) 
     { 
      formattedValue = DBNull.Value; 
     } 

     param.Value = formattedValue; 
    } 

} 

,並利用這一點,我將其添加在log4net的配置文件是這樣的:

<parameter type="MyAssembly.Logging.UserAdoNetAppenderParameter, MyAssembly"> 
<parameterName value="@username" /> 
<dbType value="String" /> 
<size value="255" /> 
<layout type="log4net.Layout.PatternLayout" value="%message" /> 
</parameter> 

按照慣例,我的日誌報表將會像這個:

if (log.IsDebugEnabled) 
    log.DebugFormat("{0}~{1}~{2}", username, someOtherParameter, message); 

如果你看看類,它使用數據[0]作爲用戶名,所以它依賴於遵循約定。但是,它會將用戶名放入其自己的參數中,並將其放入日誌數據庫表中的單獨字段中,而不會將其暫時填充到不安全的ThreadContext中。

2

是的,線程敏捷性意味着您可能無法獲得正確的數據。對於log4net,您需要將其粘貼在HttpContext's Items collection中。

麻煩的是,你需要做一些工作,以便在需要將這些值寫入數據庫的時候將其恢復出來,因爲我一直使用Marek's Adaptive Property Provider class來爲我做這些咕嚕的工作。這是超級簡單的使用它,因爲所有你需要做的是以下幾點:

log4net.ThreadContext.Properties["UserName"] = AdaptivePropertyProvider.Create("UserName", Thread.CurrentPrincipal.Identity.Name); 

自適應特性會知道適當的地方檢索值時log4net的請求它。

方案選擇

如果你不堅持log4net的,NLog使得日誌對於ASP.NET網站的方式更簡單,因爲他們本身支持ASP.NET應用程序。使用甚至配置與log4net幾乎相同!

+1

我很喜歡log4net,我有窗口雖然我們將用戶名作爲字符串傳遞,但解決方案中沒有訪問HttpContext的服務組件。我不想設置ThreadContext屬性,每次需要記錄日誌時記錄並取消設置。我希望擴展AdoNetAppenderParameter會照顧它。感謝您的建議。 – 2012-07-15 11:46:05

5

我還需要登錄結構化數據和喜歡使用日誌接口是這樣的:

log.Debug(new { 
    SomeProperty: "some value", 
    OtherProperty: 123 
}) 

所以我也寫了自定義的AdoNetAppenderParameter類做的工作:

public class CustomAdoNetAppenderParameter : AdoNetAppenderParameter 
{ 
    public override void FormatValue(IDbCommand command, LoggingEvent loggingEvent) 
    { 
     // Try to get property value 
     object propertyValue = null; 
     var propertyName = ParameterName.Replace("@", ""); 

     var messageObject = loggingEvent.MessageObject; 
     if (messageObject != null) 
     { 
      var property = messageObject.GetType().GetProperty(propertyName); 
      if (property != null) 
      { 
       propertyValue = property.GetValue(messageObject, null); 
      } 
     } 

     // Insert property value (or db null) into parameter 
     var dataParameter = (IDbDataParameter)command.Parameters[ParameterName]; 
     dataParameter.Value = propertyValue ?? DBNull.Value; 
    } 
} 

現在log4net的配置可以被用來記錄給定對象的任何屬性:

<?xml version="1.0" encoding="utf-8"?> 
<log4net> 
    <appender name="MyAdoNetAppender" type="log4net.Appender.AdoNetAppender"> 
     <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> 
     <connectionString value="... your connection string ..." /> 
     <commandText value="INSERT INTO mylog ([level],[someProperty]) VALUES (@log_level,@SomeProperty)" /> 

     <parameter> 
      <parameterName value="@log_level" /> 
      <dbType value="String" /> 
      <size value="50" /> 
      <layout type="log4net.Layout.PatternLayout"> 
       <conversionPattern value="%level" /> 
      </layout> 
     </parameter> 

     <parameter type="yourNamespace.CustomAdoNetAppenderParameter, yourAssemblyName"> 
      <parameterName value="@SomeProperty" /> 
      <dbType value="String" /> 
      <size value="255" /> 
     </parameter> 
    </appender> 

    <root> 
     <level value="DEBUG" /> 
     <appender-ref ref="MyAdoNetAppender" /> 
    </root> 
</log4net>