在配置文件中定义时,自定义目标不起作用

时间:2015-11-10 17:22:42

标签: c# logging nlog

我创建了一个自定义目标,它将值从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。为什么不设置?我没有在任何地方手动替换它,所以这应该是从配置文件加载的实例吗?

1 个答案:

答案 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...
}