Ir al contenido principal

Trazabilidad y control de errores - 2ª parte: trazabilidad estructurada

En esta nueva entrega de la publicación Trazabilidad y control de errores vamos a centrarnos en el que es quizá el más interesante aspecto de la trazabilidad. Structured Logging es la técnica que nos permitirá realizar análisis automatizados de nuestra trazabilidad, mediante software de detección de eventos.

En la primera parte de la publicación incorporamos a nuestra plataforma de test Open Source la librería NLog, y configuramos la generación de dos ficheros de texto plano para trazas, uno para dejar la información de los posibles errores no controlados, y otro para los avisos, o warnings. Además asociamos la consola para crear trazas de información para la depuración.

Hoy vamos a configurar la creación de un tercer fichero, que almacenará igualmente información de los errores, pero en este caso guardará la información no en texto plano, sino estructurada, con notación JSON que después podría ser procesada.

Para este fin NLOG nos proporciona el JSON Layout. Vamos a ponernos manos a la obra. Lo primero que haremos será ir al web.config, donde encontramos nuestra configuración para NLOG, y ahí crearemos un nuevo target:

      <target name="jsonlogfileerror" xsi:type="File" fileName="logs/jsonerror_${shortdate}.txt">  
        <layout type='JsonLayout'>
          <attribute name='type' layout='${exception:format=Type}' />
          <attribute name='message' layout='${exception:format=Message}'/>
          <attribute name='data' layout='${exception:Data}'/>
        </layout>
      </target>
      
    </targets>


Definimos los atributos, correspondientes a las informaciones que queremos guardar. recuerden recurrir a la documentación de NLog para verificar todas las opciones posibles, que son muchas. En nuestro caso en nuestro fichero JSON reflejaremos el tipo de excepción y el mensaje, y un valor interesante, la propiedad Data.

Data es una propiedad de las excepciones que ofrece gran versatilidad. Se trata de un diccionario cuyos valores pueden ser informados por el programador en el momento en que se genera la excepción, y podemos usarla, entre otras cosas, para guardar el contexto del error. 

Vamos a seguir con el ejemplo del Test1: multithreading vs singlethreading  que ya usamos en la primera entrega de este post. Aquí tenemos el testcase MultithreadingCaseWithErrors donde estamos generando un par de errores no controlados durante la ejecución del proceso. Vamos a ver cómo en el control de errores informamos la propiedad Data de la excepción:

        private static void CalcFiboWithErrors(int index)
        {
            try
            {
                _logger.Info("Inicia cálculo serie Fibo - Index " + index.ToString());

                if (index == 1)
                {
                    //error 1: 
                    object o2 = null; int i2 = (int)o2;
                }
                else if (index == 2)
                {
                    //error 2: 
                    int a = 0; int b = 1 / a;
                }

                fibo_functions.CalcFibo(200);

                _lst_process_control[index].Estado = objects.process_control.enumEstadoProceso.Finalizado;

                _logger.Info("Finaliza cálculo serie Fibo - Index " + index.ToString());
            }
            catch (Exception ex)
            {
                ex.Data.Add("test", "test1_multithreading_vs_singlethreading");
                ex.Data.Add("case", "CalcFiboWithErrors");
                ex.Data.Add("index", index.ToString());

                _logger.Error(ex, " CalcFiboWithErrors - Index " + index.ToString());

                _lst_process_control[index].Estado = objects.process_control.enumEstadoProceso.Erroneo;
            }
        }

Tan solo configurando un target para el JSON Layout en el web.config, sin tocar nada más, obtenemos un segundo fichero de errores cuyo contenido paso a mostrarles:



Como podrán Uds. deducir, la ventaja de este fichero respecto del anterior en formato de texto plano es la estructura, que nos facilitará el tratamiento automatizado de las excepciones. Para ello disponemos de muy distintas opciones. Una es usar el Database Target, y directamente guardar las trazas en una base de datos dedicada sobre la que poder implementar sus propias consultas.

NLOG también admite protocolos de red, por lo que puede Ud. personalizar su propio servidor HTTP por ejemplo, para implementar un tratamiento personalizado.

Por otro lado buscando en la web también encontrará multitud de herramientas ya desarrolladas, de mayor o menos complejidad.

A modo de ejemplo les presento la solución Seq, de instalación y uso muy agradable. Pasos a seguir:

