2013-02-19 17 views
2

我正在嘗試向我的數據庫日誌目標添加一些定製。 在我NLog.config我有這樣的:NLog - 數據庫目標的運行時參數

<target name="DatabaseExample1" xsi:type="Database" 
dbProvider="System.Data.SqlClient" 
dbDatabase="${event-context:item=dbDatabase}" 
dbUserName="${event-context:item=dbUserName}" 
dbPassword="${event-context:item=dbPassword}" 
dbHost="${event-context:item=dbHost}" 
commandText="${event-context:item=commandText}"> 
</target> 

在我的C#代碼,我有這樣的:

protected override void updateBeforeLog(LogEventInfo info) 
{ 
    info.Properties["dbDatabase"] = "TempDB"; 
    info.Properties["dbUserName"] = "username"; 
    info.Properties["dbPassword"] = "password"; 
    info.Properties["dbHost"] = "SERVER\\SQLSERVER"; 
    info.Properties["commandText"] = "exec InsertLog @LogDate, @LogLevel, @Location, @Message"; 

    info.Parameters = new DatabaseParameterInfo[] { 
     new DatabaseParameterInfo("@LogDate", Layout.FromString("${date:format=yyyy\\-MM\\-dd HH\\:mm\\:ss.fff}")), 
     new DatabaseParameterInfo("@LogLevel", Layout.FromString("${level}")), 
     new DatabaseParameterInfo("@Location", Layout.FromString("${event-context:item=location}")), 
     new DatabaseParameterInfo("@Message", Layout.FromString("${event-context:item=shortmessage}")) 
    }; 

    log.Log(info); 
} 

但我發現了,上面寫着一個SQL錯誤「必須聲明標量變量」 @LogDate 「」。

屬性正在工作,因爲連接成功。但由於某些原因,這些參數與命令中的標量變量不是「綁定」的。

如果我在NLog.config文件手動創建的參數,它完美的作品:

<target name="DatabaseExample1" xsi:type="Database" 
dbProvider="System.Data.SqlClient" 
dbDatabase="${event-context:item=dbDatabase}" 
dbUserName="${event-context:item=dbUserName}" 
dbPassword="${event-context:item=dbPassword}" 
dbHost="${event-context:item=dbHost}" 
commandText="${event-context:item=commandText}"> 
    <parameter name="@LogDate" layout="${date:format=yyyy\-MM\-dd HH\:mm\:ss.fff}" /> 
    <parameter name="@LogLevel" layout="${level}" /> 
    <parameter name="@Location" layout="${event-context:item=location}" /> 
    <parameter name="@Message" layout="${event-context:item=shortmessage}" /> 
</target> 

但是,這違背了能夠自定義的CommandText和參數值在運行時的全部目的。

請幫我理解我需要做些什麼來使目標正確獲取參數的值。 謝謝!

更新

總的來說,我想有辦法通過C#代碼,自定義目標。我想依賴於NLog.config文件只需要很多。但看起來NLog與配置文件設置很相關,我試圖在該約束內工作,但要儘可能靈活。有了這個數據庫目標,如果我可以計算出如何以編程方式更新參數,那麼我可以在配置文件中擁有一個相當通用的目標,然後更新LogEventInfo屬性和參數以適應任何數據庫連接或存儲的需要程序來執行。

+0

什麼SQL命令實際上看起來像'InsertLog'是它或你有實際的代碼..看起來像你缺少值關鍵字.. – MethodMan 2013-02-20 01:36:11

+0

'InsertLog'是一個SQL Server數據庫中的存儲過程。所有實際的INSERT SQL都在那裏。正如我上面提到的,如果我把''標籤放到NLog.config文件中,它就可以工作。 SQL部分正在工作。 – jwatts1980 2013-02-20 14:42:39

回答

4

更新

事實證明,該LogEventInfo.Parameters收集用於LogEventInfo.FormattedMessage財產。如果要使用LogEventInfo.FormatProvider或甚至將LogEventInfo.Message設置爲等於string.format字符串,則使用Parameters object []數組來提供字符串中的替換。 See here for the code

