txt practices net log errores error crear best c# logging tracing

practices - Registro/seguimiento condicional de C#



trace c# (9)

Quiero agregar el registro o el seguimiento a mi aplicación C #, pero no quiero que se sobren la sobrecarga de formatear la cadena ni calcular los valores si el nivel de verbosidad del registro es tan bajo que el mensaje no se registrará.

En C ++, puede usar el preprocesador para definir macros que evitarán que el código se ejecute de la siguiente manera:

#define VLOG(level,expr) if (level >= g_log.verbosity) { g_log.output << expr; }

Usado así:

VLOG(5,"Expensive function call returns " << ExpensiveFunctionCall());

¿Cómo se hace eso en C #?

He leído los documentos de Microsoft que explican las funciones Trazar y Depurar aquí , y afirman que al utilizar #undef DEBUG y #undef TRACE se elimina todo el código de rastreo y depuración del ejecutable producido, pero, ¿realmente elimina toda la llamada? Es decir, si escribo

System.Diagnostics.Trace.WriteLineIf(g_log.verbosity>=5,ExpensiveFunctionCall());

no llamará a mi costosa función si no defino TRACE? ¿O hace la llamada, luego decide que no rastreará nada?

De todos modos, incluso si lo elimina, esto es inferior a la macro de C ++ porque no puedo hacer que esa gran llamada fea se parezca a mi simple llamada VLOG () en C ++ y aún así evitar la evaluación de parámetros, ¿o sí? Tampoco puedo evitar la sobrecarga definiendo la verbosidad más baja en el tiempo de ejecución como puedo en C ++, ¿verdad?



Invocará la llamada costosa porque podría tener los efectos secundarios que se desean.

Lo que puede hacer es decorar su costoso método con un atributo [Condicional ("TRACE")] o [Conditional ("DEBUG")]. El método no se compilará en el ejecutable final si la constante DEBUG o TRACE no está definida, ni las llamadas ejecutarán el método costoso.


No estoy seguro, pero tú puedes descubrir la respuesta tú mismo.

Conviértalo en una función REALMENTE costosa (como Thread.Sleep(10000) ) y cronometra la llamada. Si lleva mucho tiempo, de todos modos llama a su función.

