2010-09-22 21 views
9

He estado buscando una manera de registrar nombres de clases y nombres de métodos como parte de mi infraestructura de registro. Obviamente, me gustaría que sea simple de usar y rápido en tiempo de ejecución. He leído mucho sobre el registro de nombres de clases y métodos, pero me he encontrado con 2 temas.Logging ClassName y MethodName usando log4net para un proyecto .NET

  1. Ese log4net usa una excepción de tiro interno para generar un marco de pila y eso se vuelve costoso si se usa generalmente para todo el registro.
  2. Confusión. Hay mucha literatura por ahí. He intentado un montón y no conseguí algo útil.

Si me toma el pelo por un segundo, me gustaría restablecer.

he creado una clase como esta en mi proyecto

public static class Log { 
    private static Dictionary<Type, ILog> _loggers = new Dictionary<Type, ILog>(); 
    private static bool _logInitialized = false; 
    private static object _lock = new object(); 

    public static string SerializeException(Exception e) { 
     return SerializeException(e, string.Empty); 
    } 

    private static string SerializeException(Exception e, string exceptionMessage) { 
     if (e == null) return string.Empty; 

     exceptionMessage = string.Format(
      "{0}{1}{2}\n{3}", 
      exceptionMessage, 
      (exceptionMessage == string.Empty) ? string.Empty : "\n\n", 
      e.Message, 
      e.StackTrace); 

     if (e.InnerException != null) 
      exceptionMessage = SerializeException(e.InnerException, exceptionMessage); 

     return exceptionMessage; 
    } 

    private static ILog getLogger(Type source) { 
     lock (_lock) { 
      if (_loggers.ContainsKey(source)) { 
       return _loggers[source]; 
      } 

      ILog logger = log4net.LogManager.GetLogger(source); 
      _loggers.Add(source, logger); 
      return logger; 
     } 
    } 

    public static void Debug(object source, object message) { 
     Debug(source.GetType(), message); 
    } 

    public static void Debug(Type source, object message) { 
     getLogger(source).Debug(message); 
    } 

    public static void Info(object source, object message) { 
     Info(source.GetType(), message); 
    } 

    public static void Info(Type source, object message) { 
     getLogger(source).Info(message); 
    } 

...

private static void initialize() { 
     XmlConfigurator.Configure(); 
    } 

    public static void EnsureInitialized() { 
     if (!_logInitialized) { 
      initialize(); 
      _logInitialized = true; 
     } 
    } 
} 

(Si este código parece familiar es porque ha sido tomada de los ejemplos!)

En cualquier caso, a lo largo de mi proyecto utilizo líneas como esta para iniciar sesión:

 Log.Info(typeof(Program).Name, "System Start"); 

Bueno, este tipo de obras. Lo que es más importante, obtengo el nombre de clase pero no el nombre del método. Menos, importante, estoy contaminando mi código con este "tipo de basura". Si copio y pego un fragmento de código entre archivos, etc. ¡el framework de registro estará mintiendo!

Intenté jugar con PatternLayout (% C {1}. {M}) pero eso no funcionó (todo lo que hizo fue escribir "Log.Info" en el registro, porque todo está enrutando a través del Log .X métodos estáticos!). Además, se supone que es lento.

Entonces, ¿cuál es la mejor manera, dada mi configuración y mi deseo de ser simple y rápido?

Apreciar cualquier ayuda con anticipación.

Respuesta

1

Sé que ya tiene un código que depende de log4net, pero ¿ha pensado en otro marco de trabajo de registro que pueda cumplir mejor sus requisitos? Yo personalmente uso NLog para mis propias aplicaciones. Permite un código como este:

class Stuff 
{ 
    private static readonly Logger logger = LogManager.GetCurrentClassLogger(); 

    // ... 

    void DoStuff() 
    { 
     logger.Info("blah blah"); 
    } 
} 

De forma predeterminada, NLog agregará el nombre de clase y el nombre del método a sus mensajes registrados. Tiene una API muy similar a log4net e incluye configuración XML y programática. Puede valer la pena tu tiempo.

9

