V návaznosti na používání ExceptionTraceru, který pomocí standardní .NET mechanizmů Trace/TraceSource loguje neobsloužené výjimky, jsem dopsal jednoduchý TraceListener, který lze používání pro mailování těchto výjimek na určený mail. Cílovým scénářem je tedy automatické mailování výjimek z consolových/WinForm aplikací, či spíše utilit. Není to zatím příliš vyladěno, spíše takový náznak, jakou cestou se vydat.
Do činnosti se to zapojuje zhruba takto (app.config):
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <sources> <source name="Exceptions" switchValue="Error"> <listeners> <!-- <add name="XmlListener" initializeData="Exceptions.xml" type="System.Diagnostics.XmlWriterTraceListener"/> <add name="TextWriterListener" initializeData="Exceptions.log" type="System.Diagnostics.TextWriterTraceListener"/> --> <add name="SmtpListener" initializeData="Subject=Chyby z mojí utility;To=errors@firma.cz" type="Havit.Diagnostics.SmtpTraceListener, Havit"/> </listeners> </source> </sources> </system.diagnostics> <system.net> <mailSettings> <smtp deliveryMethod="Network" from="neco@nekde.cz"> <network host="mail.server.cz"/> </smtp> </mailSettings> </system.net> </configuration>
Základem je implementovat třídu dědící z předka TraceListener:
/// <summary> /// TraceListener, který výstup posílá mailem. /// </summary> /// <remarks> /// Inspirováno implementaci System.Diagnostics.XmlWriterListener. /// </remarks> public class SmtpTraceListener : TraceListener { #region MailTo /// <summary> /// E-mailová adresa, na kterou se posílají zprávy. /// </summary> public string MailTo { get { if (_mailTo == null) { return "devmail@havit.cz"; } return _mailTo; } set { _mailTo = value; } } private string _mailTo; #endregion #region Subject /// <summary> /// Subject zprávy. /// </summary> public string Subject { get { if (_subject == null) { return "SmtpTraceListener"; } return _subject; } set { _subject = value; } } private string _subject; #endregion #region Constructors /// <summary> /// Constructor, který je volán při použití TraceListerneru z app.configu a předává se do něj hodnota atributu initializeData. /// </summary> /// <param name="initializeData">hodnota atributu initializeData z app.config</param> public SmtpTraceListener(string initializeData) { if (initializeData == null) { return; // použijí se defaulty } foreach (string arg in initializeData.Split(';')) { string[] paramValue = arg.Split('='); if (paramValue.Length >= 2) { switch (paramValue[0].Trim().ToUpper()) { case "TO": MailTo = paramValue[1].Trim(); break; case "SUBJECT": Subject = paramValue[1].Trim(); break; default: throw new InvalidOperationException("Neznámý parametr konfigurace SmtpTraceListeneru v initializeData."); } } } } #endregion #region SendMessage /// <summary> /// Interní implementace odesílání mailu. /// </summary> /// <param name="message">zpráva z trace</param> private void SendMessage(string message) { if (String.IsNullOrEmpty(this.MailTo)) { return; } try { MailMessage mailMessage = new MailMessage(); mailMessage.To.Add(this.MailTo); mailMessage.Subject = this.Subject; mailMessage.Body = message; SmtpClient smtpClient = new SmtpClient(); smtpClient.Send(mailMessage); } catch { // NOOP - nechceme, aby nám nefunkční trace-mailing zabil server #if DEBUG // při debugování nás to ale zajímá throw; #endif } // http://www.codeproject.com/KB/trace/smtptracelistenerarticle.aspx // In the SMTPTraceListener Write method - I call the Flush method. This forces the e-mail output to happen right then, and makes the component more stable. // With the Flush taken out of the Write method, I was experiencing some inconsistent behavior - i.e. exceptions thrown sometimes but not always... // goofy problem perhaps someone knows why? this.Flush(); } #endregion #region SendTrace /// <summary> /// Hlavní interní implementace sestavení mailu. /// </summary> /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param> /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param> /// <param name="eventType">One of the <see cref="T:System.Diagnostics.TraceEventType"/> values specifying the type of event that has caused the trace.</param> /// <param name="id">A numeric identifier for the event.</param> /// <param name="data">An array of objects to emit as data. Pokud je string, obsahuje přímo text zprávy.</param> private void SendTrace(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data) { StringBuilder message = new StringBuilder(); foreach (object item in data) { if (item != null) { message.AppendLine(item.ToString()); } } message.AppendLine(); message.Append("CommandLine: "); message.AppendLine(Environment.CommandLine); message.Append("CurrentDirectory: "); message.AppendLine(Environment.CurrentDirectory); message.Append("MachineName: "); message.AppendLine(Environment.MachineName); message.Append("UserDomainName: "); message.AppendLine(Environment.UserDomainName); message.Append(".NET Framework: "); message.AppendLine(Environment.Version.ToString()); if (eventCache != null) { message.AppendLine(); message.AppendLine("Call stack:"); message.AppendLine(eventCache.Callstack); message.AppendLine(); message.AppendLine("Logical operation stack:"); foreach (object item in eventCache.LogicalOperationStack) { if (item != null) { message.AppendLine(item.ToString()); } } message.AppendLine(); message.Append("DateTime: "); message.AppendLine(eventCache.DateTime.ToString()); message.Append("Timestamp: "); message.AppendLine(eventCache.Timestamp.ToString()); message.Append("ProcessId: "); message.AppendLine(eventCache.ProcessId.ToString()); message.Append("ThreadId: "); message.AppendLine(eventCache.ThreadId); } if (!String.IsNullOrEmpty(source)) { message.Append("Source: "); message.AppendLine(source); } message.Append("EventType: "); message.AppendLine(eventType.ToString("g")); message.Append("EventId: "); message.AppendLine(id.ToString("g")); SendMessage(message.ToString()); } #endregion #region TraceData (override) /// <summary> /// Writes trace information, an array of data objects and event information to the listener specific output. /// </summary> /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param> /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param> /// <param name="eventType">One of the <see cref="T:System.Diagnostics.TraceEventType"/> values specifying the type of event that has caused the trace.</param> /// <param name="id">A numeric identifier for the event.</param> /// <param name="data">An array of objects to emit as data.</param> /// <PermissionSet> /// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/> /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/> /// </PermissionSet> public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data) { if ((Filter == null) || Filter.ShouldTrace(eventCache, source, eventType, id, null, null, null, data)) { SendTrace(eventCache, source, eventType, id, data); } } /// <summary> /// Writes trace information, an array of data objects and event information to the listener specific output. /// </summary> /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param> /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param> /// <param name="eventType">One of the <see cref="T:System.Diagnostics.TraceEventType"/> values specifying the type of event that has caused the trace.</param> /// <param name="id">A numeric identifier for the event.</param> /// <param name="data">An array of objects to emit as data.</param> /// <PermissionSet> /// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/> /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/> /// </PermissionSet> public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data) { if ((Filter == null) || Filter.ShouldTrace(eventCache, source, eventType, id, null, null, data, null)) { SendTrace(eventCache, source, eventType, id, data); } } #endregion #region TraceEvent (override) /// <summary> /// Writes trace information, a message, and event information to the listener specific output. /// </summary> /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param> /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param> /// <param name="eventType">One of the <see cref="T:System.Diagnostics.TraceEventType"/> values specifying the type of event that has caused the trace.</param> /// <param name="id">A numeric identifier for the event.</param> /// <param name="message">A message to write.</param> /// <PermissionSet> /// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/> /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/> /// </PermissionSet> public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) { if ((Filter == null) || Filter.ShouldTrace(eventCache, source, eventType, id, message, null, null, null)) { SendTrace(eventCache, source, eventType, id, message); } } /// <summary> /// Writes trace information, a formatted array of objects and event information to the listener specific output. /// </summary> /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param> /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param> /// <param name="eventType">One of the <see cref="T:System.Diagnostics.TraceEventType"/> values specifying the type of event that has caused the trace.</param> /// <param name="id">A numeric identifier for the event.</param> /// <param name="format">A format string that contains zero or more format items, which correspond to objects in the <paramref name="args"/> array.</param> /// <param name="args">An object array containing zero or more objects to format.</param> /// <PermissionSet> /// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/> /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/> /// </PermissionSet> public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args) { if ((Filter == null) || Filter.ShouldTrace(eventCache, source, eventType, id, format, args, null, null)) { SendTrace(eventCache, source, eventType, id, String.Format(CultureInfo.InvariantCulture, format, args)); } } #endregion #region TraceTransfer (override) /// <summary> /// Writes trace information, a message, a related activity identity and event information to the listener specific output. /// </summary> /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param> /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param> /// <param name="id">A numeric identifier for the event.</param> /// <param name="message">A message to write.</param> /// <param name="relatedActivityId">A <see cref="T:System.Guid"/> object identifying a related activity.</param> public override void TraceTransfer(TraceEventCache eventCache, string source, int id, string message, Guid relatedActivityId) { SendTrace(eventCache, source, TraceEventType.Transfer, id, String.Format("{0} : {1}", message, relatedActivityId)); } #endregion #region Write, WriteLine (override) /// <summary> /// When overridden in a derived class, writes the specified message to the listener you create in the derived class. /// </summary> /// <param name="message">A message to write.</param> public override void Write(string message) { TraceEvent(null, "Write", TraceEventType.Information, 0, message); } /// <summary> /// When overridden in a derived class, writes a message to the listener you create in the derived class, followed by a line terminator. /// </summary> /// <param name="message">A message to write.</param> public override void WriteLine(string message) { this.Write(message); } #endregion }