Desenvolvimento - ASP. NET

Health Monitoring - ASP.NET 2.0

Nas versões anteriores do ASP.NET tínhamos algumas funcionalidades/serviços que nos permitiam monitorar a vida de uma aplicação ASP.NET. Podemos considerar como estes serviços os Traces e os contadores de performance.

por Israel Aéce



function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i Nas versões anteriores do ASP.NET tínhamos algumas funcionalidades/serviços que nos permitiam monitorar a vida de uma aplicação ASP.NET. Podemos considerar como estes serviços os Traces e os contadores de performance. O ASP.NET 2.0 introduz um novo conceito, chamado de Health Monitoring, que trata-se de uma infraestrutura completa para monitoramento da aplicação ASP.NET, permitindo-nos analisar posteriormente o comportamento de tal aplicação, analisando a performance, diagnosticar falhas da aplicação e também eventos significativos durante a vida da aplicação em questão.

Neste novo cenário, devemos nos atentar para dois principais conceitos que temos que ter em mente: events e providers. Um event é responsável por armazenar informações de um determinado acontecimento dentro da aplicação. Já o provider é o responsável por tratar e persistir (quando necessário) o evento. Este provider pode, por exemplo, fazer entrada dentro do Event Log do Windows, salvar em uma base de dados qualquer, mandar o mesmo por e-mail, etc. A amarração entre o evento e o provider é feita através de regras que definimos no arquivo de configuração - Web.Config - da aplicação.

O .NET Framework 2.0 já nos fornece uma porção de providers capazes de tratar estes eventos, podendo inclusive, criar os nossos próprios events e providers (veremos isso mais tarde, ainda neste artigo). Essa infraestrutura pode ser exibida através da imagem abaixo:

Figura 1 - Estrutura dos events e providers em funcionamento.

Partindo para o lado técnico e de design das classes, os eventos nada mais são do que classes que herdam a maioria das funcionalidades de uma classe base chamada de WebBaseEvent, sendo esta necessária para criarmos os nossos eventos customizados. Todos estes eventos devem, depois de criados, ser registrados no arquivo de configuração para que a aplicação e o runtime do ASP.NET possa fazer o uso dos mesmos. O registro pode ser a nível de Web.Config, ficando estes eventos limitados a aplicação ou, se desejar compartilhar os eventos para que todas as aplicações o utilizem, pode optar por registrá-los no arquivo Machine.config. Quando você registra este evento em um arquivo de configuração (seja ele qual for), é necessário que você defina um nome que representará a categoria associada a este evento. Para ter uma idéia dos eventos já existentes dentro do .NET Framework, consulte a tabela abaixo:

Evento Descrição
All Events Captura qualquer evento criado.
HeartBeats Permite efetuar a notificação de eventos.
Application Lifetime Events Fornece informações sobre o início, término e recompilação da aplicação.
All Errors Captura todos os erros gerados pela aplicação.
Infraestructure Errors Captura os erros relacionados com o funcionamento do IIS e do ASP.NET.
Request Processing Errors Excessões ocorridas durante o pedido de um determinado recurso.
All Audits Utilizado para efetuar auditorias.
Failure Audits Efetua a auditoria de tentativas falhas de Login.
Success Audits Ao contrário do anterior, efetua a auditoria de tentativas com sucesso de Login.

Para comprovar que estes eventos já estão pré-definidos, podemos analisar o arquivo Web.Config (que corresponde as configurações padrões de todas as aplicações Web), sendo exibidos através da imagem abaixo:

Figura 2 - Eventos já definidos dentro do .NET Framework 2.0.

Assim como os eventos, já existem também providers pré-definidos dentro do .NET Framework. São eles:

Provider Descrição
SqlWebEventProvider Utilizando este provider, o evento é armazenado dentro de uma base de dados SQL Server. É importante dizer que é necessário executar o utilitário aspnet_regsql.exe dentro do banco de dados para que seja criada a infraestrutura de tabelas necessárias. Este provider suporta buffer1.
SimpleMailWebEventProvider Permite configurar o envio de e-mails com as informações contidas dentro de um evento. Este provider suporta buffer1.
TemplatedMailWebEventProvider Semelhante ao anterior, mas neste caso podemos definir uma mensagem customizada a ser enviada.
EventLogProvider Efetua as entradas dentro da categoria Application do Event Log do Windows.
WmiWebEventProvider Através deste provider é possível vincular os eventos aos eventos WMI e, conseqüentemente, podem ser consumidos por listeners WMI.

1 - Quando falamos que um provider suporta buffering, isso quer dizer que ele espera este buffer completar para "descarregar" todo o conteúdo em memória em seu local de destino. Isso pode nos gerar um maior ganho de performance, pois evita de a cada evento ocorrido dentro da aplicação ter que perder tempo armazenando isso. Estes buffers são definidos dentro do elemento bufferModes.