儘管名稱相似,但LogEventInfo.Parameters與NLog.config文件中的<target ><parameter /></target>不對應。而且似乎沒有辦法通過LogEventInfo對象訪問數據庫參數。 (感謝Kim Christensen在NLog項目論壇上爲該鏈接)


我能夠使用自定義目標得到這個工作。 但我仍然質疑爲什麼我以前的方法不起作用。看起來好像是如果Parameters數組是可訪問的,NLog應該遵守分配給它的參數。

這就是說,這裏是我最後使用的代碼:

首先,我不得不創建自定義的目標,並設置它的數據發送到數據庫:

[Target("DatabaseLog")] 
public sealed class DatabaseLogTarget : TargetWithLayout 
{ 
    public DatabaseLogTarget() 
    { 
    } 

    protected override void Write(AsyncLogEventInfo logEvent) 
    { 
    //base.Write(logEvent); 
    this.SaveToDatabase(logEvent.LogEvent); 
    } 

    protected override void Write(AsyncLogEventInfo[] logEvents) 
    { 
    //base.Write(logEvents); 
    foreach (AsyncLogEventInfo info in logEvents) 
    { 
     this.SaveToDatabase(info.LogEvent); 
    } 
    } 

    protected override void Write(LogEventInfo logEvent) 
    { 
    //string logMessage = this.Layout.Render(logEvent); 
    this.SaveToDatabase(logEvent); 
    } 

    private void SaveToDatabase(LogEventInfo logInfo) 
    { 
    if (logInfo.Properties.ContainsKey("commandText") && 
     logInfo.Properties["commandText"] != null) 
    { 
     //Build the new connection 
     SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); 

     //use the connection string if it's present 
     if (logInfo.Properties.ContainsKey("connectionString") && 
     logInfo.Properties["connectionString"] != null) 
     builder.ConnectionString = logInfo.Properties["connectionString"].ToString(); 

     //set the host 
     if (logInfo.Properties.ContainsKey("dbHost") && 
     logInfo.Properties["dbHost"] != null) 
     builder.DataSource = logInfo.Properties["dbHost"].ToString(); 

     //set the database to use 
     if (logInfo.Properties.ContainsKey("dbDatabase") && 
     logInfo.Properties["dbDatabase"] != null) 
     builder.InitialCatalog = logInfo.Properties["dbDatabase"].ToString(); 

     //if a user name and password are present, then we're not using integrated security 
     if (logInfo.Properties.ContainsKey("dbUserName") && logInfo.Properties["dbUserName"] != null && 
     logInfo.Properties.ContainsKey("dbPassword") && logInfo.Properties["dbPassword"] != null) 
     { 
     builder.IntegratedSecurity = false; 
     builder.UserID = logInfo.Properties["dbUserName"].ToString(); 
     builder.Password = logInfo.Properties["dbPassword"].ToString(); 
     } 
     else 
     { 
     builder.IntegratedSecurity = true; 
     } 

     //Create the connection 
     using (SqlConnection conn = new SqlConnection(builder.ToString())) 
     { 
     //Create the command 
     using (SqlCommand com = new SqlCommand(logInfo.Properties["commandText"].ToString(), conn)) 
     { 
      foreach (DatabaseParameterInfo dbi in logInfo.Parameters) 
      { 
      //Add the parameter info, using Layout.Render() to get the actual value 
      com.Parameters.AddWithValue(dbi.Name, dbi.Layout.Render(logInfo)); 
      } 

      //open the connection 
      com.Connection.Open(); 

      //Execute the sql command 
      com.ExecuteNonQuery(); 
     } 
     } 
    } 
    } 
} 

接下來,我更新了我的NLog.config文件,以便爲新目標的規則:

<?xml version="1.0" encoding="utf-8" ?> 
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <targets async="true"> 
    <target name="DatabaseLog1" xsi:type="DatabaseLog" /> 
    </targets> 
    <rules> 
    <logger name="LogDB" minlevel="Trace" writeTo="DatabaseLog1" /> 
    </rules> 
</nlog> 

然後我創建了一個類來包裝我的數據庫記錄的電話。它還提供了一個功能的Exception轉換成NLOG LogEventInfo對象:

