【问题标题】:log4net database logging with custom parameters使用自定义参数记录 log4net 数据库
【发布时间】:2012-07-13 15:22:57
【问题描述】:

我使用 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() 方法中的断点只有在我停止服务时才会被命中。

我已经翻阅了大量与此相关的内容,但尚未找到任何答案。 有没有人设法做到这一点,还是我走错了路。 附言我也尝试过扩展 AdoNetAppender,但它不允许您设置参数值。

【问题讨论】:

    标签: c# database log4net


    【解决方案1】:

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

    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>
    

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

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

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

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

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

        备选方案

        如果您不使用 log4net,NLog 可以让 ASP.NET 网站的日志记录更加简单,因为它们本身就支持 ASP.NET 应用程序。用法甚至配置都和log4net几乎一模一样!

        【讨论】:

        • 我有点卡在 log4net 上,我在解决方案中有 Windows 服务组件,它们无法访问 HttpContext,尽管我们确实将用户名作为字符串传递。我真的不想设置 ThreadContext 属性,记录然后每次我需要记录一些东西时取消设置。我希望扩展的 AdoNetAppenderParameter 会处理它。感谢您的建议。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-21
        • 2013-08-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多