我正在尝试向我的数据库日志目标添加一些自定义。 在我的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属性和参数以满足连接到或存储的任何数据库的需要执行程序。
答案 0 :(得分:5)
<强>更新强>
事实证明,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
方法来创建任意数量的不同数据库日志。如果需要,我可以动态更新连接信息。我可以更改commandText
和Parameters
以适应每个数据库调用。我只需拥有一个目标。
如果我想为多种数据库类型提供功能,我可以添加info.Properties["dbProvider"]
属性,该属性可以在SaveToDatabase
方法中读取,然后可以产生不同的连接类型。