繁体   English   中英

使用自定义参数记录log4net数据库

[英]log4net database logging with custom parameters

我使用AdoNetAppender进行数据库日志记录。 我想要做的是在每个日志语句上记录用户身份。 但是,我不想使用标准的log4net%identity参数有两个原因:

  1. log4net警告它非常慢,因为它必须查找上下文标识。
  2. 在某些服务组件中,标准标识是服务帐户,但我们已经在变量中捕获了用户标识,我想使用它。

我见过一些代码,其中一些人使用log4net.ThreadContext来添加其他属性但我知道由于线程交错这是“不安全的”(并且它也是性能消耗)。

我的方法是扩展AdoNetAppenderParameter类:

public class UserAdoNetAppenderParameter : AdoNetAppenderParameter
{

    public UserAdoNetAppenderParameter()
    {
        DbType = DbType.String;
        PatternLayout layout = new PatternLayout();
        Layout2RawLayoutAdapter converter = new Layout2RawLayoutAdapter(layout);
        Layout = converter;
        ParameterName = "@username";
        Size = 255;
    }


    public override void Prepare(IDbCommand command)
    {            
        command.Parameters.Add(this);
    }


    public override void FormatValue(IDbCommand command, LoggingEvent loggingEvent)
    {            
        string[] data = loggingEvent.RenderedMessage.Split('~');
        string username = data[0];
        command.Parameters["@username"] = username;
    }

}

然后以编程方式将其添加到当前的appender,如下所示:

ILog myLog = LogManager.GetLogger("ConnectionService");
IAppender[] appenders = myLog.Logger.Repository.GetAppenders();
AdoNetAppender appender = (AdoNetAppender)appenders[0];                    

appender.AddParameter(new UserAdoNetAppenderParameter());

myLog.InfoFormat("{0}~{1}~{2}~{3}", userName, "ClassName", "Class Method", "Message");

这里的目的是使用消息的标准格式并解析字符串的第一部分,该部分应该始终是用户名。 然后,自定义appender参数的FormatValue()方法应仅使用该字符串的那一部分,以便可以将其写入日志数据库中的单独字段。

我的问题是没有日志语句写入数据库。 奇怪的是,在调试时,只有在我停止服务时才会触发FormatValue()方法中的断点。

我已经搜索了大量与此有关的东西,但还没有找到任何答案。 有没有人设法做到这一点,或者我是在错误的轨道上。 PS我也试过扩展AdoNetAppender,但它不能让你访问设置参数值。

我还需要记录结构化数据,并喜欢使用这样的日志记录界面:

log.Debug( new {
    SomeProperty: "some value",
    OtherProperty: 123
})

所以我也编写了自定义AdoNetAppenderParameter类来完成这项工作:

public class CustomAdoNetAppenderParameter : AdoNetAppenderParameter
{
    public override void FormatValue(IDbCommand command, LoggingEvent loggingEvent)
    {
        // Try to get property value
        object propertyValue = null;
        var propertyName = ParameterName.Replace("@", "");

        var messageObject = loggingEvent.MessageObject;
        if (messageObject != null)
        {
            var property = messageObject.GetType().GetProperty(propertyName);
            if (property != null)
            {
                propertyValue = property.GetValue(messageObject, null);
            }
        }

        // Insert property value (or db null) into parameter
        var dataParameter = (IDbDataParameter)command.Parameters[ParameterName];
        dataParameter.Value = propertyValue ?? DBNull.Value;
    }
}

现在log4net配置可用于记录给定对象的任何属性:

<?xml version="1.0" encoding="utf-8"?>
<log4net>
    <appender name="MyAdoNetAppender" type="log4net.Appender.AdoNetAppender">
        <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
        <connectionString value="... your connection string ..." />
        <commandText value="INSERT INTO mylog ([level],[someProperty]) VALUES (@log_level,@SomeProperty)" />

        <parameter>
            <parameterName value="@log_level" />
            <dbType value="String" />
            <size value="50" />
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%level" />
            </layout>
        </parameter>

        <parameter type="yourNamespace.CustomAdoNetAppenderParameter, yourAssemblyName">
            <parameterName value="@SomeProperty" />
            <dbType value="String" />
            <size value="255" />
        </parameter>
    </appender>

    <root>
        <level value="DEBUG" />
        <appender-ref ref="MyAdoNetAppender" />
    </root>
</log4net>

经过一些实验,我终于得到了这个。 确保log4net的内部日志记录有助于识别错误并下载log4net源代码并查看AdoNetAppenderParameter类,这表明应该如何使用FormatValue()方法。 所以,这是修改后的自定义appender参数:

public class UserAdoNetAppenderParameter : AdoNetAppenderParameter
{        

    public override void FormatValue(IDbCommand command, LoggingEvent loggingEvent)
    {            
        string[] data = loggingEvent.RenderedMessage.Split('~');
        string username = string.Empty;
        if (data != null && data.Length >= 1)
            username = data[0];

        // Lookup the parameter
        IDbDataParameter param = (IDbDataParameter)command.Parameters[ParameterName];

        // Format the value
        object formattedValue = username;

        // If the value is null then convert to a DBNull
        if (formattedValue == null)
        {
            formattedValue = DBNull.Value;
        }

        param.Value = formattedValue;
    }

}

要使用它,我将它添加到log4net配置文件中,如下所示:

<parameter type="MyAssembly.Logging.UserAdoNetAppenderParameter, MyAssembly">
 <parameterName value="@username" />
 <dbType value="String" />
 <size value="255" />
 <layout type="log4net.Layout.PatternLayout" value="%message" />  
</parameter>

按照惯例,我的日志语句将是这样的:

if (log.IsDebugEnabled)
    log.DebugFormat("{0}~{1}~{2}", username, someOtherParameter, message);

如果查看该类,它将使用data [0]作为用户名,因此它依赖于遵循约定。 但是,它确实将用户名放入其自己的参数并进入日志数据库表中的单独字段,而无需将其临时填充到不安全的ThreadContext中。

是的,线程敏捷性意味着您可能无法获得正确的数据。 对于log4net,您需要将其粘贴在HttpContext的Items集合中

麻烦的是你需要做一些工作,以便在将这些值写入数据库时​​将其恢复原状,因为我总是使用Marek的Adaptive Property Provider类来为我做咕噜咕噜的工作。 使用它非常简单,因为您需要做的就是以下内容:

log4net.ThreadContext.Properties["UserName"] = AdaptivePropertyProvider.Create("UserName", Thread.CurrentPrincipal.Identity.Name);

当log4net请求时,adaptive属性将知道检索值的适当位置。

替代选择

如果您没有坚持使用log4net, NLog会使ASP.NET网站的日志记录变得更加简单,因为它们原生支持ASP.NET应用程序。 用法甚至配置几乎与log4net相同!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM