简体   繁体   English

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

[英]log4net database logging with custom parameters

I have database logging in place using the AdoNetAppender. 我使用AdoNetAppender进行数据库日志记录。 What I'd like to do is log the user identity on each log statement. 我想要做的是在每个日志语句上记录用户身份。 However, I don't want to use the standard log4net %identity parameter for two reasons: 但是,我不想使用标准的log4net%identity参数有两个原因:

  1. log4net warn that its extremely slow as it has to look up the context identity. log4net警告它非常慢,因为它必须查找上下文标识。
  2. In some service components the standard identity is a service account but we have already captured the user identity in a variable and I'd like to use that. 在某些服务组件中,标准标识是服务帐户,但我们已经在变量中捕获了用户标识,我想使用它。

I have seen code where some people use the log4net.ThreadContext to add additional properties but I understand that this is 'unsafe' due to thread interleaving (and it is also a performance drain). 我见过一些代码,其中一些人使用log4net.ThreadContext来添加其他属性但我知道由于线程交错这是“不安全的”(并且它也是性能消耗)。

My approach has been to extend the AdoNetAppenderParameter class thus: 我的方法是扩展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;
    }

}

and then programmatically add this to the current appender like so: 然后以编程方式将其添加到当前的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");

The intention here is to use a standard format for messages and parse the first part of the string which should always be the username. 这里的目的是使用消息的标准格式并解析字符串的第一部分,该部分应该始终是用户名。 The FormatValue() method of the custom appender parameter should then use only that part of the string so that it can be written to a separate field in the log database. 然后,自定义appender参数的FormatValue()方法应仅使用该字符串的那一部分,以便可以将其写入日志数据库中的单独字段。

My problem is that no log statements are written to the database. 我的问题是没有日志语句写入数据库。 Oddly, when debugging, a breakpoint in the FormatValue() method is only hit when I stop the service. 奇怪的是,在调试时,只有在我停止服务时才会触发FormatValue()方法中的断点。

I've trawled through loads of stuff relating to this but haven't yet found any answers. 我已经搜索了大量与此有关的东西,但还没有找到任何答案。 Has anyone managed to do this, or am I on the wrong trail. 有没有人设法做到这一点,或者我是在错误的轨道上。 PS I've also tried extending the AdoNetAppender but it doesnt give you access to set the parameter values. PS我也试过扩展AdoNetAppender,但它不能让你访问设置参数值。

I also needed to log structured data and liked to use logging interface like this: 我还需要记录结构化数据,并喜欢使用这样的日志记录界面:

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

So I also wrote custom AdoNetAppenderParameter class to do the job: 所以我也编写了自定义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;
    }
}

Now log4net configuration can be used to log any property of given object: 现在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>

After some experimentation, I finally got this to work. 经过一些实验,我终于得到了这个。 Ensuring that log4net's internal logging helped identify the errors and downloading the log4net source code and reviewing the AdoNetAppenderParameter class showed how the FormatValue() method should be used. 确保log4net的内部日志记录有助于识别错误并下载log4net源代码并查看AdoNetAppenderParameter类,这表明应该如何使用FormatValue()方法。 So, here's the amended custom appender parameter: 所以,这是修改后的自定义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;
    }

}

And to use this, I add it in the log4net config file like this: 要使用它,我将它添加到log4net配置文件中,如下所示:

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

And by convention, my log statements will be something like this: 按照惯例,我的日志语句将是这样的:

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

If you look at the class, it uses data[0] as the username, so it is dependant on following the convention. 如果查看该类,它将使用data [0]作为用户名,因此它依赖于遵循约定。 It does, however, get the username into its own parameter and into a separate field in the log database table, without resorting to stuffing it temporarily into the unsafe ThreadContext. 但是,它确实将用户名放入其自己的参数并进入日志数据库表中的单独字段,而无需将其临时填充到不安全的ThreadContext中。

Yes, thread agility means that you might not get the correct data back. 是的,线程敏捷性意味着您可能无法获得正确的数据。 For log4net, you will want to stick it in your HttpContext's Items collection . 对于log4net,您需要将其粘贴在HttpContext的Items集合中

The trouble is you have to do some work to get it back out when it's time to write those values to the database because of this I have always used Marek's Adaptive Property Provider class to do the grunt work for me. 麻烦的是你需要做一些工作,以便在将这些值写入数据库时​​将其恢复原状,因为我总是使用Marek的Adaptive Property Provider类来为我做咕噜咕噜的工作。 It's super simple to use it as all you have to do is the following: 使用它非常简单,因为您需要做的就是以下内容:

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

The adaptive property will know the appropriate place to retrieve the value when log4net requests it. 当log4net请求时,adaptive属性将知道检索值的适当位置。

Alternative Option 替代选择

If you're not stuck with log4net, NLog makes logging for ASP.NET websites way more simple because they natively support ASP.NET applications. 如果您没有坚持使用log4net, NLog会使ASP.NET网站的日志记录变得更加简单,因为它们原生支持ASP.NET应用程序。 Usage and even configuration is almost identical to log4net! 用法甚至配置几乎与log4net相同!

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

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