jueves, 26 de enero de 2012

[Winforms] Control global de Errores – Implementar Log


Introducción
He notado en varias oportunidades que en ciertas circunstancias surgen errores no controlador que son difíciles de rastrear
Estos es consecuencia de un descuido, al no agregar correctamente en todos los puntos importantes el try..catch, o simplemente por la lógica de la aplicación por se grande implicaría un gran esfuerzo poner en cada evento el control de errores.
Es por eso que definir un control global a nivel de aplicación podría ayudar, atrapando el problema y registrando que sucedió y donde, esta información es muy preciada cuando se esta perdido y no se sabe que produce el problema, mas cuando se esta en el entorno de producción y no se cuenta con una herramienta de debug
En este artículo se trataran los siguientes temas:
  1. Control global de errores
  2. Log usando System.IO.Log
  3. Log usado Log4Net

1- Control Global Errores
El implementar la lógica para controlar globalmente los errores no es nada difícil, el truco esta en adjuntarse a un evento, concretamente:
Un buen lugar para realizar esta asignación del evento es el archivos Program.cs, dentro del método Main()
[C#]
01.[STAThread]
02.static void Main()
03.{
04.Application.EnableVisualStyles();
05.Application.SetCompatibleTextRenderingDefault(false);
06. 
07.Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
08. 
09.Application.Run(new Form1());
10.}
11. 
12.static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
13.{
14.MessageBox.Show(e.Exception.Message);
15.}
Para lograr esto mismo en vb.net requiere de algunos paso adicionales, ya que vb.net no brinda un acceso transparente al método Main, ni permite que este sea asignado como inicio de la aplciacion, pero en este articulo
explico como destrabar esta situación para poder así implementarlo como lo harían en c#, definiendo el Main() como inicio de la aplicación.
[VB.NET]
01.<STAThread()> _
02.Friend Shared Sub Main()
03. 
04.Application.EnableVisualStyles()
05.Application.SetCompatibleTextRenderingDefault(False)
06. 
07.AddHandler Application.ThreadException, AddressOf Application_ThreadException
08. 
09.Application.Run(New Form1())
10. 
11.End Sub
12. 
13.Private Shared Sub Application_ThreadException(ByVal sender As ObjectByVal AsSystem.Threading.ThreadExceptionEventArgs)
14. 
15.MessageBox.Show(e.Exception.Message)
16. 
17.End Sub
El código en ambos lenguajes preserva el mismo concepto, pero la forma en como se adjunta el evento difiera bastante.
Entonces, ahora si sucediera algo como lo reflejado en la imagen
O sea, si se ingresara caracteres en el calculo, el no atrapar el error en un bloque try..catch, haría que la aplicación finalice de forma brusca cerrándose, pero al tener definido el evento ThreadException, entraría en acción tomando el error y mostrando el mensaje.
Con estos simples paso ya tenemos el control global de errores implementado.

2- Log usando System.IO.Log
Si bien en una primer instancia hacer uso de un mensaje podría ayudar a detectar el problema en la aplicación, este podría evolucionar en un log a un archivo para dejar tracking de lo sucedido, en este caso no solo se pondría el mensaje del problema, sino que además se podría agregar el StackTrace para poder analizar que métodos se fueron ejecutando hasta causar el fallo.
En este caso el log a un archivos será muy simple
[C#]
01.static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
02.{
03.using(FileRecordSequence record = new FileRecordSequence("application.log", FileAccess.Write))
04.{
05. 
06.string message = string.Format("[{0}]Message::{1} StackTrace:: {2}", DateTime.Now,
07.e.Exception.Message,
08.e.Exception.StackTrace);
09. 
10.record.Append(CreateData(message), SequenceNumber.Invalid,
11.SequenceNumber.Invalid,
12.RecordAppendOptions.ForceFlush);
13.}
14.}
15. 
16. 
17.private static IList<ArraySegment<byte>> CreateData(string str)
18.{
19.Encoding enc = Encoding.Unicode;
20. 
21.byte[] array = enc.GetBytes(str);
22. 
23.ArraySegment<byte>[] segments = new ArraySegment<byte>[1];
24.segments[0] = new ArraySegment<byte>(array);
25. 
26.return Array.AsReadOnly<ArraySegment<byte>>(segments);
27.}
[VB.NET]
01.Private Shared Sub Application_ThreadException(ByVal sender As ObjectByVal AsSystem.Threading.ThreadExceptionEventArgs)
02. 
03.Using record As New FileRecordSequence("application.log", FileAccess.Write)
04. 
05.Dim message As String String.Format("[{0}]Message::{1} StackTrace:: {2}", DateTime.Now, e.Exception.Message, e.Exception.StackTrace)
06. 
07.record.Append(CreateData(message), SequenceNumber.Invalid, SequenceNumber.Invalid, RecordAppendOptions.ForceFlush)
08.End Using
09. 
10.End Sub
11. 
12. 
13.Private Shared Function CreateData(ByVal str As StringAs IList(Of ArraySegment(Of Byte))
14. 
15.Dim enc As Encoding = Encoding.Unicode
16. 
17.Dim _array As Byte() = enc.GetBytes(str)
18. 
19.Dim segments As ArraySegment(Of Byte)() = New ArraySegment(Of Byte)(0) {}
20.segments(0) = New ArraySegment(Of Byte)(_array)
21. 
22.Return Array.AsReadOnly(Of ArraySegment(Of Byte))(segments)
23. 
24.End Function
Ahora el evento global de errores tiene bastante mas código, en donde se arma un mensaje bastante mas útil, el cual será enviado a la funcionalidad de log para registrar el suceso.
Para recuperar el archivo, solo deben ir a la ubicación donde esta el .exe, en este caso debería esta en la carpeta \bin\Debug del proyecto.
Lo mas probable es que el archivo no se legible a simple vista porque este sistema de log trabaja con el concepto de entradas de registros, es por eso que se confecciono un formulario muy simple para poder recuperar la información de forma visual.
[C#]
[VB.NET]

3- Log usado Log4Net
Como alternativa siempre es bueno conocer algún otro framework de log, y log4net es uno con muchas posibilidades y configuraciones.
Lo bueno de este es que al ser configurable uno puede activarlo o cambiar el medio donde se quiere loguear sin tocar el código, hoy se loguea a un archivo, el día de mañana al visor de suceso de windows y si esto no converse, se podría enviar a una tabla en una db, y lo bueno de todo esto se puede lograr sin cambiar el código.

No hay comentarios:

Publicar un comentario