por Cesar Cassiano Schimanco

ASP.NET - Criar arquivo com Log de erros do site

Este artigo tem como finalidade mostrar como centralizar o tratamento e manipulação de todos os erros de um site em um único local. Alem disso, vamos salvar todos os erros em um arquivo txt e responder uma pagina amigável de erro para o usuário usando um template.

Alem de tratar os erros, algo muito importante é exibir os erros ou pelo menos saber o que esta acontecendo. Em uma página normal fica fácil fazer o tratamento e exibir um erro amigável, mas quando usamos Ajax nem sempre isso é possível, bem como ações acionadas por terceiros como o consumo de um webservice. Ou então quando o cliente manda um print de erro 500 do IE. Por estes e outros motivos vamos criar um arquivo de log dos erros. E quando a aplicação não funcionar e não aparecer nenhum erro na tela, podemos consultar o arquivo de log.

    • Vamos usar o Application_Error do Global.asax.
      Assim como o Web.Config o Global.asax fica no diretorio base do site e tudo passa por ele.
      O Application_Error do Global.asax.cs lida com todos os erros não tratados de uma aplicação ASP.NET. Mas é importante lembrar que se o erro for tratado usando try e catch ele não vai passar pelo Application_Error.
    • Log.cs, vamos criar essa classe para gerenciar o log e as mensagens de erro.
      Nesse exemplo vou criar um txt com o nome log-yyyyMMdd.txt, ou seja, vai criar um arquivo novo para cada dia. Para facilitar a exlusao de logs velhos e para não criar um arquivo gigantesco. Sem contar que ficar mais fácil de achar o erro já que os arquivos ficam divididos por data.
    • template.htm, este arquivo será o template para as mensagens de erro e para as paginas não encontradas. Pois não faremos nenhum redirect para manter o “Internal Server Error” e o “Not Found” na URL devida. Em resumo, esse template vai substituir aquela tela amarela da morte.

Vamos inverter a ordem e iniciar pelo template, neste exemplo usei um template muito básico.

/template.htm

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Ops, {0}</title>
</head>
<body>
    <h1>Ops, {0}</h1>
    <p>URL:{1}</p>
    <p>{2}</p>
</body>
</html>

Agora vamos criar a classe Log

/App_Code/Log.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Net;

/// <summary>
/// Summary description for Log
/// </summary>
public class Log
{
    /// <summary>
    /// Salva o LOG de erros, cria um novo arquivo para cada dia.
    /// </summary>
    /// <param name="msg"></param>
    public static void Save(string msg)
    {
        //Path do arquivo txt
        //C:\inetpub\wwwroot\nomedosite\App_Data\log-20111024.txt
        string strFile = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/log-" + DateTime.Now.ToString("yyyyMMdd") + ".txt");

        //Se arquivo não existir
        if (!File.Exists(strFile))
        {
            //Criar o arquivo, 
            //Estou usando o using para fazer o Dispose automático do arquivo após criá-lo.
            using (FileStream fs = File.Create(strFile)) { }
        }

        //Escreve o Erro no txt
        //Os erros são concatenados, ou seja, não são sobreescritos.
        using (StreamWriter w = File.AppendText(strFile))
        {
            string _msg = string.Empty;
            string URL = HttpContext.Current.Request.Url.AbsoluteUri.ToString();

            //Adicionar um separador
            _msg = "#############################################################\r\n";
            //Data do erro
            _msg += "Data:" + DateTime.Now.ToString("yyyyMMdd-HH:mm:ss") + "\r\n";
            //URL do erro
            _msg += "URL:" + URL + "\r\n";
            //Adicionando a mensagem
            _msg += msg;
            //quebra de lina e nova linha
            _msg += "\r\n\r\n";

            //Escreve no arquivo
            w.Write(_msg);
            //Fecha
            w.Close();
        }
    }

    /// <summary>
    /// Monta e exibe o template para o erro
    /// </summary>
    /// <param name="strTitle"></param>
    /// <param name="strMessage"></param>
    public static void ShowTemplate(string strTitle, string strMessage)
    {
        HttpContext context = HttpContext.Current;

        //A linha abaixo limpa as mensagens de erro e não vai exibi-las na página.
        context.Server.ClearError();
        //Limpa todo o conteudo.
        context.Response.Clear();
        
        //Criando o WebClient, para ler o template
        WebClient wc = new WebClient();
        wc.Encoding = System.Text.Encoding.UTF8;

        //Obtendo o conteúdo do template
        string strTemplate = wc.DownloadString(context.Server.MapPath("~/template.htm"));
        strTemplate = string.Format(strTemplate, strTitle, strMessage, DateTime.Now.ToString());
        context.Response.Write(strTemplate);
        context.Response.End();
    }
}

E por fim vamos criar o Global.asax

/Global.asax

<%@ Application Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Text" %>

<script runat="server">

    void Application_Start(object sender, EventArgs e) 
    {

    }
    
    void Application_End(object sender, EventArgs e) 
    {

    }
        
    void Application_Error(object sender, EventArgs e) 
    {
        //URL da pagina
        string URL = HttpContext.Current.Request.Url.AbsoluteUri.ToString();
        
        //Criando um objeto do tipo System.Exception e pegar o último erro
        Exception error = Server.GetLastError();

        //Se error não for null
        if (error != null)
        {
            string strErrorMessage = string.Empty;
            //Pegando o código do erro.
            //Caso o erro seja do tipo HttpException vai pegar o código do erro, 
            //ou então definirá o código como 500
            int code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500;

            //Pegando os erros e montar a mensagem
            while (error.InnerException != null)
            {
                //código do erro
                strErrorMessage += "Código:" + code + "\r\n";
                //Mensagem  de erro (InnerException)
                strErrorMessage += "Mensagem:" + error.InnerException;

                //Salva no arquivo txt
                Log.Save(strErrorMessage);

                //Próximo erro
                error = error.InnerException;
            }

            //Caso o erro seja 404, vamos salvar como URL não encontrada.
            if (code == 404)
            {
                strErrorMessage += "URL não encontrada";

                //Definir o StatusCode da página para 404
                Response.StatusCode = 404;
                
                //Salva no arquivo txt
                Log.Save(strErrorMessage);

                //Exibir o template de erro na página atual
                Log.ShowTemplate("página não encontrada.", URL);
            }
            else
            {
                //Definir o StatusCode da página para 500 
                Response.StatusCode = 500;
                
                //Exibir o template de erro na página atual
                Log.ShowTemplate("ocorreu um erro inesperado na página.", URL);
            }
        }
    }
    
    void Session_Start(object sender, EventArgs e) 
    {

    }

    void Session_End(object sender, EventArgs e) 
    {

    }
       
</script>

Para testar crie uma página com o código abaixo.

/Default.aspx.cs

protected void Page_Load(object sender, EventArgs e)
{
	Response.Write(Convert.ToInt32("c"));
}

Ou entao:

try
{
	//lança nova Exceção (apenas para teste)
	throw new Exception("Exemplo de Exception");
}
catch (Exception ex)
{
	//Gravar o erro usando a classe Log
	Log.Save(ex.Message);
	Log.ShowTemplate("Exemplo de Exception", Request.Url.AbsoluteUri);
}

Se preferir pode fazer o download do projeto aqui.

Comentários

Carregando comentários

Postar um novo comentário



Processando...