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:
- Control global de errores
- Log usando System.IO.Log
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#]
[STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException); Application.Run(new Form1()); } static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { MessageBox.Show(e.Exception.Message); }
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 aplicación, pero en este articulo
Winforms, realizar tareas antes de inicializar aplicación
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]
<STAThread()> _ Friend Shared Sub Main() Application.EnableVisualStyles() Application.SetCompatibleTextRenderingDefault(False) AddHandler Application.ThreadException, AddressOf Application_ThreadException Application.Run(New Form1()) End Sub Private Shared Sub Application_ThreadException(ByVal sender As Object, ByVal e As System.Threading.ThreadExceptionEventArgs) MessageBox.Show(e.Exception.Message) 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#]
static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { using(FileRecordSequence record = new FileRecordSequence("application.log", FileAccess.Write)) { string message = string.Format("[{0}]Message::{1} StackTrace:: {2}", DateTime.Now, e.Exception.Message, e.Exception.StackTrace); record.Append(CreateData(message), SequenceNumber.Invalid, SequenceNumber.Invalid, RecordAppendOptions.ForceFlush); } } private static IList<ArraySegment<byte>> CreateData(string str) { Encoding enc = Encoding.Unicode; byte[] array = enc.GetBytes(str); ArraySegment<byte>[] segments = new ArraySegment<byte>[1]; segments[0] = new ArraySegment<byte>(array); return Array.AsReadOnly<ArraySegment<byte>>(segments); }
[VB.NET]
Private Shared Sub Application_ThreadException(ByVal sender As Object, ByVal e As System.Threading.ThreadExceptionEventArgs) Using record As New FileRecordSequence("application.log", FileAccess.Write) Dim message As String = String.Format("[{0}]Message::{1} StackTrace:: {2}", DateTime.Now, e.Exception.Message, e.Exception.StackTrace) record.Append(CreateData(message), SequenceNumber.Invalid, SequenceNumber.Invalid, RecordAppendOptions.ForceFlush) End Using End Sub Private Shared Function CreateData(ByVal str As String) As IList(Of ArraySegment(Of Byte)) Dim enc As Encoding = Encoding.Unicode Dim _array As Byte() = enc.GetBytes(str) Dim segments As ArraySegment(Of Byte)() = New ArraySegment(Of Byte)(0) {} segments(0) = New ArraySegment(Of Byte)(_array) Return Array.AsReadOnly(Of ArraySegment(Of Byte))(segments) 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] |
Muy buen articulo... Gracias por compartir
ResponderEliminarHola, disculpa pero no logro entender como se utiliza tu aplicacion, ejecuto la demo del calculo, ingreso un numero y una letra y el error me dice: "La cadena de entrada no tiene el formato correcto.", luego intento abrir la ventana de "LogViewer", para supuestamente leer el error, pero no veo nada!, que estoy haciendo mal?
ResponderEliminargracias!!
hola hgjhgj
ResponderEliminarque raro, deberias ver el log del error
si editas con el notepad el archivo donde se registro el error que generaste, puedes ver que hay texto alli, aunque no se entieneda del todo el contenido del archivo, pero hay algo que indique un registro del problema
has validado que tengas permiso de escritura al archivo del log
saludos
La explicación del Application.ThreadException es exactamente lo que estaba buscando, mil gracias!!!
ResponderEliminarNo tiene nada que ver con el articulo pero tengo una duda:
ResponderEliminartengo que generar un reporte similar a esto:
campo 1 campo2
11 4 5
12 1 2
donde campo uno y campo 2 son enfermedades y 11 y 12 son tipos de dientes como puedo contar cuantos dientes de tipo 11 tienen la enfermedad tipo 1 y cuantos dientes tipo 11 tienen la enfermedad tipo dos sin tener que hacer una consulta como esta:
select count(campo1) from tabla campo=12
para saber cuantos hay celda por celda
con respecto a la tabla mas claro seria
ResponderEliminarcampo1 campo2
11 1 2
12 3 1
hola K-loca
ResponderEliminarel tema es que sino entendi mal, las enfermedades del duente deberian ser registro y no campo
porque usas campos para la enfermedad
no seri mas simple si la tabla es del tipo
TipoDienteEnfermedades (tabla)
idtipoenfermedad PK
TipoDiente
idEnfermedad FK
Enfermedades(tabla)
idenfermedad PK
descripcion
entonces los datos podrian ser
idtipoenfermedad | diente | idEnfermedad
1 | 11 | 1
2 | 11 | 2
3 | 12 | 2
Nota: no respeta muy bien el diseñod e la tabla por eso use "|" para separar cada campo
como veras ahora es mas simple de contar porque als enfermedades son un campo relacionado con la tabla de enfermedades
ahora si puedes agrupar
SELECT E.descripcion, COUNT(*) FROM DienteEnfermedades DE INNER JOIN Enfermedades E
ON DE.idenfermedad = E.idenfermendad
WHERE tipodiente = 11
saludos
gracias
ResponderEliminarsobre mi duda a ver si me pude explicar mejor aqui esta:
ResponderEliminarhttp://jjcaloca.blogspot.com/
como haría para agregar a este log de errores por ejemplo el nombre del formulario donde se originó el error, para poder identificar mejor la fuente del mismo..???
ResponderEliminarhola FEMAVEL
ResponderEliminarsi logueas el StackTrace del error alli deberia indicar en que clase (o sea en que form) se genero el error
solo que el StackTrace menciona toda la pila de llamadas incluyendo los metodos que se invocaron, se que no es el form directamente, pero es que no se puede determinar el form que genera el error
salvo que en cada metodo del form definas un try...catch y crees un propio exception, o sea
try{
} catch(Exception ex){
throw new Exception(string.Format("{0} - Form:{1}", ex.Message, this.Name), ex)
}
esto lo deberias poner en cada evento del form, para asi con el this.Name armas un mensaje custom poniendo el nombre del formulario que genera el problema
saludos
Hola Leandro, una consulta, y si es desde un método desde una clase que genera el error no me reconoce el THIS.NAME , que otra alternativa hay?
Eliminarhola buenas estoy queriendo poner en practica tu ejemplo en vb creo una clase igual a la del ejemplo y creo el archivo log pero no logro comprender en que momento ejecutas la clase ya que en mi caso me muestra el error pero no logro sincronizar la clase
ResponderEliminarestado probando en guardar este es el evento
Private Sub bt_guardar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bt_guardar.Click
Try
If validarmelascajas(ErrorProvider1, txt_codiagronomo, txt_apellidos, txt_nombres, txt_telefono, txt_zona) Then
Exit Sub '<-- sale del subprocedimiento
End If
Dim result As DialogResult
result = MessageBox.Show("¿Seguro de Guardar el agronomo?", "Mensaje", MessageBoxButtons.OKCancel, MessageBoxIcon.Question)
If result = DialogResult.OK Then
cn.Open()
Dim guardar As New SqlCommand("SP_AGRONOMO_GUARDAR", cn)
guardar.CommandType = CommandType.StoredProcedure
guardar.Parameters.AddWithValue("@CODIAGRON", txt_codiagronomo.Text)
guardar.Parameters.AddWithValue("@APELLIDO_AGRONOMO", txt_apellidos.Text)
guardar.Parameters.AddWithValue("@NOMBRE_AGRONOMO", txt_nombres.Text)
guardar.Parameters.AddWithValue("@TELEF_AGRONOMO", txt_telefono.Text)
guardar.Parameters.AddWithValue("@ZONA", txt_zona.Text)
guardar.Parameters.AddWithValue("@ESTADO_AGRONOMO", ck_estado.Checked)
'parate para guardar la accion del usuario
Dim FECH As Date = Frm_Principal2.fecha.Text
guardar.Parameters.AddWithValue("@USER_CREA", Frm_Principal2.coduser.Text)
guardar.Parameters.AddWithValue("@FECHA_CREA", FECH)
Dim msgparam As New SqlParameter("@msg", SqlDbType.VarChar, 100)
msgparam.Direction = ParameterDirection.Output
guardar.Parameters.Add(msgparam)
Dim rowsAffected As Integer = guardar.ExecuteNonQuery()
Dim mensaje As String = ""
If rowsAffected > 0 Then
mensaje = Convert.ToString(guardar.Parameters("@msg").Value)
MessageBox.Show(mensaje, "Mensaje", MessageBoxButtons.OK)
Else
mensaje = Convert.ToString(guardar.Parameters("@msg").Value)
MessageBox.Show(mensaje, "Mensaje", MessageBoxButtons.OK)
End If
cn.Close()
bt_cancelar_Click(Nothing, Nothing)
Else
bt_cancelar_Click(Nothing, Nothing)
End If
Catch ex As Exception
cn.Close()
MessageBox.Show(ex.Message, "Mensaje", MessageBoxButtons.OK)
Finally
Frm_Principal2.fecha.Text = Date.Now
End Try
End Sub
el error que me genera en el siguiente
conversion strin a date ese error ya se a que se debe, pero me gustaria ocupar la forma que tu lo haces para que el usuario solo vea un mensaje prediseñado y un pueda ver el ultimo error generado o secrea un archivo por frm
hola Fredy
ResponderEliminarrecuerda que si ejecutas el codigo desde el propio VS este mostrara igualmente el error porque estas depurando, ejecuta sin debug desde el VS (o sino desde el .exe que se genera) y seguramente ya no tendras ningun mensaje de error
si quieres poner el control centralizado no definas ningun try...catch en el evento o metodo que desarrolles deja que se encargue el evento global que asignas en el Main() asi podras tomar el error loguearlo a un archivo y mostrar un mensjae estandar, en resumen quita el try..catch del codigo
saludos
hola Leandro.
ResponderEliminarestaba en otras cosas, pero ya le saque varias galladas a la aplicacion que hay que mejorar, y estado probando y no puedo aplicar tu codigo en el proyecto, cuando genero un excepcion no crea el archivo log, como podrias revisar el proyecto para que veas que es lo q estoy realizando mal
hola Fredy
ResponderEliminarpara crear un archivo de log quizas no es buena idea usar la que implemente en el articulo, sino usar algun framework de log como ser log4net o sino System.Diagnostics
http://www.3engine.net/wp/2011/01/como-escribir-facilmente-un-fichero-log-en-net-framework/
esta es una mejor alternativa
saludos
Hola Leandro, Estoy trabajando en capas. Mi pregunta es, con esto me aseguro que se atrapen todas las excepciones de mi aplicación?.
ResponderEliminarPregunta adicional:
Yo en mi capa de negocios suelo lanzar excepciones con throw new por lo cual lo atrapo con un try catch desde mi capa de presentación.. con esto que planteas aca ya no deberia tener los try catch en mi capa de presentacion?
Si Señor
ResponderEliminarexacto, todos los errores que lleguen a la UI y que no fueorn controlados por un try...catch entraran por el control global de errores
puedes ponerlo si algun caso particular lo requiere, pero ya no sera necesario definirlo en todos los metodo de los eventos
saludos
Hola Leandro,
ResponderEliminarPerfecto, lo probé y es todo como vos decís!!
saludos
Hola leandro, te pregunto algo mas que me quedo..
ResponderEliminarTengo un método para desencriptar campos de la BD.
El algortimo es AES
el Application.ThreadException me detecta los errores de todos los eventos menos de este método.. a que se debe?
El error:
Longitud no valida para una matriz o cadena de caracteres Base-64
Este error aparece por que cambio los caracteres de la encriptacion.
Si en este método yo coloco
try
'implementacion
catch ex as exception
msgbox(ex.message)
end try
el error me lo reconoce y lo muestra.. Lo dejo así?
hola Si Señor
ResponderEliminarlo que veo alli falla no es la desencriptacion sino la aplicacion del Base64, validaste que el campo no este nullable ? o que el campo no tenga datos
quizas si no viene datos y aplicar la desencriptacion en ese momento falle
ademas es raro que el try..catch no tome el error, estas seguro que se produce dentro de la definicion de este bloque de codigo, no sera que se produce fuera
saludos
Hola Leandro, ahi me reconoce el error.
ResponderEliminarEsto que planteas aca solo sirve para los errores de los formularios?
Porque recien haciendo una prueba de conexion a mi base de datos no me tomo la excepcion sqlexception
hay alguna forma de controlar tambien estos errores?
hola Si Señor
ResponderEliminaresta tecnica controla los errores de toda la aplicacion, si se produce en alguna otra libreria que es usada desde la UI tambien la controlara
las SqlException deberia tambien atraparlas, pero ojo que no engañe el hecho que el VS se detenga en la exception, porque esto sucede al estar dentro del VS, lo importante es si dejas seguir el codigo que ingrese al evento que controla de forma global los errores
saludos
Hola Leandro,
ResponderEliminarNo me toma las excepciones que ocurren dentro del submain
tengo esto
AddHandler Application.ThreadException, AddressOf Application_ThreadException
'Rutina para validar registros de la BD (Aca tira error no controlado)
Application.Run(New Form1())
despues al iniciar la aplicacion si me detecta todos los errores!!
solamente el del submain no!
hola Si Señor
ResponderEliminarintenta asignando el evento
AppDomain.UnhandledException (Evento)
en el link hay ejemplos de como usarlo
saludos
Hola @Leandro como se llama el archivo de errores.
ResponderEliminarhola Pedro
ResponderEliminarno te preocupes por el archivo, la parte del log no tienes que implementarla solo asigna los eventos al Application.ThreadException
y muestra el mensaje en un messagebox
la idea es ver si se puede capturar el problema desde alli, despues ves cual es la mejor forma de loguearlo
saludos
Me marca un error en "FileRecordSequence".
ResponderEliminarHe de añadir una directiva using o una referencia de ensamblado.
¿Cómo?
Gracias.
hola Dudas
ResponderEliminarsi tienes que agregar la referencia a System.IO.Log, seguro veras en los ejemplos que tengo ese using definido
igualmente si es para un log recomendaria usar
Cómo escribir facilmente un fichero Log en .NET Framework
es mas facil de crear el log
saludos
Otra opción es NLog, les dejo este link con un turial básico pero explicado para principiantes, cortesía de Franklin (el autor): http://redk33.wordpress.com/2012/06/22/agregando-logs-a-nuestro-proyecto/#comment-39
ResponderEliminarhola César
ResponderEliminarsi es verdad que la funcionalidad de System.IO no es muy practica para loguear
esta libreria que comentas parece interesante, aunque suelo utilizar log4net
saludos
hola mi problema es que me sale un error de windows xp que dice asi error in main app- se produjo una excepcion en el inicializador de tipo de y me aparece un cuadrado entre comillas que es?
ResponderEliminarhola mark
ResponderEliminarla verdad que no tengo ni idea, quizas si tomas una imagen del problema ayude
esta ventana tiene un boton de detalle?
has creando un control de errores como explico en el articulo
se produce cuando ejecutas desde el VS o cuando lo haces desde el .exe?
saludos
Leandro tuttini no sabe cuanto le agradesco este aporte, ha sido de gran ayuda a mi proyeto, gracias!!!!!
ResponderEliminarMil gracias esto es lo que estaba buscando... !!!
ResponderEliminarSldos.
Hola Leandro, me nace una consulta, si le agregara un botón "Limpiar" como podría limpiar ese log que esta en el archivo?
ResponderEliminarhola
EliminarEn realidad esta forma que expongo en el articulo no es la mejor forma de loguear en un archivo
Cómo escribir facilmente un fichero Log en .NET Framework
usando las librerias de .net es mas simple de loguear y configurar
saludos