8

我使用 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;
    }

}

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

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,但它不允许您设置参数值。

4

3 回答 3

5

我还需要记录结构化数据并且喜欢使用这样的日志接口:

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>
于 2014-11-25T10:25:59.830 回答
4

经过一些实验,我终于得到了这个工作。确保 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 中。

于 2012-07-17T20:09:09.983 回答
2

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

问题是当需要将这些值写入数据库时​​,您必须做一些工作才能将其恢复,因此我一直使用Marek 的 Adaptive Property Provider 类为我完成繁重的工作。使用它非常简单,因为您只需执行以下操作:

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

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

替代选项

如果您不使用 log4net,NLog使 ASP.NET 网站的日志记录更加简单,因为它们本机支持 ASP.NET 应用程序。用法甚至配置几乎与 log4net 相同!

于 2012-07-13T15:55:07.967 回答