Pokud se sháníte po jednoduchém mechanizmu, jak ve Vašich aplikacích logovat výjimky, pak zřejmě v .NET pro consolové/WinForm aplikace marně hledáte něco, jako je healthMonitoring pro ASP.NET, kde je připraven jednoduchý, ale mocný nástroj a lze pomocí pouhých několika řádků ve web.config souboru získat přehled o výjimkách ve Vaší aplikaci, např.:
<configuration> <system.web> <healthMonitoring enabled="true"> <rules> <add name="Mail Notifications on All Errors" eventName="All Errors" provider="SimpleMailWebEventProvider" profile="Default"/> </rules> <providers> <add name="SimpleMailWebEventProvider" type="System.Web.Management.SimpleMailWebEventProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" from="errors@havit.cz" to="errors@example.cz" subjectPrefix="MyApplication: " buffer="true" bufferMode="Notification" maxEventLength="4096" maxMessagesPerNotification="2"/> </providers> </healthMonitoring> </system.web> </configuration>
…výše uvedená konfigurace Vám zajistí zasílání mailových notifikacích o neobsloužených výjimkách a chyách Vaší aplikace, jednoduché, prosté.
Jak na to u consolových a WinForm aplikací?
Pro consolové či WinForm aplikace nic jako healthMonitoring připraveno není, nicméně s pár řádky kódu lze dosáhnout obdobného efektu a dokonce u toho využít široké možnosti pro tracing v .NET 2.0. Základní myšlenka je prostá:
- .NET 2.0+ v sobě obsahuje široké možnosti pro tracing, kdy můžeme definovat různé zdroje zpráv (TraceSource), zprávy různé rozdělovat (Switch), filtrovat (TraceFilter) a směrovat na různé listenery (výstup do konzole, XML, textového souboru, event-logu, SQL, SMTP, atd. další si můžeme napsat) – to vše bez nutnosti jakýchkoliv doplňků – ať už Microsoftích Enterprise Library / Application Blocků, či produktů třetích stran typu Log4Net. Základní přehled o možnostech si můžeme udělat například z článku A Tracing Primer – Part I [Mike Rousos].
- .NET sám nemá mechanizmus, jak výjimky zapisovat do trace
- napsat pár řádků kódu, které zajistí zápis výjimek do trace je snadné!!!
- do trace můžeme buď výjimky posílat explicitně (níže volání ExceptionTracer.TraceException(myException)),
- nebo se můžeme přihlásit k odběru událostí, které neošetřené výjimky způsobují,
- obecně jde o událost AppDomain.CurrentDomain.UnhandledException
- ve WinForm aplikacích pak událost Application.ThreadException
Interface pro nasazení? …prostý
V nejjednodušší variantě si můžeme udělat například třídu ExceptionTracer, kterou přihlásíme k odběru příslušných událostí a v obsluze událostí už budeme jen výjimku posílat do trace.
Pro konzolové aplikace:
class Program { static void Main(string[] args) { ExceptionTracer.SubscribeToUnhandledExceptions(); throw new InvalidOperationException("Chybka!"); // ExceptionTracer.Default.TraceException(new ArgumentNullException("param", "Sakrapísek!")); } }
Pro WinFormAplikace:
static class Program { [STAThread] static void Main() { ExceptionTracer.SubscribeToWindowsFormsThreadExceptions(); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } }
App.config pak může vypadat nějak takto:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <sources> <source name="Exceptions" switchValue="Error"> <listeners> <add name="LogFileListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="Exceptions.log" /> <add name="XmlListener" initializeData="Exceptions.xml" type="System.Diagnostics.XmlWriterTraceListener" /> </listeners> </source> </sources> </system.diagnostics> </configuration>
ExceptionTracer [Simplified Version]
Jednoduchá, ale plně funkční verze ExceptionTracekru může vypadat třeba takto:
using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; using System.Windows.Forms; namespace Havit.Diagnostics { public class ExceptionTracer { /// <summary> /// Přihlásí ExceptionTracer k odběru všech neobsloužených výjimek (event AppDomain.CurrentDomain.UnhandledException). /// </summary> public static void SubscribeToUnhandledExceptions() { AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); } private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { if (e.ExceptionObject is Exception) { TraceException((Exception)e.ExceptionObject); } } /// <summary> /// Přihlásí ExceptionTracer k odběru všech neobsloužených výjimek WinForm (event Application.ThreadException). /// </summary> public static void SubscribeToWindowsFormsThreadExceptions() { Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException); } private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { TraceException(e.Exception); // původní implementace obsluhy výjimky, WinForm dialog s chybou using (ThreadExceptionDialog excptDlg = new ThreadExceptionDialog(e.Exception)) { DialogResult result = excptDlg.ShowDialog(); if (result == DialogResult.Abort) { Application.Exit(); } } } /// <summary> /// Pošle do trace zadanou výjimku. /// </summary> /// <param name="exception">výjimka k zaznamenání</param> public static void TraceException(Exception exception) { TraceSource ts = new TraceSource("Exceptions"); ts.TraceEvent(TraceEventType.Critical, 0, exception.ToString()); ts.Flush(); ts.Close(); } } }
Příklad výstupu [XML]
<E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent"> <System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system"> <EventID>0</EventID> <Type>3</Type> <SubType Name="Critical">0</SubType> <Level>1</Level> <TimeCreated SystemTime="2008-01-31T21:08:35.0625000Z" /> <Source Name="Exceptions" /> <Correlation ActivityID="{00000000-0000-0000-0000-000000000000}" /> <Execution ProcessName="WindowsFormsApplication1" ProcessID="1232" ThreadID="1" /> <Channel /> <Computer>OSKAR</Computer> </System> <ApplicationData>System.ApplicationException: Test !!! at WindowsFormsApplication1.Form1.button1_Click(Object sender, EventArgs e) in D:\Development\ExceptionLogging\WindowsFormsApplication1\Form1.cs:line 20 at System.Windows.Forms.Control.OnClick(EventArgs e) at System.Windows.Forms.Button.OnClick(EventArgs e) at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent) at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.ButtonBase.WndProc(Message& m) at System.Windows.Forms.Button.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)</ApplicationData> </E2ETraceEvent>