public class DatabaseLogger 
{ 
    public Logger log = null; 

    public DatabaseLogger() 
    { 
    //Make sure the custom target is registered for use BEFORE using it 
    ConfigurationItemFactory.Default.Targets.RegisterDefinition("DatabaseLog", typeof(DatabaseLogTarget)); 

    //initialize the log 
    this.log = NLog.LogManager.GetLogger("LogDB"); 
    } 

    /// <summary> 
    /// Logs a trace level NLog message</summary> 
    public void T(LogEventInfo info) 
    { 
    info.Level = LogLevel.Trace; 
    this.Log(info); 
    } 

    /// <summary> 
    /// Allows for logging a trace exception message to multiple log sources. 
    /// </summary> 
    public void T(Exception e) 
    { 
    this.T(FormatError(e)); 
    } 

    //I also have overloads for all of the other log levels... 

    /// <summary> 
    /// Attaches the database connection information and parameter names and layouts 
    /// to the outgoing LogEventInfo object. The custom database target uses 
    /// this to log the data. 
    /// </summary> 
    /// <param name="info"></param> 
    /// <returns></returns> 
    public virtual void Log(LogEventInfo info) 
    { 
    info.Properties["dbHost"] = "SQLServer"; 
    info.Properties["dbDatabase"] = "TempLogDB"; 
    info.Properties["dbUserName"] = "username"; 
    info.Properties["dbPassword"] = "password"; 
    info.Properties["commandText"] = "exec InsertLog @LogDate, @LogLevel, @Location, @Message"; 

    info.Parameters = new DatabaseParameterInfo[] { 
     new DatabaseParameterInfo("@LogDate", Layout.FromString("${date:format=yyyy\\-MM\\-dd HH\\:mm\\:ss.fff}")), 
     new DatabaseParameterInfo("@LogLevel", Layout.FromString("${level}")), 
     new DatabaseParameterInfo("@Location", Layout.FromString("${event-context:item=location}")), 
     new DatabaseParameterInfo("@Message", Layout.FromString("${event-context:item=shortmessage}")) 
    }; 

    this.log.Log(info); 
    } 


    /// <summary> 
    /// Creates a LogEventInfo object with a formatted message and 
    /// the location of the error. 
    /// </summary> 
    protected LogEventInfo FormatError(Exception e) 
    { 
    LogEventInfo info = new LogEventInfo(); 

    try 
    { 
     info.TimeStamp = DateTime.Now; 

     //Create the message 
     string message = e.Message; 
     string location = "Unknown"; 

     if (e.TargetSite != null) 
     location = string.Format("[{0}] {1}", e.TargetSite.DeclaringType, e.TargetSite); 
     else if (e.Source != null && e.Source.Length > 0) 
     location = e.Source; 

     if (e.InnerException != null && e.InnerException.Message.Length > 0) 
     message += "\nInnerException: " + e.InnerException.Message; 

     info.Properties["location"] = location; 

     info.Properties["shortmessage"] = message; 

     info.Message = string.Format("{0} | {1}", location, message); 
    } 
    catch (Exception exp) 
    { 
     info.Properties["location"] = "SystemLogger.FormatError(Exception e)"; 
     info.Properties["shortmessage"] = "Error creating error message"; 
     info.Message = string.Format("{0} | {1}", "SystemLogger.FormatError(Exception e)", "Error creating error message"); 
    } 

    return info; 
    } 
} 

所以,當我開始我的申請,我就可以開始輕鬆地登錄:

DatabaseLogger dblog = new DatabaseLogger(); 
dblog.T(new Exception("Error message", new Exception("Inner message"))); 

隨着功夫,我可以稍微有點繼承DatabaseLogger類並覆蓋Log方法以創建任意數量的不同數據庫日誌。如果需要,我可以動態更新連接信息。我可以更改commandTextParameters以適應每個數據庫調用。我只需要擁有一個目標。

如果我想提供多種數據庫類型的功能,我可以添加一個info.Properties["dbProvider"]屬性,該屬性在SaveToDatabase方法中讀取,然後可以產生不同的連接類型。