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

Custom Target does not work when defined in config file

我创建了一个自定义目标,它从 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.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.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.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.

更新

如果我在我的 Target 覆盖的三个 Write 方法上设置断点,调试器永远不会命中它们。这似乎证实了在 NLog 尝试从配置中设置我的 Target 时出现了问题,并且它从未被添加到日志管理器配置中。

我还确定空引用即将到来,因为 LogManager.Configuration 属性 为空。为什么不设置呢?我没有在任何地方手动替换它,所以这应该是从配置文件加载的实例吧?

我发现注释掉目标中的 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...
}