[英]NLog - Run-Time Parameters for Database Target
I am attempting to add some customization to my database log target.我正在尝试向我的数据库日志目标添加一些自定义。 In my NLog.config, I have this:
在我的 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>
And in my C# code, I have this:在我的 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);
}
But I'm getting a SQL error that reads "Must declare the scalar variable "@LogDate"".但是我收到一个 SQL 错误,内容为“必须声明标量变量“@LogDate””。
The properties are working because the connection succeeds.由于连接成功,这些属性正在工作。 But for some reason the parameters are not "binding" to the scalar variables in the command.
但是由于某种原因,参数没有“绑定”到命令中的标量变量。
If I create the parameters manually in the NLog.config file, it works perfectly:如果我在 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>
But that defeats the whole purpose of being able to customize the commandText and parameter values at run-time.但这违背了能够在运行时自定义 commandText 和参数值的全部目的。
What do I need to do to cause the target to correctly pick up the values for the parameters?我需要做什么才能使目标正确获取参数值?
update更新
Overall, I want to have a way to customize the targets via C# code.总的来说,我想要一种通过 C# 代码自定义目标的方法。 I want to rely on the NLog.config file only has much as needed.
我想依靠 NLog.config 文件只需要多少。 But it seems like NLog is pretty tied to the config file settings, and I'm trying to work within that constraint but be as flexible as possible.
但似乎 NLog 与配置文件设置非常相关,我正在尝试在该约束范围内工作,但要尽可能灵活。 With this database target, if I can figure out how to update the parameters programmatically, then I can have a fairly generic target in the config file, then update the LogEventInfo properties and parameters to suit the needs of whatever database to connect to, or stored procedure to execute.
有了这个数据库目标,如果我能弄清楚如何以编程方式更新参数,那么我就可以在配置文件中有一个相当通用的目标,然后更新 LogEventInfo 属性和参数以满足要连接或存储的任何数据库的需要执行的程序。
update更新
It turns out that the LogEventInfo.Parameters
collection is used for the LogEventInfo.FormattedMessage
property.事实证明,
LogEventInfo.Parameters
集合用于LogEventInfo.FormattedMessage
属性。 If you want to use the LogEventInfo.FormatProvider
or even set LogEventInfo.Message
equal to a string.format
string, then the Parameters
object[] array is used to provide the substitutions in the string.如果要使用
LogEventInfo.FormatProvider
或者甚至设置LogEventInfo.Message
等于A string.format
字符串,则Parameters
对象[]数组被用于提供该串中的取代。 See here for the code . 代码见这里。
Despite the similar naming, LogEventInfo.Parameters
does not correspond to <target ><parameter /></target>
in the NLog.config file.尽管命名相似,但
LogEventInfo.Parameters
与 NLog.config 文件中的<target ><parameter /></target>
并不对应。 And there does not appear to be a way to get to the database parameters via the LogEventInfo
object.而且似乎没有办法通过
LogEventInfo
对象获取数据库参数。 (Thanks to Kim Christensen over at NLog project forum for that link) (感谢 Kim Christensen 在 NLog 项目论坛上提供的链接)
I was able to get this working using a custom target.我能够使用自定义目标来完成这项工作。
But I am still questioning why my previous approach did not work.但我仍然质疑为什么我以前的方法不起作用。 It really seems like that if the Parameters
array is accessible, that NLog should honor the parameters that are assigned to it.看起来,如果Parameters
数组是可访问的,那么 NLog 应该尊重分配给它的参数。
That said, here is the code that I ended up using:也就是说,这是我最终使用的代码:
First I had to create the custom target and set it up to send data to the database:首先,我必须创建自定义目标并将其设置为将数据发送到数据库:
[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();
}
}
}
}
}
Next, I updated my NLog.config file to include a rule for the new target:接下来,我更新了我的 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>
Then I created a class to wrap my database logging calls.然后我创建了一个类来包装我的数据库日志记录调用。 It also provides a function to convert an
Exception
into an NLog LogEventInfo
object:它还提供了将
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;
}
}
So when I start my application, I can start logging easily:所以当我启动我的应用程序时,我可以轻松地开始记录:
DatabaseLogger dblog = new DatabaseLogger();
dblog.T(new Exception("Error message", new Exception("Inner message")));
With a slight bit of effort, I can inherit from the DatabaseLogger
class and override the Log
method to create any number of different database logs.只需稍加努力,我就可以从
DatabaseLogger
类继承并覆盖Log
方法来创建任意数量的不同数据库日志。 I can update the connection info dynamically if I want.如果需要,我可以动态更新连接信息。 I can change the
commandText
and Parameters
to suit each database call.我可以更改
commandText
和Parameters
以适应每个数据库调用。 And I just have to have the one target.我只需要有一个目标。
If I want to provide functionality for multiple database types, I can add a info.Properties["dbProvider"]
property which gets read in the SaveToDatabase
method, which can then spawn off a different connection type.如果我想为多种数据库类型提供功能,我可以添加一个
info.Properties["dbProvider"]
属性,该属性在SaveToDatabase
方法中读取,然后可以产生不同的连接类型。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.