c# exception nlog

c# - Mejor TypeInitializationException(innerException también es nulo)



nlog (6)

Introducción

Cuando un usuario crea un error en la configuración de NLog (como XML no válido), Nosotros (NLog) NLogConfigurationException una NLogConfigurationException . La excepción contiene la descripción de lo que está mal.

Pero a veces esta NLogConfigurationException es "comido" por una System.TypeInitializationException si la primera llamada a NLog es desde un campo / propiedad estática.

Ejemplo

Por ejemplo, si el usuario tiene este programa:

using System; using System.Collections.Generic; using System.Linq; using NLog; namespace TypeInitializationExceptionTest { class Program { //this throws a NLogConfigurationException because of bad config. (like invalid XML) private static Logger logger = LogManager.GetCurrentClassLogger(); static void Main() { Console.WriteLine("Press any key"); Console.ReadLine(); } } }

y hay un error en la configuración, NLog lanza:

throw new NLogConfigurationException("Exception occurred when loading configuration from " + fileName, exception);

Pero el usuario verá:

"Copia los detalles de la excepción al portapapeles":

System.TypeInitializationException no se manejó Mensaje: Se produjo una excepción no controlada del tipo ''System.TypeInitializationException'' en mscorlib.dll Información adicional: El inicializador de tipo para ''TypeInitializationExceptionTest.Program'' produjo una excepción.

¡Así que el mensaje se ha ido!

Preguntas

  1. ¿Por qué no se ve la excepción interna? (probado en Visual Studio 2013).
  2. ¿Puedo enviar más información a la TypeInitializationException ? ¿Te gusta un mensaje? Ya estamos enviando una excepción interna.
  3. ¿Podemos usar otra excepción o hay propiedades en Exception para que se informe más información?
  4. ¿Hay otra manera de dar (más) retroalimentación al usuario?

Notas

Editar :

tenga en cuenta que soy el mantenedor de la biblioteca, no el usuario de la biblioteca. ¡No puedo cambiar el código de llamada!


Dije en un comentario anterior que no puedo reproducir tu problema. Entonces se me ocurrió que solo lo estaba buscando en el cuadro de diálogo emergente de excepción, que no muestra un enlace de detalles de la demostración .

Así que aquí es cómo puede obtener la InnerException todos modos, porque definitivamente está ahí , excepto que Visual Studio no lo informa por alguna razón (probablemente porque está en el tipo de punto de entrada como vendettamit descubierto).

Entonces, cuando ejecutas la rama del probador, obtienes el siguiente cuadro de diálogo:

Y no muestra el enlace Ver detalles .

El detalle de la excepción de Copia al portapapeles tampoco es particularmente útil:

System.TypeInitializationException no fue manejado
Mensaje: se produjo una excepción no controlada de tipo ''System.TypeInitializationException'' en mscorlib.dll
Información adicional: el inicializador de tipo para ''TypeInitializationExceptionTest.Program'' lanzó una excepción.

Ahora, cierre el cuadro de diálogo con el botón Aceptar y diríjase a la ventana de depuración de Locales . Esto es lo que verás:

¿Ver? La InnerException definitivamente está ahí, y has encontrado una peculiaridad de VS :-)


El problema es que la inicialización estática ocurre cuando se hace referencia a la clase por primera vez. En su Program sucede incluso antes del método Main() . Por lo tanto, como regla general, evite cualquier código que pueda fallar en el método de inicialización estática. En cuanto a su problema particular, use un enfoque perezoso en su lugar:

private static Lazy<Logger> logger = new Lazy<Logger>(() => LogManager.GetCurrentClassLogger()); static void Main() { logger.Value.Log(...); }

Por lo tanto, la inicialización del registrador ocurrirá (y posiblemente fallará) cuando acceda por primera vez al registrador, no en un contexto estático loco.

ACTUALIZAR

En última instancia, es una carga para el usuario de su biblioteca el apegarse a las mejores prácticas. Así que si fuera yo lo mantendría como está. Sin embargo, hay pocas opciones si realmente tienes que resolverlo en tu extremo:

1) No lanzar excepciones, este es un enfoque válido en el motor de registro y cómo funciona log4net , es decir,

