ASP.NET - Geração e tratamento de log de erros


Este artigo mostra como você pode gerar um arquivo de log de erros em uma aplicação ASP.NET. Vai ser um abordagem bem simples onde os erros serão gravados em um arquivo texto chamado logErro.txt.

Para que serve um arquivo de log ? Serve para você auditar o seu sistema em produção e acompanhar o seu comportamento monitorando as ocorrências que porventura estejam afetando o desempenho ou causando um problema.

Irei criar um novo web site usando o Visual Web Developer Express onde a página Default.aspx irá conter um controle dropdownlist e um controle GridView .

O controle DropDownlist será preenchido com os dados da tabela Categories do banco de dados Northwind.mdf  e o GridView irá exibir os dados da tabela Products  e Categories relacionados com a categoria selecionada no Dropdownlist.

Para criar esta aplicação podemos usar os assistentes de configuração sem ter que digitar nenhuma linha de código mas não vou usar os assistentes e vou fazer tudo via programação. Além disso vou criar um web site AJAX usando o componente UpdatePanel para evitar o postback da página inteira.

Inicie o Visual Web Developer Express e crie um novo web site chamado cmdFiltroNet a partir do menu File -> New Web Site selecionando na janela New Web Site o template ASP.NET AJAX - Enabled Web Site. (Você deve instalar o pacote a última versão do Microsoft ASP.NET AJAX instalada e configurada.( http://ajax.asp.net/)

Selecionando a página Default.aspx , no modo Design, você deverá ver o controle ScriptManager exibido na página.

Inclua agora , a partir da guia AJAX Extensions da ToolBox, o controle UpdatePanel na página;

A seguir inclua , no interior do componente UpdatePanel, um controle DropdownList e um controle GridView , e, fora do UpdatePanel um controle Label;

Como não vou usar nenhum assistente para configurar a conexão com o banco de dados nem preencher os controles ou selecionar os dados para exibição, e assim, teremos que definir primeiro a string de conexão com o banco de dados Northwind.mdf no arquivo web.config.

Você pode usar o assistente de configuração e definir uma fonte de dados para o controle Dropdownlist salvando a string de conexão no web.config e em seguida remover o componente de dados da página ou pode informar a string diretamente no arquivo web.config conforme o código abaixo:

<connectionStrings>

<add name="NORTHWNDConnectionString" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=C:\dados\NORTHWND.MDF;Integrated Security=True;Connect Timeout=30;User Instance=True" providerName="System.Data.SqlClient"/>

</connectionStrings>

Em seguida devemos declarar os seguintes namespaces no arquivo code-behind :

Imports System.IO
Imports
System.Data.sqlclient
Imports
System.Data

Preenchendo o DropDownList com os dados da tabela Categories

Vamos preencher o controle Dropdownlist com os dados da tabela Categories. Isto deve ser feito quando a página for carregada por isso no evento Load da página inclua o seguinte código:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
 
If Page.IsPostBack = False
Then

 Dim conexaoBD As New SqlConnection()
Try

   ' configura a string de conexão obtendo-a a partir do arquivo web.config

   Dim strConnectionString As String =        ConfigurationManager.ConnectionStrings("NORTHWNDConnectionString").ConnectionString

    conexaoBD.ConnectionString = strConnectionString

    ' cria o comando com a instrução SQL para selecionar os dados da tabela Categories

    Dim strComandoTexto As String = "SELECT CategoryID, CategoryName FROM Categories ORDER BY CategoryName"

     Dim comando As New SqlCommand(strComandoTexto, conexaoBD)

    ' Abre a conexão com o banco de dados

    conexaoBD.Open()

    ' Preenche controle dropdownlist

    DropDownList1.DataSource = comando.ExecuteReader()
    DropDownList1.DataTextField =
"CategoryName"
    DropDownList1.DataValueField = "CategoryD"
    DropDownList1.DataBind()

    ' força a primeira exibição do Gridview    DropDownList1_SelectedIndexChanged(Nothing, Nothing)

Catch ex As Exception

' escreve o erro no arquivo de log
Dim sw As StreamWriter = File.AppendText(Server.MapPath("~/logErro.txt"))
sw.WriteLine(DateTime.Now.ToString &
" : & ex.Message)
sw.Close()
' exibe o erro na página
label1.text = ex.Message.ToString

Finally

    ' fecha a conexão com o banco de dados
     conexaoBD.Close()

End Try

End If

End Sub

No evento DropDownList1_SelectedIndexChanged vamos colocar o código que irá exibir os dados no GridView de acordo com a seleção feita no dropdownlist:

Protected Sub DropDownList1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles DropDownList1.SelectedIndexChanged

' configura a string de conexão a partir do arquivo web.config

' abre a conexão

Dim strConnectionString As String = ConfigurationManager.ConnectionStrings("NORTHWNDConnectionString").ConnectionString

Dim conexaoBD As New SqlConnection(strConnectionString)

Try

'Constroi a consulta SQL para exibir os dados da tabela Produtos e Categorias usando um parâmetro
Dim strComandoTexto As String = "SELECT Products.ProductName, Categories.CategoryName " _     & "FROM Products INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID " _    
&
"WHERE Products.CategoryID = @CategoryID " _
&
"ORDER BY ProductName"

' cria o comando
Dim comando As New SqlCommand(strComandoTexto, conexaoBD)

' define e inclui o parâmetro
Dim parametro As New SqlParameter()
parametro.ParameterName =
"@CategoryID"
parametro.SqlDbType = SqlDbType.Int
parametro.Value = DropDownList1.SelectedValue

comando.Parameters.Add(parametro)

' abre a conexao com o banco de dados
conexaoBD.Open()

' exibe os dados
GridView1.DataSource = comando.ExecuteReader()

GridView1.DataBind()

finally

' fecha a conexao
conexaoBD.Close()

End Try
End
Sub

Executando a página no servidor ASP.NET teremos o seguinte resultado:

Para provocar um erro vou alterar o nome do parâmetro CategoryID removendo a letra I , desta forma ao executar iremos o obter a seguinte mensagem de erro:

Dê uma olhada na janela Solution Explorer , clicando no botão Refresh e perceba que o arquivo logErro.txt foi criado. Se visualizarmos o seu conteúdo iremos ver exatamente a mesma mensagem de erro com indicação de data e hora.

Tudo certo ? Quase...

Na verdade podemos melhorar essa nossa implementação de forma a torná-la menos rígida e mais elegante.

A primeira coisa a fazer é criar uma pasta na estrutura do nosso projeto para armazenar o arquivo de log. Clique com o botão direito do mouse sobre o nome do projeto e selecione a opção New Folder e crie uma nova pasta chamada logErro

Agora vamos abrir o arquivo web.config e vamos definir o local e o nome do arquivo de log que vamos criar incluindo uma chave para que possamos identificar e obter estas informações através da nossa aplicação. Abaixo temos a definição incluída entre as tags <appSettings>:

.....
<configuration>
<appSettings>
      
<add key="arqLogErro" value="~/logErro/logErro.txt"/>
</appSettings>
<connectionStrings/>
<system.web>
.......

Outra mudança importante que vamos efetuar será criar uma classe e nela definir a criação do arquivo e a gravação das mensagens no arquivo de log . Na solução atual estamos usando código diretamente na instrução try/catch e desta forma teremos que repetir o código para cada try/catch existente na aplicação.

É Bom saber.

Se você rodar a sua aplicação usando o Visual Web Developer, a ASP.NET irá reportar erros pela exibição de uma mensagem na página do seu Browser. Será usada uma página com uma mensagem de erro padrão mas você pode personalizar a página que é exibida quando um erro ocorrer. Basta definir a tag customErrors no arquivo web.config.: Veja o exemplo abaixo:

<configuration>
  <system.web>
     <customErrors mode="on"  defaultRedirect="paginaErro.aspx" />
  </system.web>
</configuration>

Existem 3 valores modos que podem ser usados:

  • Off - usa a página padrão para usuários locais e remotos na exibição dos erros.
  • On - usa a página definida pelo programador para exibir os erros para  usuários locais e remotos.
  • RemoteOnly - a página de erro é mostrada somente para usuário locais.

Após isso você deve definir a página paginaErro.aspx contendo a mensagem que deseja exibir para o usuário.

Clique com o botão direito sobre o nome do projeto e selecione a opção Add New Item. Na janela templates selecione o template Class e informe o nome Log.vb. O arquivo será incluído na pasta App_Data.

Abra o arquivo Log.vb e defina as seguintes declarações no início do arquivo:

Imports Microsoft.VisualBasic
Imports System.configuration
Imports System.io

Em seguida vamos definir dois construtores para a nossa classe:

1- O construtor padrão sem argumentos
2- O construtor que usa dois argumentos: a mensagem de erro e o objeto Exception

Observe que agora podemos gravar a mensagem e também o stackTrace, ou seja, a pilha de erros, fornecendo assim mais informações sobre a exceção ocorrida.

Public Sub New()
     MyBase.New()
End Sub

Public Sub New(ByVal mensagem As String, ByVal erro As Exception)

logErro(mensagem)

If erro IsNot Nothing Then
      logErro(erro.StackTrace)
End If
End Sub

Agora vamos criar o método estático (Shared) logErro(msg) que irá efetivamente criar o arquivo e gravar as mensagens de erro:

Public Shared Sub logErro(ByVal mensagem As String)

Dim caminho As String = ""
Dim data As String = ""
Dim contexto As HttpContext = HttpContext.Current

caminho = ConfigurationManager.AppSettings("arqlogErro")
data = DateTime.Now.ToString

Try
    Dim sw As StreamWriter = File.AppendText(contexto.Server.MapPath(caminho))
    sw.WriteLine(data & " :: " & mensagem)
    sw.Close()
Catch ex As Exception
    MsgBox(ex.Message)
End Try

End Sub

Aqui temos o código usado na solução inicial , onde incluímos a obtenção do caminho e nome do arquivo de log. Usamos a classe HttpContext para obtermos o caminho a partir do contexto , pois no servidor o caminho pode ser diferente do definido inicialmente.

Para usar a classe você deve criar uma instância da mesma e no bloco try/catch, fazer a chamada do construtor passando os argumentos, que podem ser : a mensagem e/ou objeto Exception.

.......
Dim log As LogErro

Try
      'codigo que efetua uma ação
Catch ex As Exception
    
  log = New LogErro(ex.Message, ex)
End Try
..................

Após efetuarmos alguns testes abrimos o arquivo logErro.txt onde podemos visualizar as mensagens gravadas:

09/08/2007 08:47:53 :: Arithmetic operation resulted in an overflow.
09/08/2007 08:48:13 :: Arithmetic operation resulted in an overflow.
09/08/2007 08:48:19 :: at _Default.Button1_Click(Object sender, EventArgs e) in C:\_aspn\artigos\geraLog\Default.aspx.vb:line 11
09/08/2007 08:49:44 :: Arithmetic operation resulted in an overflow.
09/08/2007 08:49:44 :: App_Web_dhz8c-i7
09/08/2007 08:49:44 :: at _Default.Button1_Click(Object sender, EventArgs e) in C:\_aspn\artigos\geraLog\Default.aspx.vb:line 11
09/08/2007 08:50:53 :: Arithmetic operation resulted in an overflow.
09/08/2007 08:51:28 :: Arithmetic operation resulted in an overflow.
09/08/2007 08:51:28 :: at _Default.Button1_Click(Object sender, EventArgs e) in C:\_aspn\artigos\geraLog\Default.aspx.vb:line 11

Temos ainda uma outra opção para tratar os erros inesperados em páginas ASP.NET :  fazer o tratamento no evento de erro a nível de página ou no evento de erro a nível de aplicação.

Para ilustrar isso clique com o botão direito do mouse sobre o nome do projeto e selecione a opção Add New Item.

Na janela Templates selecione o template  Global Configuration Class e aceite o nome padrão Global.asax

Abra o arquivo Global.asax podemos notar o evento Application_Error() destacada na figura abaixo.

Você pode incluir o seu código para tratar erros neste evento. Abaixo um exemplo de código que você pode usar neste evento para tratar erros:

<%@ Application Language="VB" %>

<%@ Import Namespace="System.Diagnostics"%>
 

<script runat="server">

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)

' Code that runs on application startup

End Sub

Sub Application_End(ByVal sender As Object, ByVal e As EventArgs)

' Code that runs on application shutdown

End Sub

 

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
 

Dim descricaoErro As String = Server.GetLastError.ToString


'Cria um evento de log se ele não existir

Dim eventoLog As String = "logErro"


If
(Not EventLog.SourceExists(eventoLog)) Then

    EventLog.CreateEventSource(eventoLog, eventoLog)

End If
 

' Inclui no evento Log

Dim Log As New EventLog()


Log.Source = eventoLog

Log.WriteEntry(descricaoErro, EventLogEntryType.Error)


End
Sub


Sub
Session_Start(ByVal sender As Object, ByVal e As EventArgs)

' Code that runs when a new session is started

End Sub

Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)

' Code that runs when a session ends.

' Note: The Session_End event is raised only when the sessionstate mode

' is set to InProc in the Web.config file. If session mode is set to StateServer

' or SQLServer, the event is not raised.

End Sub

</script>

 

A classe EventLog permite que você leia e escreva para o log de eventos e permite também que você crie um log, e, é isso que estamos fazendo no código acima.

É Bom saber.

Podemos ainda usar o evento Page_Error() para tratar erros a nível de página conforme exemplo de código abaixo:

Private Sub Page_Error(ByVal sender As Object, ByVal e As EventsArgs)

Dim errorMessage As String = "Erro ocorrido " + Server.GetLastError()

Server.ClearError()

Dim LogName As String = "MyAppLog"
Dim SourceName As String = "MyAppSource"

If Not (EventLog.SourceExists(SourceName) Then
     Not (EventLog.SourceExists(SourceName))
End If

EventLog.CreateEventSource(SourceName, LogName)

Dim MyLog As EventLog = New EventLog()
MyLog.Source = SourceName
MyLog.WriteEnTry(errorMessage, EventLogEnTryType.Error)

End Sub

Eu sei é apenas ASP.NET mas eu gosto ...


José Carlos Macoratti