Apesar de existirem todos estes providers dentro do .NET Framework, no arquivo de configuração só temos adicionados o SqlWebEventProvider, EventLogProvider e WmiWebEventProvider. Se desejarmos utilizar um dos outros providers devemos registrar o mesmo dentro do elemento providers do arquivo Web.Config local.

Dando seqüencia, vamos analisar as definições de regras, que é onde "amarramos" o provider com os events. É com estas regras que definimos por qual provider o nosso evento vai ser tratado. Isso permite a independência de providers, ou seja, podemos ter qualquer evento sendo tratado por qualquer provider. Felizmente, a infraestrutura não me obriga a tratar todos os eventos com apenas um único provider. Para exemplificar, vamos analisar como devemos fazer para habilitar o health monitoring e utilizar um evento e provider:

<system.web>
  <healthMonitoring enabled="true">
    <rules>
      <clear />
      <add 
        name="All Errors Default" 
        eventName="All Errors" 
        provider="EventLogProvider" 
        profile="Default" 
        minInterval="00:01:00"/>
    </rules>
  </healthMonitoring>
</system.web>
Web.Config

Como podemos analisar no código acima, apenas com algumas linhas de configurações dentro do arquivo Web.Config, o health monitoring já está habilitado. Antes de ver isso em funcionamento, vamos analisar os atributos: name identifica a regra; eventName refere-se ao evento que deverá ser tratado; provider é o nome do provider que iremos utilizar para persistir o evento; profile permite-nos associar um perfil a esta regra; e finalmente o atributo minInterval, qual tem um funcionamento interessante, ou seja, se uma excessão acontecer mais de uma vez dentro do período estipulado nele, apenas será logado/persistido uma vez o evento no EventLog (neste caso). Para poder testar, apenas vou criar um código .NET que gere uma excessão, como por exemplo uma divisão por 0 (zero):

Figura 3 - Health Monitoring em funcionamento.
function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i Criação de um Event e Provider customizado

Mesmo com todas as classes que o .NET Framework 2.0 traz para trabalhar com Health Monitoring, ainda temos a flexibilidade de criar os nossos próprios events e providers, se assim desejarmos. Para isso, basta trabalharmos com as classes base (tanto para event quanto para provider) que já trazem as funcionalidades principais para operar os dados e apenas customizamos o que e para onde direcionaremos estes eventos.

Como já vimos no começo do artigo, utilizamos a classe base WebBaseEvent para criarmos o nosso próprio evento. O cenário aqui será criar um event para utilizarmos no decorrer da aplicação e um provider para persistirmos os eventos em arquivos de texto (txt). O código abaixo mostra como devemos proceder para criarmos o nosso próprio event:

using System.Web.Management;

public class CodeEvent : WebBaseEvent
{
    private static readonly int _eventCode = WebEventCodes.WebExtendedBase + 1;

    public CodeEvent(string msg, object eventSource, int eventCode)
        : base(msg, eventSource, CodeEvent._eventCode) {}

    public CodeEvent(string msg, object eventSource, int eventCode, int eventDetails)
        : base(msg, eventSource, CodeEvent._eventCode, eventDetails) {}
}
Imports System.Web.Management

Public Class CodeEvent

    Inherits WebBaseEvent

    Private Shared ReadOnly _eventCode As Integer = WebEventCodes.WebExtendedBase + 1

    Public Sub New(ByVal msg As String, _
        ByVal eventSource As Object, _
        ByVal eventCode As Integer)

        MyBase.New(msg, eventSource, CodeEvent._eventCode)
    End Sub

    Public Sub New(ByVal msg As String, _
        ByVal eventSource As Object, _
        ByVal eventCode As Integer, _
        ByVal eventDetails As Integer)

        MyBase.New(msg, eventSource, CodeEvent._eventCode, eventDetails)
    End Sub

End Class
C# VB.NET

A única coisa que devemos ter atenção é no código interno do event. Ele está definido como _eventCode e é responsável por gerar um código para distinguir os eventos desta classe. Depois do evento criado, antes de ver como fica a configuração no arquivo Web.Config, analisaremos como proceder para a criação do provider para o arquivo texto:

using System.IO;
using System.Web.Management;

public class TextEventProvider : WebEventProvider
{
    private string _providerName = "TextEventProvider";
    private string _path = string.Empty;

    public override void Initialize(string name, 
        System.Collections.Specialized.NameValueCollection config)
    {
        if (string.IsNullOrEmpty(config["path"]))
            throw new ArgumentException("Caminho inválido/inexistente.");

        this._path = config["path"];
        base.Initialize(name, config);
    }