static Logger GetCurrentClassLogger() { try { var logger = ...; // current implementation } catch(Exception e) { // let the poor guy now something is wrong - provided he is debugging Debug.WriteLine(e); // null logger - every single method will do nothing return new NullLogger(); } }

2) envuelva el enfoque perezoso en torno a la implementación de la clase Logger (sé que su clase Logger es mucho más compleja, por el bien de este problema, supongamos que tiene solo un método Log y se necesita la string className para construir la instancia de Logger .

class LoggerProxy : Logger { private Lazy<Logger> m_Logger; // add all arguments you need to construct the logger instance public LoggerProxy(string className) { m_Logger = new Lazy<Logger>(() => return new Logger(className)); } public void Log(string message) { m_Logger.Value.Log(message); } } static Logger GetCurrentClassLogger() { var className = GetClassName(); return new LoggerProxy(className); }

Se librará de este problema (la inicialización real ocurrirá mientras se llame el primer método de registro y sea un enfoque compatible con versiones anteriores); El único problema es que ha agregado otra capa (no espero una reducción drástica del rendimiento, pero algunos motores de registro están realmente en la micro-optimización).


La única solución que veo ahora es:

mover las inicializaciones estáticas (campos) a un constructor estático con un try catch


La razón por la que veo es porque la inicialización de tipo de la clase de punto de entrada ha fallado. Dado que no se inicializó ningún tipo, el cargador de tipo no tiene nada que informar sobre el tipo fallido en TypeInitializationException .

Pero si cambias el inicializador estático del registrador a otra clase y luego refieres esa clase en el método de Entrada. obtendrá la excepción InnerException on TypeInitialization.

static class TestClass { public static Logger logger = LogManager.GetCurrentClassLogger(); } class Program { static void Main(string[] args) { var logger = TestClass.logger; Console.WriteLine("Press any key"); Console.ReadLine(); } }

Ahora obtendrá la InnerException porque el tipo de entrada se cargó para informar la excepción TypeInitializationException.

Espero que ahora tengas la idea de mantener limpio el Punto de entrada y Arrancar la aplicación desde Main () en lugar de la propiedad estática de la clase de punto de entrada.

Actualización 1

También puede utilizar la Lazy<> para evitar la ejecución de la inicialización de la configuración en la declaración.

class Program { private static Lazy<Logger> logger = new Lazy<Logger>(() => LogManager.GetCurrentClassLogger()); static void Main(string[] args) { //this will throw TypeInitialization with InnerException as a NLogConfigurationException because of bad config. (like invalid XML) logger.Value.Info("Test"); Console.WriteLine("Press any key"); Console.ReadLine(); } }

Alternativamente, intente Lazy<> en el LogManager para la creación de instancias del registrador, de modo que la inicialización de la configuración ocurra cuando realmente ocurra la primera declaración de registro.

Actualización 2

Analicé el código fuente de NLog y parece que ya está implementado y tiene sentido. De acuerdo con los comentarios sobre la propiedad "NLog no debe lanzar una excepción a menos que la propiedad LogManager.ThrowExceptions lo especifique en LogManager.cs".

Arreglo: en la clase LogFactory, el método privado GetLogger () tiene la instrucción de inicialización que está causando que ocurra la excepción. Si introduce una captura de prueba con la verificación de la propiedad ThrowExceptions , puede evitar la excepción de inicialización.

if (cacheKey.ConcreteType != null) { try { newLogger.Initialize(cacheKey.Name, this.GetConfigurationForLogger(cacheKey.Name, this.Configuration), this); } catch (Exception ex) { if(ThrowExceptions && ex.MustBeRethrown()) throw; } }

También sería bueno tener estas excepciones / errores almacenados en algún lugar para que pueda rastrearse por qué falló la inicialización del registrador porque se ignoraron debido a ThrowException .


Solo señalaré el problema subyacente con el que estás lidiando aquí. Estás luchando contra un error en el depurador, tiene una solución muy simple. Use Herramientas> Opciones> Depuración> General> marque la casilla de verificación "Usar modo de compatibilidad administrada". También desmarque Just My Code para el informe de depuración más informativo:

Si se marca Just My Code, el informe de excepción es menos informativo, pero aún puede ser detallado fácilmente haciendo clic en el enlace "Ver detalles".

El nombre de la opción es innecesariamente críptico. Lo que realmente hace es decirle a Visual Studio que use una versión anterior del motor de depuración. Cualquiera que use VS2013 o VS2015 tendrá este problema con el nuevo motor, posiblemente VS2012. También la razón básica por la que este problema no se abordó en NLog anteriormente.

Si bien esta es una solución muy buena, no es exactamente fácil de descubrir. A los programadores tampoco les gustaría particularmente usar el motor antiguo, las funciones nuevas y brillantes como la depuración del valor de retorno y E + C para el código de 64 bits no son compatibles con el motor antiguo. Es difícil adivinar si esto es realmente un error, un descuido o una limitación técnica en el nuevo motor. Esto es excesivamente feo, así que no dude en etiquetarlo "error", le recomiendo que lo lleve a connect.microsoft.com. Todo el mundo estará por delante cuando se arregle, me rascé la cabeza por lo menos una vez que recuerdo. Se profundizó usando Debug> Windows> Exceptions> Excepciones CLR marcadas en ese momento.

Una solución para este comportamiento tan desafortunado seguramente será fea. Debe retrasar el aumento de la excepción hasta que la ejecución del programa haya avanzado lo suficiente. No conozco bien la base de código, pero retrasar el análisis de la configuración hasta que el primer comando de registro deba encargarse de ello. O almacene el objeto de excepción y tírelo en el primer comando de registro, probablemente más fácil.


System.TypeInitializationException siempre o casi siempre ocurre cuando no se inicializa correctamente los miembros estáticos de una clase.

LogManager.GetCurrentClassLogger() verificar LogManager.GetCurrentClassLogger() través del depurador. Estoy seguro de que el error se produce dentro de esa parte del código.

//go into LogManager.GetCurrentClassLogger() method private static Logger logger = LogManager.GetCurrentClassLogger();

También te sugiero que revises tu app.config y te asegures de que no hayas incluido nada incorrecto.

System.TypeInitializationException produce cuando un constructor estático lanza una excepción, o cuando intenta acceder a una clase donde el constructor estático lanzó una excepción.

Cuando .NET carga el tipo, debe preparar todos sus campos estáticos antes de la primera vez que use el tipo. A veces, la inicialización requiere ejecutar código. Cuando se produce un error en ese código, se obtiene una System.TypeInitializationException .

Segun los System.TypeInitializationException

Cuando un inicializador de clase no inicializa un tipo, se crea una excepción TypeInitializationException y se pasa una referencia a la excepción generada por el inicializador de clase del tipo. La propiedad InnerException de TypeInitializationException contiene la excepción subyacente. TypeInitializationException utiliza HRESULT COR_E_TYPEINITIALIZATION, que tiene el valor 0x80131534. Para obtener una lista de valores de propiedad iniciales para una instancia de TypeInitializationException, consulte los constructores TypeInitializationException.

1) La InnerException no es visible, es muy InnerException para este tipo de excepción. La versión de Visual Studio no importa (asegúrese de que la opción "Habilitar el asistente de excepción" esté marcada - Tools> Options>Debugging>General )

2) Por lo general, la TypeInitializationException oculta la excepción real que puede verse a través de InnerException. Pero el siguiente ejemplo de prueba muestra cómo puede rellenar la información de la excepción interna:

public class Test { static Test() { throw new Exception("InnerExc of TypeInitializationExc"); } static void Main(string[] args) { } }

Pero a veces esta NLogConfigurationException es "consumida" por una excepción System.TypeInitializationException si la primera llamada a NLog es de un campo / propiedad estática.

Nada extraño. Alguien perdió el try catch bloque en alguna parte.