1. Instalar MSI aquí.
2. Instalar Nuget
Install-Package NLog.Targets.Seq
3. Configurar en web.config:

  <!--NLOG Configuration-->
  <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true">

    <extensions>
      <add assembly="NLog.Targets.Seq"/>
    </extensions>
    
    <variable name="ExceptionLayout" value="${machinename} | ${longdate} | ${level:upperCase=true} | ${message} | ${exception:format=ToString,Properties,Data}  " />

    <targets async="true">
      
      <default-target-parameters xsi:type="File" keepFileOpen="false"/>

      <target name="logconsole" xsi:type="Console" />

      <target name="logfilewarning" xsi:type="File" fileName="logs/warning_${shortdate}.txt" />
      <target name="logfileerror" xsi:type="File" fileName="logs/error_${shortdate}.txt" layout="${ExceptionLayout}" />
      
      <target name="jsonlogfileerror" xsi:type="File" fileName="logs/jsonerror_${shortdate}.txt">  
        <layout type='JsonLayout'>
          <attribute name='type' layout='${exception:format=Type}' />
          <attribute name='message' layout='${exception:format=Message}'/>
          <attribute name='data' layout='${exception:Data}'/>
        </layout>
      </target>

      <target name="seq" xsi:type="Seq" serverUrl="http://localhost:5341" apiKey="">
        <property name="type" value="${exception:format=Type}" />
        <property name="message" value="${exception:format=Message}" />
      </target>
      
    </targets>

    <rules>
      <logger name="*" minlevel="Debug" maxlevel="Warn" writeTo="logconsole" />
      
      <logger name="*" minlevel="Warn" maxlevel="Warn" writeTo="logfilewarning"  />
      <logger name="*" minlevel="Error" writeTo="logfileerror" />
      <logger name="*" minlevel="Error" writeTo="jsonlogfileerror" />
      <logger name="*" minlevel="Error" writeTo="seq" />
      
    </rules>
    
  </nlog>

4. Navegar a http://localhost:5341/ para obtener este resultado:


Bonito ¿verdad?

Llegados a este punto amigos, me pregunto si no sería ésta la ocasión perfecta para montar con C# un servidor UDP, o una WebApi... y construir nuestro servicio personalizado de captación de excepciones.

Déjenme saber si desean una tercera entrega sobre esta fascinante disciplina de la trazabilidad. De pronto en próximas publicaciones les presentaré un nuevo proyecto Open Source ya disponible en HitGub. Se trata de una interpretación personal del patrón Command and Query Responsibility Segregation (o CQRS), útil cuando topamos con sistemas de alta exigencia de lecturas y de escrituras. Les aseguro que es un proyecto apasionante en el que pondremos al límite los recursos del sistema.








Comentarios

Entradas populares de este blog

Unit Testing

Como programador, he aprehendido la importancia de los test unitarios por la vía del dolor. Por la vía de los mantenimientos de software que se van volviendo más y más inmantenibles día tras día, por la vía de corregir un error con urgencia, subirlo a un entorno de producción habiendo hecho las pruebas funcionales justas, y totalmente orientadas a probar la parte que se ha modificado, para darme cuenta -también dolorosamente- que la corrección del error produce otro error en algún otro punto de la solución, que obliga a revertir el cambio, corregir de nuevo con la presión del negocio en aumento, y subir la corrección de la corrección con los dedos cruzados y las rodillas temblorosas, a sabiendas de que ningún mecanismo me garantiza que no vuelva a pasar lo mismo. Sencillamente no se pueden repetir manualmente todas las pruebas, todas las casuísticas que pudieran haberse visto afectadas por el cambio. Cuesta un poco entender el test unitario como una inversión, pero cuando por fin ...

Test 1: Multithreading vs Singlethreading

¡¡ Bien !! Después de unos cuantos arreglos, ya tenemos disponible el primer TEST para nuestra plataforma. Puesto que me gustaría mediante este blog interactuar con las entradas de Stackoverflow , para este primer test he seleccionado este interesante tema:  multithreading slower than singlethreading Y para introducirnos en la filosofía de los TEST, no es mi intención impartir una conferencia sobre este asunto, sino simplemente implementar las casuísticas que nos interesa evaluar, y medir los rendimientos. Vamos a ello. En primer lugar grabamos en BBDD (a través del recién desarrollado interfaz) el test, y los testcases . La cosa quedaría así: A nivel programático, debemos definir una clase cuyo nombre se corresponda con el campo "Clase" del Test. Y tantos métodos como "Casos" asociemos al test, haciendo igualmente coincidir los nombres. En este punto les recuerdo que pueden descargar el proyecto íntegro en  EnigmaLABS - GitHub Para que la comparació...