    public override void ProcessEvent(WebBaseEvent raisedEvent)
    {
        if (raisedEvent is CodeEvent)
        {
            StreamWriter sw = File.AppendText(this._path);
            sw.WriteLine(
                string.Format("{0}\t{1}\t{2}\t{3}\t",
                    raisedEvent.EventCode,
                    raisedEvent.EventID,
                    raisedEvent.EventTime,
                    raisedEvent.Message));
            sw.Close();
        }
    }

    public override void Shutdown(){}
    public override void Flush(){}
}
Imports System.IO
Imports System.Web.Management

Public Class TextEventProvider

    Inherits WebEventProvider

    Private _providerName As String = "TextEventProvider"
    Private _path As String = String.Empty

    Public Overrides Sub Initialize(ByVal name As String, _
        ByVal config As System.Collections.Specialized.NameValueCollection)

        If String.IsNullOrEmpty(config("path")) Then
            Throw New ArgumentException("Caminho inválido/inexistente.")
        End If

        Me._path = config("path")
        MyBase.Initialize(name, config)
    End Sub

    Public Overrides Sub ProcessEvent(ByVal raisedEvent As _
        System.Web.Management.WebBaseEvent)

        If (TypeOf raisedEvent Is CodeEvent) Then
            Dim sw As StreamWriter = File.AppendText(Me._path)
            sw.WriteLine( _
                String.Format("{0}\t{1}\t{2}\t{3}\t", _
                    raisedEvent.EventCode, _
                    raisedEvent.EventID, _
                    raisedEvent.EventTime, _
                    raisedEvent.Message))
            sw.Close()
        End If
    End Sub

    Public Overrides Sub Flush()
    End Sub

    Public Overrides Sub Shutdown()
    End Sub

End Class
C# VB.NET

Através do método Initialize recuperamos informações do arquivo de configuração que, neste nosso caso, devemos recuperar o caminho do arquivo através do atributo path. Temos também o método ProcessEvent que temos acesso ao evento gerado e assim podemos tratá-lo como quisermos. Como vamos armazenar isso em um arquivo TXT, utilizaremos o conjunto de caracteres "\t" para incluirmos o TAB entre os valores dos campos do evento. O método AppendText da classe File fará com que o conteúdo sempre será adicionado no TXT, sem perder o que já temos lá gravado.Com estas classes prontas, agora basta configurarmos o event e o provider dentro do Web.Config criando uma nova regra e "amarrando" o evento ao provider:

<system.web>
  <healthMonitoring enabled="true">
    <providers>
      <add 
          name="TextEventProvider" 
          type="TextEventProvider" 
          path="C:\EventLog.txt"/>
    </providers>
    <eventMappings>
      <add 
          name="Code Event" 
          type="CodeEvent"/>
    </eventMappings>
    <rules>
      <add 
          name="Text Event Rule" 
          eventName="Code Event" 
          provider="TextEventProvider" 
          profile="Default" 
          minInterval="00:00:00"/>
    </rules>
  </healthMonitoring>
</system.web>
Web.Config

Aqui vale chamar a atenção para o atributo type. Como eu estou criando as classes internamente, ou seja, dentro do diretório App_Code do meu projeto ASP.NET, eu não precisei definir o nome do Assembly, informação que seria necessária se estivesse com esse código dentro de uma DLL a parte.

Da mesma forma que fizemos anteriormente, vamos criar uma situação e forçar a divisão por zero para que uma excessão seja atirada e, com isso, salvamos o evento que criamos com o uso do nosso provider, definido na regra Text Event Rule. O notificação é gerada quando invocamos o método Raise (que é herdado da classe base). O código abaixo exibe isso:

try
{
    int i = 0;
    int resultado = 10 / i;
}
catch (DivideByZeroException ex)
{
    CodeEvent codeEvent = new CodeEvent("Divisão por zero.", sender, 0);
    codeEvent.Raise();
}
Try
    Dim i As Integer = 0
    Dim resultado As Integer = 10 / i
Catch(ex As DivideByZeroException)
    Dim codeEvent As New CodeEvent("Divisão por zero.", sender, 0)
    codeEvent.Raise()
End Try
C# VB.NET

Figura 4 - O arquivo TXT gerado com o Log.

CONCLUSÃO: Como vimos no decorrer deste artigo, o Health Monitoring traz uma grande quantidade de features e classes que nos permitem trabalhar com o monitoramento da vida da aplicação sem precisarmos recorrer a componentes e/ou ferramentas de terceiros para isso. Finalmente, ratifico a simplicidade que temos quando precisamos criar algo mais customizado, onde os events e os providers que estão intrínsicos e fornecidos pelo .NET Framework não nos atende.
Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.