C'è solo una cosa di cui siamo sicuri quando scriviamo un qualunque software: prima o poi accadrà un'eccezione.
Accadrà perchè il software senza bachi non esiste, o perché la bella ma imprevedibile sistemista ha spento il DB, o solo perché l'utente ne sa una più del diavolo.
Gestire le eccezioni a volte non basta, o semplicemente ci è sfuggito quel caso, quel try, quell'inezia che dalla pancia del nostro programma rimbalza direttamente in faccia all'utente. Con la temuta paginona di errore di ASP.NET.
E' necessario quindi, specialmente in ambito web, avere una strategia un po' più sofisticata della semplice pagina di errore di .NET per la gestione o, almeno, per la rendicontazione e la registrazione di quanto è successo. Per capire dove, a chi e possibilmente, perché.
In questo post introdurremi i primi rudimenti dell'utilizzo di un tool open source, ELMAH, che ha come scopo proprio questo: fornire un framework semplice, potente e versatile per il logging e l'archiviazione delle eccezioni generate da un software ASP.NET.
In breve, le features principali di ELMAH sono
-logging automatico delle eccezioni non gestite
-logging delle eccezioni segnalate
-filtraggio delle eccezioni loggate
-utilizzo non intrusivo: basta aggiungere le dll di supporto, modificare il web config della nostra applicazione, e il gioco è fatto
-logging su diversi media: in RAM, su SQL server, Oracle, Acess, sql lite, XML...
-notifiche email degli errori
-report automatico: una pagina web permette di vedere la lista degli errori, i dettagli e persino la pagina errore originale.
-feed RSS degli errori, post su twitter (!) delle eccezioni
Per illustrare l'uso di questo semplice quanto potentissimo componente la cosa migliore è installarlo ed usarlo passo passo. Allegato a questo post trovate il semplice progetto ASP.NET 3.5 che andremo a costruire. Potete scaricare il progetto cliccando qui.
1) creiamo una semplice applicazione Web.
l'applicazione avrà una pagina con tre buttons e un link.
Il primo bottone creerà un'eccezione di index out of bounds, non gestita. Questo è un errore tipico che magari può sfuggirci e, per i motivi più vari, non essere gestito e "finire" in faccia all'utente.
Il secondo istanzierà una classe contenuta in una class library, tenterà l'accesso ad un db utilizzando una stringa di configurazione errata, con eccezione non gestita. Anche in questo caso vedremo come l'eccezione verrà salvata.
Il terzo bottone tenterà anch'esso l'accesso al db, ma l'eccezione verrà gestita. Vedremo come registrare comunque in elmah l'avvenuto problema.
il semplice codice che realizza questi errori è il seguente
1: protected void Button1_Click(object sender, EventArgs e)
2: {
3: int[] arr = new int[2];
4: arr[0] = 1;
5: arr[2] = 100;
6:
7: }
8: protected void Button2_Click(object sender, EventArgs e)
9: {
10: liberia lib = new liberia();
11: lib.Accedi();
12:
13:
14: }
15:
16: protected void Button3_Click(object sender, EventArgs e)
17: {
18: try
19: {
20: liberia lib = new liberia();
21: lib.Accedi();
22: }
23: catch (ApplicationException ex)
24: {
25: Response.Write("si è verificata un'eccezione nell'accesso al db");
26: Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
27: }
28: }
Il link punterà ad una pagina inesistente, per vedere come vengono gestiti anche errori non direttamente generati dal codice.
Eseguendo l’applicazione e cliccando sul primo bottone, ci troveremo davanti alla normale pagina di errore di ASP.NET:
per prima cosa, ancora prima di installare ELMAH, per evitare che l'utente veda la pagina standard di errore, possiamo creare una nostra pagina in cui comunichiamo che si è verificata una eccezione, error.htm, e impostarla nel web.config come pagina di destinazione in caso di eccezione non gestita, utilizzando il seguente codice:
1: <customErrors mode="On" defaultRedirect="error.htm">
2: </customErrors>
per maggiori info sul tag customErrors si può vedere qui http://msdn.microsoft.com/en-us/library/h0hfz6fc.aspx
2) Installiamo Elmah
Per iniziare dobbiamo scaricare l'ultima versione disponibile di ELMAH, facendo attenzione all'ambiente di destinazione, se è a 64 o 32 bit.
Troviamo la distribuzione qui: http://code.google.com/p/elmah/
Nella distribuzione troviamo molti files. Un demo standalone che permette di testare le potenzialità di ELMAH e alcune cartelle con dentro i binari. Prendiamo i binari contenuti in bin\net-3.5\Release
e copiamoli nella directory \bin della nostra applicazione.
Aggiungiamo le references agli assembly elmah.dll e sqllite.
3) configuriamo ELMAH e cominciamo a loggare…
A questo punto, per utilizzare ELMAH basta configurare opportunamente il web.config.
Nel file di distribuzione è presente un file di configurazione generico, da cui possiamo “rubare” la configurazione del nostro web.config.
Nel nostro caso scegliamo di salvare gli errori generati su file XML. Questa è una situazione comoda per poter avere gli errori in un formato gestibile a posteriori. Se è possibile possiamo anche pensare di utilizzare SQLlite, o addirittura sql server. Quest’ultima opzione è sicuramente più potente e versatile, ma chiediamoci: se l’eccezione è proprio l’impossibilità di accedere al SQL Server, dove andrà a finire l’eccezione? :)
Come in ogni situazione, scegliamo fra le varie possibilità a disposizione quella che più si confà al problema che stiamo studiando. La potenza di ELMAH si vede anche dal fatto che possiamo cambiare idea in ogni momento, semplicemente riscrivendo il file di configurazione.
Pochi passi ci portano ad avere ELMAH funzionante:
aggiungiamo la sectionGroup nelle configSections
1: <configuration>
2: <configSections>
3: <sectionGroup name="elmah">
4: <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
5: <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
6: </sectionGroup>
aggiungiamo la vera e propria sezione di configurazione, in cui diciamo ad ELMAH di loggare su file XML, nella cartella virtuale /logs della nostra applicazione (ovviamente avremo già creato tale cartella):
1: <elmah>
2: <!-- errorLog type="Elmah.SQLiteErrorLog, Elmah" connectionStringName="ELMAH.SQLite" /-->
3:
4: <errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/logs" />
5: </elmah>
aggiungiamo ELMAH agli httphandlers e modules. Segnatamente, nel codice che segue, l’ultima riga degli handlers e le ultime due dei modules:
1: <httpHandlers>
2: <remove verb="*" path="*.asmx" />
3: <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
4: <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
5: <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false" />
6: <add verb="POST,GET,HEAD" path="public/elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
7: </httpHandlers>
8: <httpModules>
9: <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
10: <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
11: <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
12:
13: </httpModules>
in particolare nella riga
1: <add verb="POST,GET,HEAD" path="public/elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
diciamo che la pagina in cui vogliamo vedere i report di errore generati sarà la pagina \public\elmah.axd.
Questo se vogliamo poter accedere da non autenticati ad una cartella che in questo caso abbiamo chiamato \public\ che è liberamente accessibile. Chiaramente sarebbe meglio che questa cartella avesse un nome meno ovvio… L’utilità è quella di poter accedere remotamente al report degli errori anche quando l’errore si verifica proprio nel controllo degli utenti.
Dovremmo quindi, se scegliamo di percorrere questa strada, creare una cartella \Public settando opportunamente le politiche di controllo degli accessi. Non dobbiamo creare alcun file particolare al suo interno, segnatamente non è necessario creare elmah.axd.
4) Fatto. Sentito niente?
A questo punto abbiamo la nostra applicazione che logga ogni errore. L’utente finale vedrà solo l’educatissima pagina error.htm, in cui ci scusiamo dell’errore avvenuto. Ovviamente potremmo anche utilizzare una pagina aspx che riprende una eventuale MasterPage, per rendere l’esperienza ancora più indolore.
Se premiamo i bottoni e clicchiamo sul link, sbirciando nella directory \logs vedremo 4 files:
1: 15/12/2009 17.02 12.789 error-2009-12-15160210Z-f82885d7-74eb-41c8-bc54-e4d72639f51c.xml
2: 15/12/2009 17.03 15.605 error-2009-12-15160300Z-fa4ecb0c-2d4f-4f85-9a56-f7672cf4bc5e.xml
3: 15/12/2009 17.03 15.601 error-2009-12-15160305Z-1fe0f86d-454b-4cc4-a357-1fb4bc4aa1a1.xml
4: 15/12/2009 17.03 7.247 error-2009-12-15160324Z-13e498bf-87c2-4fce-8af9-e10fc312de81.xml
che sono i quattro files xml contenenti tutti i dati degli errori avvenuti.
navigando invece sulla pagina \public\elmah.axd avremo un report interattivo degli errori avvenuti:
la cosa interessante è che oltre all’ora, all’utente e al tipo di errore, cliccando su details si hanno tutte le informazioni di contesto e di debug che possiamo sperare di avere, dallo stack completo dell’errore a tutte informazioni di contesto http, dal viewstate completo per arrivare perfino alla pagina di errore originale.
conclusioni
Questo post non dà che un assaggio delle potenzialità di ELMAH. Soprattutto la possibilità di innestare in un’applicazione già esistente, senza alcun cambiamento invasivo al codice, di un framework potente e utilissimo per il logging degli errori.
Una volta capito come utilizzare ELMAH e apprezzata la potenza e la facilità d’uso, si potrà approfondirne lo studio e utilizzarne le funzioni più approfonditamente, per esempio leggendo questo articolo: http://code.google.com/p/elmah/wiki/DotNetSlackersArticle.
Buon lavoro e come sempre buon divertimento.