2013-02-19 98 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方法中读取,然后可以产生不同的连接类型。

相关问题