2012-07-13 15 views
6

Tengo el inicio de sesión de la base de datos utilizando AdoNetAppender. Lo que me gustaría hacer es registrar la identidad del usuario en cada declaración de registro. Sin embargo, no quiero utilizar el parámetro de identidad log4net% estándar por dos razones:registro de base de datos de log4net con parámetros personalizados

  1. log4net advierten que es extremadamente lento ya que tiene que buscar la identidad del contexto.
  2. En algunos componentes del servicio, la identidad estándar es una cuenta de servicio, pero ya hemos capturado la identidad del usuario en una variable y me gustaría utilizarla.

He visto un código donde algunas personas usan el log4net.ThreadContext para agregar propiedades adicionales, pero entiendo que esto es 'inseguro' debido al entrelazado de hilos (y también es un drenaje de rendimiento).

Mi enfoque ha sido extender la clase AdoNetAppenderParameter así:

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

} 

y luego agregar mediante programación esto a la appender actual de este modo:

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

La intención aquí es utilizar un formato estándar para mensajes y analizar la primera parte de la cadena, que siempre debe ser el nombre de usuario. El método FormatValue() del parámetro de apéndice personalizado debe usar solo esa parte de la cadena para que pueda escribirse en un campo separado en la base de datos de registro.

Mi problema es que no se escriben las instrucciones de registro en la base de datos. Curiosamente, cuando se depura, un punto de interrupción en el método FormatValue() solo se activa cuando detengo el servicio.

He navegado a través de un montón de cosas relacionadas con esto, pero todavía no he encontrado ninguna respuesta. ¿Alguien ha logrado hacer esto o estoy en el camino equivocado? P.S. También intenté extender AdoNetAppender, pero no le da acceso a establecer los valores de los parámetros.

Respuesta

4

Después de algunos experimentos, finalmente conseguí que esto funcionara. Asegurar que el registro interno de log4net ayudó a identificar los errores y descargar el código fuente de log4net y revisar la clase AdoNetAppenderParameter mostró cómo debería usarse el método FormatValue().Por lo tanto, aquí está el parámetro appender personalizada modificada:

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

} 

Y para usar esto, agrego que en el archivo de configuración de log4net así:

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

Y por convención, mis declaraciones de registro será algo así como esto:

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

Si nos fijamos en la clase, que utiliza datos [0] como el nombre de usuario, por lo que depende de la convención siguiente. Sin embargo, obtiene el nombre de usuario en su propio parámetro y en un campo separado en la tabla de la base de datos de registro, sin recurrir a rellenarlo temporalmente en el ThreadContext inseguro.

2

Sí, agilidad de hilos significa que es posible que no obtenga los datos correctos. Para log4net, querrá pegarlo en su HttpContext's Items collection.

El problema es que tienes que trabajar un poco para que vuelva a salir cuando sea el momento de escribir esos valores en la base de datos porque siempre he usado Marek's Adaptive Property Provider class para hacer el trabajo duro para mí. Es super fácil de usar como todo lo que tiene que hacer es la siguiente:

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

La propiedad adaptativa sabrá el lugar apropiado para recuperar el valor cuando log4net lo solicite.

Opción Alternativa

Si no está pegado con log4net, NLog hace que el registro para los sitios web de ASP.NET de manera más simples, ya que soportan nativamente aplicaciones ASP.NET. ¡La configuración de uso e incluso es casi idéntica a log4net!

+1

Estoy algo atrapado con log4net y tengo Window s componentes de servicio en la solución que no tienen acceso a HttpContext, aunque pasamos el nombre de usuario como una cadena. Realmente no quiero establecer la propiedad ThreadContext, registrarme y luego desarmarlo cada vez que necesito registrar algo. Esperaba que el AdoNetAppenderParameter extendido se ocuparía de ello. Gracias por tu sugerencia. –

5

También necesitaba para registrar los datos estructurados y no le gustaba usar interfaz de registro de esta manera:

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

Así también escribí clase AdoNetAppenderParameter personalizada para hacer el trabajo:

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

configuración Ahora log4net puede se utilizará para registrar cualquier propiedad del objeto dado:

<?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> 
Cuestiones relacionadas