【问题标题】:NLog - Run-Time Parameters for Database TargetNLog - 数据库目标的运行时参数
【发布时间】:2013-02-19 21:15:55
【问题描述】:

我正在尝试向我的数据库日志目标添加一些自定义。 在我的 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}">
</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 属性和参数以适应要连接或存储的任何数据库的需要要执行的过程。

【问题讨论】:

  • InsertLog 的 SQL 命令实际上是什么样的,是它还是您有实际代码.. 看起来您缺少 Values 关键字..
  • InsertLog 是 SQL Server 数据库中的存储过程。所有实际的INSERT SQL 都在其中。正如我上面提到的,如果我将&lt;parameter /&gt; 标签放入 NLog.config 文件中,它就可以工作。 SQL 部分正在运行。

标签: c# .net c#-4.0 nlog


【解决方案1】:

更新

原来LogEventInfo.Parameters 集合用于LogEventInfo.FormattedMessage 属性。如果您想使用LogEventInfo.FormatProvider 甚至设置LogEventInfo.Message 等于string.format 字符串,那么Parameters object[] 数组用于提供字符串中的替换。 See here for the code.

尽管命名相似,LogEventInfo.Parameters 不对应 NLog.config 文件中的&lt;target &gt;&lt;parameter /&gt;&lt;/target&gt;。而且似乎没有办法通过LogEventInfo 对象获取数据库参数。 (感谢 NLog 项目论坛上的 Kim Christensen 提供该链接)


我能够使用自定义目标来完成这项工作。 但我仍然在质疑为什么我以前的方法不起作用。看起来如果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 以适应每个数据库调用。我只需要有一个目标。

如果我想为多种数据库类型提供功能,我可以添加在SaveToDatabase 方法中读取的info.Properties["dbProvider"] 属性,然后可以生成不同的连接类型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-05
    • 1970-01-01
    • 2018-06-19
    • 2014-11-18
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多