domingo, 11 de outubro de 2009

Tratador de Erros Global para ASP.NET e Windows Forms

Você não deve deixar *nenhuma* parte da sua aplicação sem tratamento de erro. Mas isto não significa que você terá que espalhar blocos try/catch em todo o seu código. Tanto aplicações Windows Forms quanto aplicações ASP.NET oferecem a possibilidade de criação de um tratador de erro global, que será ativado sempre que uma exceção não tratada (i.e., uma exceção que ocorreu fora de um bloco try/catch) acontecer.

Windows Forms

Como sempre, as coisas são mais fáceis em aplicações Windows Forms do que em ASP.NET. Windows Forms provê para o desenvolvedor o objeto Application que, dentre outas coisas, tem o evento ThreadException. Basicamente, devemos adicionar um tratador de eventos a este evento, e o tradador de erros global da nossa aplicação estará pronto.

Crie um novo projeto VB.NET Windows Forms e adicione um botão no formulário Form1. Depois, substitua seu código pelo seguinte:


Imports System.Diagnostics

Public Class Form1

Const NOME_FONTE_EVENTOS As String = "Minha Aplicação"

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' Cria a fonte de eventos no Log de Aplicação do Windows
' Isto só precisa ser feito uma vez (p.e. no setup da aplicação)
If Not EventLog.SourceExists(NOME_FONTE_EVENTOS) Then
EventLog.CreateEventSource(NOME_FONTE_EVENTOS, "Application")
End If
' Define o tratador de errors global
AddHandler Application.ThreadException, AddressOf Me.TratadorErros
End Sub

Sub TratadorErros(ByVal sender As Object, ByVal e As Threading.ThreadExceptionEventArgs)
' Acesso à exceção ocorrida
Dim erro As Exception = e.Exception
' Escreve dados da exceção no Log de Aplicação
EventLog.WriteEntry(NOME_FONTE_EVENTOS, _
"Erro não tratado: " + vbNewLine + erro.ToString(), _
EventLogEntryType.Error)
' Mostra mensagem de erro não-técnica ao usuário
MsgBox("Um erro inesperado ocorreu. Por favor entre em contato com o administrador do sistema.", _
MsgBoxStyle.Critical)
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
' Simula uma exceção não tratada
Throw New Exception("Oh oh Erro acessando o banco de dados")
End Sub
End Class

Clique no botão para simular uma exceção não tradada. Uma mensagem será mostrada e se você olhar o Log de Aplicação do Windows no Visualizador de Eventos (Iniciar > Executar > ebentvwr.msc), lá estará a exceção.

ASP.NET

ASP.NET disponibiliza o evento de aplição Application_Error, que é disparado sempre que uma exceção não tratada ocorre. Este tratador de evento pertence à classe de aplicação global (global application class), como o Visual Studio a chama. O código da classe de aplicação global fica no arquivo Global.asax. As aplicações ASP.NET, por default, não tem um arquivo Global.asax. Você pode adicionar um indo no menu Project e selecionando Add New Item > Global Application Class.

No tratador de evento Application_Error, você terá que usar dois métodos especiais:
  • Server.GetLastError(), que dá acesso à exceção ocorrida.
  • Server.ClearError(), que evita que o ASP.NET mostre a página com a mensagem de erro default.
Crie um novo projeto ASP.NET Web Application, adicione um arquivo Global.asax, e substitua o seu código com o seguinte:


Imports System.Web.SessionState
Imports System.Diagnostics

Public Class Global_asax
Inherits System.Web.HttpApplication

Const NOME_FONTE_EVENTOS As String = "Minha Aplicação"

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Cria a fonte de eventos no Log de Aplicação do Windows
' Isto só precisa ser feito uma vez (p.e. no setup da aplicação)
If Not EventLog.SourceExists(NOME_FONTE_EVENTOS) Then
EventLog.CreateEventSource(NOME_FONTE_EVENTOS, "Application")
End If
End Sub

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Acessa a exceção ocorrida
Dim erro As Exception = Server.GetLastError()
' Instrui o Framework a não mostrar a mensagem de erro default
Server.ClearError()
' Escreve dados da exceção no Log de Aplicação
EventLog.WriteEntry(NOME_FONTE_EVENTOS, _
"Erro não tratado: " + vbNewLine + erro.ToString(), _
EventLogEntryType.Error)
' Redireciona usuário para página com mensagem de erro não-técnica
Response.Redirect("/erro.aspx")
End Sub

End Class

Crie uma página chamada "erro.aspx"; esta é a página para a qual os usuário serão redirecionados quando da ocorrência de uma exceção não tratada.

Por último, adicione um botão à página Default.aspx, e substitua seu código com o seguinte:


Partial Public Class _Default
Inherits System.Web.UI.Page

Protected Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
' Simula uma exceção não tratada
Throw New Exception("Oh oh Aquele erro de acesso ao banco de novo")
End Sub
End Class

Clique no botão e você será redirecionado para a página erro.aspx, e um evento será logado no Log de Aplicação com a informação da exceção.



UPDATE: O código no evento Application_Start vai falhar no Windows Vista e Windows 7, devido à segurança mais restrita destes sistemas operacionais. O código tenta criar uma fonte de eventos no Log de Aplicação para logar os erros nesta fonte. Os métodos EventLog.SourceExists() e EventLog.Create() tentam varrer os logs do Windows para verificar se a fonte especificada já existe. Isto causa o erro, pois por default não é permitido o acesso ao Log de Segurançapor aplicações ASP.NET. Há várias maneiras de se solucionar isto, mas uma solução simples e rápida é criar a fonte "na mão": crie no Registry a seguinte chave: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\eventlog\Application\NOME_APLICACAO, trocando NOME_APLICACAO pelo nome da fonte de eventos que você quer criar. Um caminho "mais correto" seria p.e. criar uma aplicação console que executasse o código do evento Application_Start, e empacotar esta aplicação em no pacote de setup do seu site, para ser executada ao final do setup.