我创建了一个自定义目标,它将值从LogInfoEvent属性字典中提取出来,以便发送到存储过程。
[Target("DatabaseModelEventLogger")]
public class SqlStoredProcedureTarget : TargetWithLayout
{
private const string CommandTextKey = "commandText";
private const string ConnectionStringKey = "connectionString";
private const string HostKey = "dbHost";
private const string DatabaseNameKey = "dbDatabase";
private const string UsernameKey = "dbUserName";
private const string PasswordKey = "dbPassword";
[RequiredParameter]
public string StoredProcedureName { get; set; } = string.Empty;
public string SqlConnectionString { get; set; } = string.Empty;
protected override void Write(AsyncLogEventInfo[] logEvents)
{
foreach (AsyncLogEventInfo eventInfo in logEvents)
{
this.Write(eventInfo);
}
}
protected override void Write(AsyncLogEventInfo logEvent)
{
this.Write(logEvent.LogEvent);
}
protected override void Write(LogEventInfo logEvent)
{
this.SaveToDatabase(logEvent);
}
private void SaveToDatabase(LogEventInfo logInfo)
{
if (logInfo == null || logInfo.Parameters == null || logInfo.Parameters.Length == 0)
{
return;
}
SqlConnectionStringBuilder connectionBuilder = this.CreateConnectionStringBuilder(logInfo);
using (var connection = new SqlConnection(connectionBuilder.ToString()))
{
using (var sqlCommand = new SqlCommand(this.StoredProcedureName, connection))
{
sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
foreach (LogParameter parameter in logInfo.Parameters)
{
// Add the parameter info using the rendered value of the layout.
sqlCommand.Parameters.AddWithValue(parameter.Name, parameter.Value.ToString());
}
sqlCommand.Connection.Open();
sqlCommand.ExecuteNonQuery();
}
}
}
private SqlConnectionStringBuilder CreateConnectionStringBuilder(LogEventInfo logInfo)
{
var connectionBuilder = new SqlConnectionStringBuilder();
if (string.IsNullOrEmpty(this.StoredProcedureName))
{
throw new InvalidOperationException("You can not save the provided LogEventInfo to the database without a valid CommandText property.");
}
// Setup the connection builder
if (!string.IsNullOrEmpty(this.SqlConnectionString))
{
connectionBuilder.ConnectionString = this.SqlConnectionString;
}
object hostName = null;
if (logInfo.Properties.TryGetValue(HostKey, out hostName) && hostName != null)
{
connectionBuilder.DataSource = hostName.ToString();
}
object database = null;
if (logInfo.Properties.TryGetValue(DatabaseNameKey, out database) && database != null)
{
connectionBuilder.InitialCatalog = database.ToString();
}
object userName = null;
object password = null;
if ((logInfo.Properties.TryGetValue(UsernameKey, out userName) && userName != null) &&
(logInfo.Properties.TryGetValue(PasswordKey, out password) && password != null))
{
connectionBuilder.IntegratedSecurity = false;
connectionBuilder.UserID = userName.ToString();
connectionBuilder.Password = password.ToString();
}
else
{
connectionBuilder.IntegratedSecurity = true;
}
return connectionBuilder;
}
}
在我的应用中执行的第一行代码是该目标定义的注册。
ConfigurationItemFactory.Default.Targets
.RegisterDefinition("DatabaseModelEventLogger", typeof(SqlStoredProcedureTarget));
然后我将它添加到我的配置文件
<variable name="EncryptedTarget" value="database" />
<variable name="AppName" value="StoredProcedureWithEncryptedConnectionString" />
<targets async="true">
<target name="database" xsi:type="DatabaseModelEventLogger" storedProcedureName="SaveAppLog" sqlConnectionString="foo">
<parameter name="@Severity" layout="${level}" />
<parameter name="@ClassName" layout="${logger}" />
<parameter name="@Message" layout="${message}" />
<parameter name="@CreatedBy" layout="${windows-identity}" />
</target>
</targets>
<rules>
<logger name="*" minLevel="Trace" writeTo="database" />
</rules>
现在,当我想要记录某些东西时,我只是调用我的日志方法并为事件分配额外的东西。
public void Error(string message, Exception exception, [CallerMemberName] string methodName = "")
{
this.Log(LogLevel.Error, message, exception, methodName);
}
public void Log(LogLevel level, string message, Exception exception = null, [CallerMemberName] string methodName = "")
{
var targetRuleConfig = new TargetRuleConfiguration();
var eventInfo = new LogEventInfo(targetRuleConfig.MapLogLevelToNLog(level), this.logger.Name, message);
eventInfo.Properties.Add(nameof(exception), exception.GetType().FullName);
eventInfo.Properties.Add(nameof(exception.StackTrace), exception.StackTrace);
eventInfo.Properties.Add(nameof(methodName), methodName);
this.logger.Log(eventInfo);
}
没有任何内容写入数据库。在运行时,我在LogManager.Configuration.AllTargets
内看不到任何目标。
如果我以编程方式实例化目标并进行设置:
public class SqlLogConfiguration
{
private SqlStoredProcedureTarget databaseTarget;
public SqlLogConfiguration(string connectionString, string storedProcedure)
{
this.LoggingLevels = new LogLevel[] { LogLevel.Info };
this.StoredProcedureName = storedProcedure;
this.ConnectionString = connectionString;
this.databaseTarget = new SqlStoredProcedureTarget();
}
public SqlLogConfiguration(string connectionString, string storedProcedure, LogLevelType logLevelType, params LogLevel[] levels) : this(connectionString, storedProcedure)
{
this.LoggingLevels = levels;
this.LogLevelType = logLevelType;
}
public LogLevel[] LoggingLevels { get; private set; }
public LogLevelType? LogLevelType { get; private set; }
public string ConnectionString { get; set; }
public string StoredProcedureName { get; set; }
public void Configure()
{
if (!this.LogLevelType.HasValue)
{
throw new InvalidOperationException("The configuration has not been assigned a valid LogLevelType.");
}
this.databaseTarget.StoredProcedureName = StoredProcedureName;
this.databaseTarget.SqlConnectionString = ConnectionString;
LogManager.Configuration.AddTarget("Sql", this.databaseTarget);
var targetRules = new TargetRuleConfiguration();
targetRules.ConfigureTargetRules(this.databaseTarget, this.LoggingLevels, this.LogLevelType.Value);
}
}
它写入数据库没有任何问题。为什么我的配置文件不起作用?
当我打开NLog内部日志记录时,我确实看到了NullReferenceException
被抛出。但是在找到我的目标之后会发生异常。您可以在此处的日志文件中看到它为我的目标指定了正确的属性值。我不确定为什么抛出异常。
2015-11-10 10:20:21.7119 Trace FindReachableObject<NLog.Internal.IRenderable>:
2015-11-10 10:20:21.7119 Trace Scanning LongDateLayoutRenderer 'Layout Renderer: ${longdate}'
2015-11-10 10:20:21.7249 Debug Setting 'UppercaseLayoutRendererWrapper.uppercase' to 'true'
2015-11-10 10:20:21.7249 Trace Wrapping LevelLayoutRenderer with UppercaseLayoutRendererWrapper
2015-11-10 10:20:21.7249 Trace FindReachableObject<NLog.Internal.IRenderable>:
2015-11-10 10:20:21.7249 Trace Scanning LevelLayoutRenderer 'Layout Renderer: ${level}'
2015-11-10 10:20:21.7249 Trace FindReachableObject<NLog.Internal.IRenderable>:
2015-11-10 10:20:21.7379 Trace Scanning UppercaseLayoutRendererWrapper 'Layout Renderer: ${uppercase}'
2015-11-10 10:20:21.7379 Trace Scanning SimpleLayout ''''
2015-11-10 10:20:21.7379 Trace Scanning LevelLayoutRenderer 'Layout Renderer: ${level}'
2015-11-10 10:20:21.7379 Trace FindReachableObject<NLog.Internal.IRenderable>:
2015-11-10 10:20:21.7379 Trace Scanning LoggerNameLayoutRenderer 'Layout Renderer: ${logger}'
2015-11-10 10:20:21.7379 Trace FindReachableObject<NLog.Internal.IRenderable>:
2015-11-10 10:20:21.7539 Trace Scanning MessageLayoutRenderer 'Layout Renderer: ${message}'
2015-11-10 10:20:21.7539 Debug Setting 'SqlStoredProcedureTarget.name' to 'database'
2015-11-10 10:20:21.7539 Debug Setting 'SqlStoredProcedureTarget.storedProcedureName' to 'SaveAppLog'
2015-11-10 10:20:21.7539 Debug Setting 'SqlStoredProcedureTarget.sqlConnectionString' to 'foo'
2015-11-10 10:20:21.7539 Error Error in Parsing Configuration File. Exception : NLog.NLogConfigurationException: Exception occurred when loading configuration from C:\Users\b5130\Source\Workspaces\Core\Main\Source\Samples\ CompanyFoo.Logging\04.StoredProcedureWithEncryptedConnectionString\bin\Debug\NLog.config ---> System.NullReferenceException: Object reference not set to an instance of an object.
at NLog.Config.XmlLoggingConfiguration.ExpandSimpleVariables(String input)
at NLog.Config.XmlLoggingConfiguration.SetPropertyFromElement(Object o, NLogXmlElement element)
at NLog.Config.XmlLoggingConfiguration.ParseTargetElement(Target target, NLogXmlElement targetElement)
at NLog.Config.XmlLoggingConfiguration.ParseTargetsElement(NLogXmlElement targetsElement)
at NLog.Config.XmlLoggingConfiguration.ParseNLogElement(NLogXmlElement nlogElement, String baseDirectory)
at NLog.Config.XmlLoggingConfiguration.ParseTopLevel(NLogXmlElement content, String baseDirectory)
at NLog.Config.XmlLoggingConfiguration.Initialize(XmlReader reader, String fileName, Boolean ignoreErrors)
--- End of inner exception stack trace ---
2015-11-10 10:20:21.7699 Debug --- NLog configuration dump ---
2015-11-10 10:20:21.7699 Debug Targets:
2015-11-10 10:20:21.7699 Debug Rules:
2015-11-10 10:20:21.7699 Debug --- End of NLog configuration dump ---
2015-11-10 10:20:21.7699 Info Watching path 'C:\Users\b5130\Source\Workspaces\Core\Main\Source\Samples\ CompanyFoo.Logging\04.StoredProcedureWithEncryptedConnectionString\bin\Debug' filter 'NLog.config' for changes.
2015-11-10 10:20:21.8069 Trace FindReachableObject<System.Object>:
2015-11-10 10:20:21.8069 Info Found 0 configuration items
2015-11-10 10:20:21.8069 Info Configuration initialized.
2015-11-10 10:20:21.8169 Info NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c. File version: 4.3.0. Product version: 4.3.0-alpha1.
2015-11-10 10:20:21.8169 Trace FindReachableObject<System.Object>:
2015-11-10 10:20:21.8169 Info Found 0 configuration items
2015-11-10 10:20:21.8389 Trace FindReachableObject<System.Object>:
2015-11-10 10:20:21.8389 Info Found 0 configuration items
2015-11-10 10:20:21.8389 Trace FindReachableObject<System.Object>:
2015-11-10 10:20:21.8479 Info Found 0 configuration items
2015-11-10 10:20:21.8479 Trace FindReachableObject<System.Object>:
2015-11-10 10:20:21.8479 Info Found 0 configuration items
2015-11-10 10:20:21.8479 Debug Targets for _04.StoredProcedureWithEncryptedConnectionString.Program by level:
2015-11-10 10:20:21.8479 Debug Trace =>
2015-11-10 10:20:21.8639 Debug Debug =>
2015-11-10 10:20:21.8639 Debug Info =>
2015-11-10 10:20:21.8639 Debug Warn =>
2015-11-10 10:20:21.8639 Debug Error =>
2015-11-10 10:20:21.8639 Debug Fatal =>
2015-11-10 10:20:21.8639 Debug Targets for CompanyFoo.Core.Logging.LogService by level:
2015-11-10 10:20:21.8819 Debug Trace =>
2015-11-10 10:20:21.8819 Debug Debug =>
2015-11-10 10:20:21.8819 Debug Info =>
2015-11-10 10:20:21.8819 Debug Warn =>
2015-11-10 10:20:21.8819 Debug Error =>
2015-11-10 10:20:21.8939 Debug Fatal =>
2015-11-10 10:20:21.8939 Trace LogFactory.Flush(00:00:15)
2015-11-10 10:20:21.8939 Trace Flushing all targets...
2015-11-10 10:20:21.8939 Trace ForEachItemInParallel() 0 items
2015-11-10 10:20:22.4297 Info Shutting down logging...
2015-11-10 10:20:22.4297 Info Stopping file watching for path 'C:\Users\b5130\Source\Workspaces\Core\Main\Source\Samples\ CompanyFoo.Logging\04.StoredProcedureWithEncryptedConnectionString\bin\Debug' filter 'NLog.config'
2015-11-10 10:20:22.4297 Info Closing old configuration.
2015-11-10 10:20:22.4297 Trace LogFactory.Flush(00:00:15)
2015-11-10 10:20:22.4407 Trace Flushing all targets...
2015-11-10 10:20:22.4407 Trace ForEachItemInParallel() 0 items
2015-11-10 10:20:22.4407 Debug Closing logging configuration...
2015-11-10 10:20:22.4407 Debug Finished closing logging configuration.
2015-11-10 10:20:22.4407 Debug Targets for _04.StoredProcedureWithEncryptedConnectionString.Program by level:
2015-11-10 10:20:22.4407 Debug Trace =>
2015-11-10 10:20:22.4407 Debug Debug =>
2015-11-10 10:20:22.4587 Debug Info =>
2015-11-10 10:20:22.4587 Debug Warn =>
2015-11-10 10:20:22.4587 Debug Error =>
2015-11-10 10:20:22.4587 Debug Fatal =>
2015-11-10 10:20:22.4587 Debug Targets for CompanyFoo.Core.Logging.LogService by level:
2015-11-10 10:20:22.4587 Debug Trace =>
2015-11-10 10:20:22.4737 Debug Debug =>
2015-11-10 10:20:22.4737 Debug Info =>
2015-11-10 10:20:22.4737 Debug Warn =>
2015-11-10 10:20:22.4737 Debug Error =>
2015-11-10 10:20:22.4737 Debug Fatal =>
2015-11-10 10:20:22.4737 Info Logger has been shut down.
更新
如果我在Write
覆盖的三个Target
方法上设置了断点,则调试器永远不会命中它们。这似乎证实,当NLog尝试从配置设置我的Target并且它永远不会被添加到日志管理器配置时,某些事情出错了。
我还确定空引用即将到来,因为LogManager.Configuration
属性为null。为什么不设置?我没有在任何地方手动替换它,所以这应该是从配置文件加载的实例吗?
答案 0 :(得分:1)
我发现注释掉目标中的parameter
元素会让记录器毫无例外地加载。
如果添加以下内容,则会毫无例外地解析配置文件。
[Target("DatabaseModelEventLogger")]
public class SqlStoredProcedureTarget : TargetWithLayout
{
//removed for brevity...
public SqlStoredProcedureTarget()
{
this.Parameters = new List<DatabaseParameterInfo>();
}
/// <summary>
/// Gets the collection of parameters. Each parameter contains a mapping
/// between NLog layout and a database named or positional parameter.
/// </summary>
/// <docgen category='SQL Statement' order='12' />
[ArrayParameter(typeof (DatabaseParameterInfo), "parameter")]
public IList<DatabaseParameterInfo> Parameters { get; private set; }
//removed for brevity...
}