(Puede ajustar la llamada Trace.WriteLineIf() con #if TRACE y #endif y volver a probarla para una comparación base).


Para responder a una de sus preguntas, todas las llamadas a métodos que deben evaluarse para llamar a Trace.WriteLine (o sus hermanos / primos) no se llaman si se compila Trace.WriteLine. Así que adelante y ponga sus costosas llamadas de método directamente como parámetros a la llamada de Trace y se eliminará en tiempo de compilación si no define el símbolo TRACE.

Ahora para su otra pregunta con respecto a cambiar su verbosidad en tiempo de ejecución. El truco aquí es que Trace.WriteLine y métodos similares toman ''params object [] args'' para sus argumentos de formateo de cadenas. Solo cuando la cadena se emite realmente (cuando la verbosidad se establece suficientemente alta) el método llama a ToString en esos objetos para obtener una cadena de ellos. Así que un truco que juego a menudo es pasar objetos en lugar de cadenas totalmente ensambladas a estos métodos, y dejar la creación de cadenas en el ToString del objeto que paso. De esta forma, el impuesto de rendimiento en tiempo de ejecución solo se paga cuando el registro está realmente ocurriendo. y te da la libertad de cambiar la verbosidad sin recompilar tu aplicación.


Por su comentario

"porque no puedo hacer que esa gran llamada fea se parezca a mi simple llamada VLOG () en C ++". Podría agregar una declaración de uso como ejemplo a continuación.

using System.Diagnostics; .... Trace.WriteLineIf(.....)

Según tengo entendido, eliminará las líneas que contengan Trace, si no define el símbolo de Trace.


Una solución que me ha funcionado es usar una clase singleton . Puede exponer sus funciones de registro y puede controlar su comportamiento de manera eficiente. Llamemos a la clase ''AppLogger''. Ella es un ejemplo

public class AppLogger { public void WriteLine(String format, params object[] args) { if ( LoggingEnabled ) { Console.WriteLine( format, args ); } } }

Tenga en cuenta que las cosas de Singleton quedan fuera del ejemplo anterior. Hay TONELADAS de buenos ejemplos en los tubos. NO lo interesante es cómo admitir multi-threading. Lo he hecho así: (abreviado por brevedad, jajajaja)

public static void WriteLine( String format, params object[] args ) { if ( TheInstance != null ) { TheInstance.TheCreatingThreadDispatcher.BeginInvoke( Instance.WriteLine_Signal, format, args ); } }

De esta forma, cualquier hilo puede iniciar sesión y los mensajes se manejan en el hilo de creación original. O puede crear un hilo especial solo para manejar la salida de registro.


ConditionalAttribute es tu mejor amigo. La llamada se eliminará por completo (como si los sitios de llamadas fueran # if''d) cuando #define no está establecido.

EDITAR: alguien puso esto en un comentario (¡gracias!), Pero vale la pena señalar en el cuerpo de la respuesta principal:

Todos los métodos de la clase Trace están decorados con Conditional ("TRACE"). Acabo de ver esto usando un reflector.

Lo que significa que Trace.Blah (... costoso ...) desaparece por completo si TRACE no está definido.


Dos de estas respuestas (la de Andrew Arnott y la de Brian) respondieron una parte de mi pregunta. ConditionalAttribute que se aplica a los métodos de clase Trace y Debug hace que todas las llamadas a los métodos se eliminen si TRACE o DEBUG son # undef''d, incluida la costosa evaluación de parámetros. ¡Gracias!

Para la segunda parte, si puede eliminar por completo todas las llamadas en tiempo de ejecución, no en tiempo de compilación, encontré la respuesta en log4net fac . Según ellos, si establece una propiedad de solo lectura en el momento del inicio, el tiempo de ejecución compilará todas las llamadas que no pasen la prueba. Esto no le permite cambiarlo después del inicio, pero está bien, es mejor que eliminarlos en el momento de la compilación.


Toda la información sobre Conditional (Trace) es buena, pero supongo que su verdadera pregunta es que desea dejar las llamadas de Trace en su código de producción pero (normalmente) desactivarlas en tiempo de ejecución a menos que experimente un problema.

Si está utilizando TraceSource (que creo que debería, en lugar de llamar directamente a Trace porque le da un control más preciso sobre el seguimiento en un nivel de componente en tiempo de ejecución), puede hacer algo como esto:

if (Component1TraceSource.ShouldTrace(TraceEventType.Verbose)) OutputExpensiveTraceInformation()

Esto supone que puede aislar los parámetros de seguimiento en otra función (es decir, depende principalmente de miembros de la clase actual en lugar de costosas operaciones en los parámetros de la función en la que se encuentra este código).

La ventaja de este enfoque es que JITer compila según la función que necesita, si el "si" se evalúa como falso, la función no solo no se invocará, sino que tampoco se JIT. La desventaja es (a) que ha separado el conocimiento del nivel de seguimiento entre esta llamada y la función OutputExpensiveTraceInformation (por lo que si, por ejemplo, cambia el TraceEventType allí para que sea TraceEventType.Information, por ejemplo, no funcionará porque nunca lo hará). incluso llámelo a menos que TraceSource esté habilitado para el rastreo de nivel detallado en este ejemplo) y (b) escriba más código.

Este es un caso donde parecería que un preprocesador similar a C ayudaría (ya que podría asegurarse, por ejemplo, que el parámetro para ShouldTrace y para la eventual llamada TraceEvent son los mismos), pero entiendo por qué C # no lo hace incluir eso.

La sugerencia de Andrew de aislar operaciones costosas en los métodos .ToString de los objetos que pasas a TraceEvent también es buena; en ese caso, podría, por ejemplo, desarrollar un objeto que se acaba de usar para el Trace al que pasa los objetos que desea construir una representación de cadena cara y aislar ese código en el método ToString del objeto de traza en lugar de hacerlo en el lista de parámetros a la llamada TraceEvent (que hará que se ejecute incluso si TraceLevel no está habilitado en tiempo de ejecución).

Espero que esto ayude.