log4net (y NLog) exponen un método de registro que hace posible "ajustar" sus registradores y aún así corregir la información del sitio de llamadas. Básicamente, al registrador log4net (o NLog) se le debe indicar el Tipo que forma el "límite" entre el código de registro y el código de la aplicación. Creo que llaman a esto el "tipo de registrador" o algo similar. Cuando las bibliotecas obtienen la información del sitio de llamadas, navegan hacia arriba en la pila de llamadas hasta que MethodBase.DeclaringType sea igual (o tal vez AssignableFrom) al "tipo de registrador". El siguiente marco de pila será el código de llamada de la aplicación.

Aquí es un ejemplo de cómo iniciar sesión a través de Nlog desde el interior de una envoltura (log4net sería similar - mira en la documentación log4net para ILogger (no ILOG) Interfaz:

LogEventInfo logEvent = new LogEventInfo(level, _logger.Name, null, "{0}", new object[] { message }, exception); 

    _logger.Log(declaringType, logEvent); 

Dónde declaringType es una variable miembro que se creó algo como esto:

private readonly static Type declaringType = typeof(AbstractLogger); 

Y "AbstractLogger" es el tipo de su envoltorio registrador En su caso, es probable que este aspecto:.

private readonly static Type declaringType = typeof(Log); 

Si NLog necesita obtener la información del sitio de la llamada (debido a los operadores del sitio de llamada en el diseño), navegará hacia arriba hasta que MethodBase.DeclaringType para el cuadro actual sea igual (o AsignableFrom) declaringType. El siguiente cuadro en la pila será el sitio real de llamadas.

Aquí hay un código que funcionará para el registro con un logger "envuelto" log4net. Utiliza la interfaz log4net ILogger y pasa el tipo del registrador "envoltura" para preservar la información del sitio de llamada. Usted no tiene que rellenar una clase de evento/estructura con este método:

_logger.Log(declaringType, level, message, exception); 

Una vez más, "declaringType" es el tipo de su envoltorio. _logger es el registrador log4net, Level es el valor log4net.LogLevel, el mensaje es el mensaje, la excepción es la excepción (si la hay, nulo en caso contrario).

En cuanto a la contaminación de sus sitios de llamadas con Typeof (lo que sea), creo que está atrapado con eso si desea utilizar un solo objeto "Log" estático. Por otra parte, dentro de los métodos de registro "log" del objeto, se puede obtener llamando al igual que el método de la respuesta aceptada en este post

How can I find the method that called the current method?

Ese vínculo se muestra cómo obtener la persona que llama inmediatamente anterior. Si necesita obtener el método que llamó a la función de registro, pero su trabajo se está haciendo un par de capas más profundo, tendrá que subir la pila un número de cuadros en lugar de un solo cuadro.

Teniendo todo esto en conjunto, podría escribir su método de depuración algo como esto (de nuevo, esto es en términos de Nlog porque eso es lo que tengo frente a mí):

public static void Debug(object message) 
{ 
    MethodBase mb = GetCallingMethod(); 
    Type t = mb.DeclaringType; 
    LogEventInfo logEvent = new LogEventInfo(LogLevel.Debug, t.Name, null, "{0}", new object [] message, null); 
    ILogger logger = getLogger(t) As ILogger; 
    logger.Log(declaringType, logEvent) 
} 

Tenga en cuenta que probablemente no encontrará mucha gente aquí en StackOverflow que recomiende escribir una función de conteo de registros como esta (que obtiene explícitamente el método de llamadas para siempre). No puedo decir que lo recomendaría tampoco, pero responde más o menos a la pregunta que me hizo. Si desea utilizar un objeto "Log" estático, tendrá que pasar explícitamente el Type en cada sitio de llamada de registro (para obtener el registrador de clase correcto) o deberá agregar código dentro de la llamada de registro para navegar por el apila y calcula esa información por ti mismo. No creo que ninguna de esas opciones sea particularmente atractiva.

Ahora, habiendo dicho todo eso, puede considerar usar log4net o NLog directamente en lugar de agregar este código complicado (y no necesariamente confiable) para obtener la información del sitio de llamadas. Como señala Matthew, NLog proporciona una forma fácil de obtener el registrador para la clase actual.Para obtener el registrador para la clase actual usando log4net, podría hacer esto en cada clase:

private static readonly log4net.ILog log = log4net.LogManager.GetLogger( 
     System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 

vs esta manera con Nlog:

private static readonly NLog.logger log = NLog.LogManager.GetCurrentClassLogger(); 

Eso es un uso bastante común.

Si no desea depender de una implementación de registro en particular, puede utilizar una de las abstracciones de registro disponibles como Common.Logging (NET) o Simple Logging Facade (SLF).

Incluso si no usa una de estas abstracciones, descargue la fuente de Common.Logging y mire la abstracción para log4net. Mostrará exactamente cómo ajustar un registrador de log4net de modo que la información del sitio de llamada se preserve (y esté disponible para los operadores de disposición).

3

prefiero un patrón como el siguiente, que trabaja con Log4net y APIs similares:

class MyClass 
{ 
    private static readonly ILog logger = LogManager.GetLogger(typeof(MyClass)); 

    void SomeMethod(...) 
    { 
     logger.Info("some message"); 

     ... 

     if (logger.IsInfoEnabled) 
     { 
      logger.Info(... something that is expensive to generate ...); 
     } 
    } 

} 

algunas observaciones:

  • Con este patrón, que está evaluando typeof(MyClass) sólo una vez - en comparación con su muestra en la que está llamando a object.GetType() en cada llamada de registro, independientemente de si el nivel de registro correspondiente está habilitado o no. No es gran cosa, pero en general es deseable que el registro tenga una sobrecarga mínima.

  • Aún necesita utilizar typeof, y asegúrese de no obtener el nombre de clase incorrecto al usar copiar/pegar. Prefiero vivir con esto, porque la alternativa (por ejemplo, LogManager.GetCurrentClassLogger de NLog como se describe en la respuesta de Matthew Ferreira) requiere obtener un StackFrame que tenga una sobrecarga de rendimiento, y requiere que el código de llamada tenga el permiso UnmanagedCode. Por otro lado, creo que sería bueno si C# proporcionara alguna sintaxis en tiempo de compilación para referirse a la clase actual, algo así como la macro de la clase C++ _ _.

  • Debo abandonar cualquier intento de obtener el nombre del método actual por tres razones. (1) Hay una sobrecarga general de rendimiento significativa, y el registro debe ser rápido. (2) Inline significa que no puede obtener el método que cree que está recibiendo. (3) Impone el requisito del permiso UnmanagedCode.

+0

Tenga en cuenta que, de acuerdo con este enlace (http://msdn.microsoft .com/en-us/library/system.diagnostics.stacktrace.aspx), StackTrace en .NET sí requiere el permiso UnmanagedCode. Sin embargo, de acuerdo con este enlace,() StackTrace en Silverlight no tiene la misma restricción. Mi punto es que, si NLog es compatible con Silverlight (como lo será la próxima versión 2.0), GetCurrentClassLogger podría funcionar perfectamente en Silverlight (si se implementa correctamente). Como señala Joe, en .NET o .NET Client Profile "normal", podría encontrarse con la restricción UnmanagedCode. – wageoghe

+0

Perdió pegando el segundo enlace anterior: StackTrace en .NET (http://msdn.microsoft.com/en-us/library/system.diagnostics.stacktrace.aspx) StackTrace en Silverlight (http://msdn.microsoft.com) /en-us/library/system.diagnostics.stacktrace(VS.95).aspx) – wageoghe

2

También hice algunas investigaciones sobre esto y creo que la única manera de hacer esto de manera eficiente es envolver las funciones de registro, tanto como me gusta hacerlo:

public static void InfoWithCallerInfo(this ILog logger, 
    object message, Exception e = null, [CallerMemberName] string memberName = "", 
    [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) 
{ 
    if (!logger.IsInfoEnabled) 
     return; 
    if (e == null) 
     logger.Info(string.Format("{0}:{1}:{2} {3}", sourceFilePath, 
      memberName, sourceLineNumber, message)); 
    else 
     logger.Info(string.Format("{0}:{1}:{2} {3}", sourceFilePath, 
      memberName, sourceLineNumber, message), e); 
} 

Notas:

  • Esta es la función de envoltura que escribí en ILog :: Info, también necesitaría uno alrededor de los otros niveles de registro.
  • Esto requiere Caller Information que solo está disponible a partir de .Net 4.5. La ventaja es que estas variables se reemplazan en tiempo de compilación con literales de cadena, por lo que todo es muy eficiente.
  • Por simplicidad dejó el parámetro sourceFilePath y como es, es probable que prefieren darle formato (la mayor parte del ajuste/toda su trayectoria)