2012-07-13 124 views
6

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

  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()方法中的断点仅在我停止服务时才会被触发。

我已经浏览了大量与此相关的内容,但还没有找到任何答案。 有没有人设法做到这一点,或者我在错误的轨迹上。 P.S.我也尝试过扩展AdoNetAppender,但它不允许你设置参数值。

回答

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); 

如果你看看类,它使用数据[0]作为用户名,所以它依赖于遵循约定。但是,它会将用户名放入其自己的参数中,并将其放入日志数据库表中的单独字段中,而不会将其暂时填充到不安全的ThreadContext中。

2

是的,线程敏捷性意味着您可能无法获得正确的数据。对于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几乎相同!

+1

我很喜欢log4net,我有窗口虽然我们将用户名作为字符串传递,但解决方案中没有访问HttpContext的服务组件。我不想设置ThreadContext属性,每次需要记录日志时记录并取消设置。我希望扩展AdoNetAppenderParameter会照顾它。感谢您的建议。 – 2012-07-15 11:46:05

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>