");
html.AppendLine();
html.Append("
More information
");
html.AppendLine();
html.Append("
");
html.AppendLine();
html.Append("");
html.AppendLine();
html.Append("");
html.AppendLine();
html.Append(m_moreInfoTextMethod());
html.AppendLine();
html.Append("");
html.AppendLine();
html.Append(" | |
");
html.AppendLine();
html.Append("");
html.AppendLine();
html.Append("");
html.AppendLine();
html.Append("");
html.AppendLine();
// Display the exception information.
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.Write(html.ToString());
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();
HttpContext.Current.Server.ClearError();
}
///
/// Logs encountered to the .
///
/// that was encountered.
protected virtual void ExceptionToFile(Exception exception)
{
// Log if enabled.
if (m_logToFile)
{
m_logToFileOK = false;
if (!m_errorLog.IsOpen)
m_errorLog.Open();
m_errorLog.WriteTimestampedLine(GetExceptionInfo(exception, m_logUserInfo));
m_logToFileOK = true;
}
}
///
/// Logs encountered to an e-mail message.
///
/// that was encountered.
protected virtual void ExceptionToEmail(Exception exception)
{
// Log if enabled.
if (m_logToEmail && !string.IsNullOrEmpty(m_contactEmail) && !m_suppressInteractiveLogging)
{
m_logToEmailOK = false;
Mail email = new Mail(m_contactEmail, m_contactEmail);
email.Subject = string.Format("Exception in {0} at {1}", ApplicationName, DateTime.Now.ToString());
email.Body = GetExceptionInfo(exception, m_logUserInfo);
email.Attachments = GetEmailAttachments();
email.SmtpServer = m_smtpServer;
email.Send();
m_logToEmailOK = true;
}
}
///
/// Gets or sets the comma-separated or semicolon-separated list of file names to be attached to the message.
///
protected virtual string GetEmailAttachments()
{
return null;
}
///
/// Logs encountered to the .
///
/// that was encountered.
protected virtual void ExceptionToEventLog(Exception exception)
{
// Log if enabled.
if (m_logToEventLog)
{
m_logToEventLogOK = false;
EventLog.WriteEntry(ApplicationName, GetExceptionInfo(exception, m_logUserInfo), EventLogEntryType.Error);
m_logToEventLogOK = true;
}
}
///
/// Logs encountered to the database.
///
/// that was encountered.
protected virtual void ExceptionToDatabase(Exception exception)
{
// Log if enabled.
if (m_logToDatabase)
{
m_logToDatabaseOK = false;
// Make sure a database processing queue exists to process items every two seconds
if ((object)m_databaseErrorQueue == null)
{
m_databaseErrorQueue = ProcessQueue>.CreateSynchronousQueue(ProcessExceptionsToDatabase, 2.0D, 30, false, false);
m_databaseErrorQueue.ProcessException += m_databaseErrorQueue_ProcessException;
m_databaseErrorQueue.Start();
}
// Make sure table size curtailment timer exists to maintain error log table size
if ((object)m_tableSizeCurtailmentTimer == null)
{
m_tableSizeCurtailmentTimer = new Timer();
m_tableSizeCurtailmentTimer.AutoReset = false;
m_tableSizeCurtailmentTimer.Interval = 15000;
m_tableSizeCurtailmentTimer.Enabled = false;
m_tableSizeCurtailmentTimer.Elapsed += m_tableSizeCurtailmentTimer_Elapsed;
}
// Queue up exception to be logged into the database - we only process database inserts once every two
// seconds in case there are many errors, this way database thrashing is kept to a minimum
m_databaseErrorQueue.Add(new Tuple(
exception.Source.ToNonNullNorEmptyString("No Source"),
exception.GetType().FullName,
exception.Message.ToNonNullNorEmptyString("No Message"),
GetExceptionInfo(exception, true)));
m_logToDatabaseOK = true;
}
}
// Handle logging of queued exceptions to database
private void ProcessExceptionsToDatabase(Tuple[] exceptions)
{
using (AdoDataConnection database = new AdoDataConnection("systemSettings"))
using (IDbCommand command = database.Connection.CreateCommand())
using (IDbTransaction transaction = database.Connection.BeginTransaction())
{
try
{
string parameterizedQueryString = database.ParameterizedQueryString("INSERT INTO ErrorLog (Source, Type, Message, Detail) VALUES ({0}, {1}, {2}, {3})", "source", "type", "message", "detail");
// Associate all commands with a single transaction
command.Transaction = transaction;
// Insert new exceptions
foreach (Tuple exception in exceptions)
command.ExecuteNonQuery(parameterizedQueryString, exception.Item1, exception.Item2, exception.Item3, exception.Item4);
// Commit the transaction
transaction.Commit();
// Since we have now added records, kick off lazy timer to check error log size
if ((object)m_tableSizeCurtailmentTimer != null && !m_tableSizeCurtailmentTimer.Enabled)
m_tableSizeCurtailmentTimer.Start();
}
catch
{
// Rollback transaction if there is an exception (e.g., thread is being aborted)
transaction.Rollback();
// Re-throw any captured exception, process queue will handle
throw;
}
}
}
// Maintain log size
private void m_tableSizeCurtailmentTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
using (AdoDataConnection database = new AdoDataConnection("systemSettings"))
using (IDbCommand command = database.Connection.CreateCommand())
using (IDbTransaction transaction = database.Connection.BeginTransaction(database.DefaultIsolationLevel))
{
bool executingTransaction = false;
try
{
// Associate command with transaction
command.Transaction = transaction;
// Get min and max IDs from error log
int minID = Convert.ToInt32(command.ExecuteScalar(string.Format("SELECT MIN(ID) FROM ErrorLog")));
int maxID = Convert.ToInt32(command.ExecuteScalar(string.Format("SELECT MAX(ID) FROM ErrorLog")));
int errorLogSize = maxID - minID; // Roughly, assuming no manual deletions
// When exception log is larger than desired size - delete roughly 25% of the records,
// timeout for this action currently hard-coded to one minute
if (errorLogSize >= m_databaseLogSize)
{
executingTransaction = true;
command.ExecuteNonQuery("DELETE FROM ErrorLog WHERE ID <= " + (int)(errorLogSize / 4 + minID), 60);
transaction.Commit();
}
}
catch
{
// Rollback any executing transaction if there is an exception
if (executingTransaction)
transaction.Rollback();
// Re-throw any captured exception, outer try will handle
throw;
}
}
}
catch (Exception ex)
{
OnLoggingException(ex);
}
}
private void m_databaseErrorQueue_ProcessException(object sender, EventArgs e)
{
OnLoggingException(e.Argument);
}
///
/// Raises the event.
///
/// to send to event.
protected virtual void OnLoggingException(Exception exception)
{
if ((object)LoggingException != null)
LoggingException(this, new EventArgs(exception));
}
///
/// Default for .
///
private string GetErrorText()
{
StringBuilder errorText = new StringBuilder();
errorText.AppendFormat("An unexpected exception has occurred in {0}. ", ApplicationName);
errorText.Append("This may be due to an inconsistent system state or a programming error.");
return errorText.ToString();
}
///
/// Default for .
///
private string GetScopeText()
{
StringBuilder scopeText = new StringBuilder();
switch (ApplicationType)
{
case ApplicationType.WindowsCui:
case ApplicationType.WindowsGui:
scopeText.Append("The action you requested was not performed.");
break;
case ApplicationType.Web:
scopeText.Append("The current page will not load.");
break;
}
return scopeText.ToString();
}
///
/// Default for .
///
private string GetActionText()
{
StringBuilder actionText = new StringBuilder();
switch (ApplicationType)
{
case ApplicationType.WindowsCui:
case ApplicationType.WindowsGui:
actionText.AppendFormat("Restart {0}, and try repeating your last action. ", ApplicationName);
break;
case ApplicationType.Web:
actionText.AppendFormat("Close your browser, navigate back to the {0} website, and try repeating you last action. ", ApplicationName);
break;
}
actionText.Append("Try alternative methods of performing the same action. ");
// Add information about the contact person if provided.
if (!string.IsNullOrEmpty(m_contactName) && (!string.IsNullOrEmpty(m_contactEmail) || !string.IsNullOrEmpty(m_contactPhone)))
{
actionText.AppendFormat("If you need immediate assistance, contact {0} ", m_contactName);
if (!string.IsNullOrEmpty(m_contactEmail))
{
actionText.AppendFormat("via email at {0}", m_contactEmail);
if (!string.IsNullOrEmpty(m_contactPhone))
actionText.Append(" or ");
}
if (!string.IsNullOrEmpty(m_contactPhone))
actionText.AppendFormat("via phone at {0}", m_contactPhone);
actionText.Append(".");
}
return actionText.ToString();
}
///
/// Default for .
///
protected string GetMoreInfoText()
{
string bullet = string.Empty;
switch (ApplicationType)
{
case ApplicationType.WindowsCui:
bullet = "-";
break;
case ApplicationType.Web:
case ApplicationType.WindowsGui:
bullet = "•";
break;
}
StringBuilder moreInfoText = new StringBuilder();
moreInfoText.Append("The following information about the error was automatically captured:");
moreInfoText.AppendLine();
moreInfoText.AppendLine();
if (m_logToEventLog)
{
moreInfoText.AppendFormat(" {0} ", bullet);
moreInfoText.Append(m_logToEventLogOK ? "an event was written to the application log" : "an event could NOT be written to the application log");
moreInfoText.AppendLine();
}
if (m_logToFile)
{
moreInfoText.AppendFormat(" {0} ", bullet);
moreInfoText.Append(m_logToFileOK ? "details were written to a text log at:" : "details could NOT be written to the text log at:");
moreInfoText.AppendLine();
moreInfoText.Append(" ");
moreInfoText.Append(m_errorLog.Name);
moreInfoText.AppendLine();
}
if (m_logToDatabase)
{
moreInfoText.AppendFormat(" {0} ", bullet);
moreInfoText.Append(m_logToDatabaseOK ? "details were queued to be written to the database log" : "details could NOT be queued to be written to the database log");
moreInfoText.AppendLine();
}
if (m_logToEmail)
{
moreInfoText.AppendFormat(" {0} ", bullet);
moreInfoText.Append(m_logToEmailOK ? "an email has been sent to:" : "an email could NOT be sent to:");
moreInfoText.AppendLine();
moreInfoText.Append(" ");
moreInfoText.Append(m_contactEmail);
moreInfoText.AppendLine();
}
string extendedInfoText = GetExtendedMoreInfoText(bullet);
if (!string.IsNullOrWhiteSpace(extendedInfoText))
moreInfoText.Append(extendedInfoText);
moreInfoText.AppendLine();
moreInfoText.AppendLine();
moreInfoText.Append("Detailed error information follows:");
moreInfoText.AppendLine();
moreInfoText.AppendLine();
moreInfoText.Append(GetExceptionInfo(m_lastException, m_logUserInfo));
return moreInfoText.ToString();
}
///
/// Allows other loggers to extend "more info text".
///
/// Type of bullet to use for extended info text.
protected virtual string GetExtendedMoreInfoText(string bullet)
{
return null;
}
private void UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
// Almost always (starting .NET 2.0) when the AppDomain.UnhandledException event is raised, .NET CLR
// will terminate the application after all the handlers for the event have been processed. So, if
// .NET CLR is going to terminate the application, we terminate the application before .NET CLR does
// in order to prevent the .NET "Application has encountered a problem and needs to close" error.
// See: http://msdn.microsoft.com/en-us/library/system.unhandledexceptioneventargs.isterminating.aspx
m_suppressInteractiveLogging = false;
Log(new Exception("UnhandledException", (Exception)e.ExceptionObject), e.IsTerminating);
}
#endregion
#region [ Static ]
// Static Methods
///
/// Gets information about an complete with system and application information.
///
/// whose information is to be retrieved.
/// true if user information is to be include; otherwise false.
/// information in text.
public static string GetExceptionInfo(Exception ex, bool includeUserInfo)
{
StringBuilder info = new StringBuilder();
AggregateException aggregateException = ex as AggregateException;
if ((object)aggregateException != null && (object)aggregateException.InnerExceptions != null && aggregateException.InnerExceptions.Count > 0)
{
int aggregateCount = 1;
foreach (Exception innerException in aggregateException.InnerExceptions)
{
info.AppendFormat("(Aggregate Exception {0})", aggregateCount++);
info.AppendLine();
info.Append(GetExceptionInfo(innerException, includeUserInfo));
}
}
else
{
if ((object)ex.InnerException != null)
{
// Sometimes the original exception is wrapped in a more relevant outer exception
// the detail exception is the "inner" exception
// See: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/exceptdotnet.asp
info.Append("(Inner Exception)");
info.AppendLine();
info.Append(GetExceptionInfo(ex.InnerException, includeUserInfo));
info.AppendLine();
info.Append("(Outer Exception)");
info.AppendLine();
}
// Get general system information.
info.Append(GetSystemInfo(includeUserInfo));
info.AppendLine();
// Get general application information.
info.Append(GetApplicationInfo());
info.AppendLine();
// Get general exception information.
info.Append(GetExceptionGeneralInfo(ex));
info.AppendLine();
// Get the stack trace for the exception.
info.Append("---- Stack Trace ----");
info.AppendLine();
info.Append(GetExceptionStackTrace(ex));
info.AppendLine();
}
return info.ToString();
}
///
/// Gets information about the system where current application is executing.
///
/// true if user information is to be include; otherwise false.
/// System information in text.
private static string GetSystemInfo(bool includeUserInfo)
{
StringBuilder info = new StringBuilder();
info.AppendFormat("Date and Time: {0}", DateTime.Now);
info.AppendLine();
switch (Common.GetApplicationType())
{
case ApplicationType.Web:
info.AppendFormat("Server Name: {0}", Environment.MachineName);
info.AppendLine();
info.AppendFormat("Server IP: {0}", Dns.GetHostEntry(Environment.MachineName).AddressList[0]);
info.AppendLine();
info.AppendFormat("Server OS: {0}", Environment.OSVersion.VersionString);
info.AppendLine();
if (includeUserInfo)
{
UserInfo remoteUserInfo = UserInfo.RemoteUserInfo;
info.AppendFormat("Process User: {0}", UserInfo.CurrentUserID);
info.AppendLine();
info.AppendFormat("Remote User ID: {0}", remoteUserInfo.LoginID);
info.AppendLine();
info.AppendFormat("Remote User Name: {0}", remoteUserInfo.FullName);
info.AppendLine();
info.AppendFormat("Remote User Phone: {0}", remoteUserInfo.Telephone);
info.AppendLine();
info.AppendFormat("Remote User Email: {0}", remoteUserInfo.Email);
info.AppendLine();
}
info.AppendFormat("Remote Host: {0}", HttpContext.Current.Request.ServerVariables["REMOTE_HOST"]);
info.AppendLine();
info.AppendFormat("Remote Address: {0}", HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"]);
info.AppendLine();
info.AppendFormat("HTTP Agent: {0}", HttpContext.Current.Request.ServerVariables["HTTP_USER_AGENT"]);
info.AppendLine();
info.AppendFormat("HTTP Referrer: {0}", HttpContext.Current.Request.ServerVariables["HTTP_REFERER"]);
info.AppendLine();
info.AppendFormat("Web Page URL: {0}", HttpContext.Current.Request.Url);
info.AppendLine();
break;
default:
info.AppendFormat("Machine Name: {0}", Environment.MachineName);
info.AppendLine();
info.AppendFormat("Machine IP: {0}", Dns.GetHostEntry(Environment.MachineName).AddressList[0]);
info.AppendLine();
info.AppendFormat("Machine OS: {0}", Environment.OSVersion.VersionString);
info.AppendLine();
if (includeUserInfo)
{
UserInfo currentUserInfo = UserInfo.CurrentUserInfo;
info.AppendFormat("Current User ID: {0}", currentUserInfo.LoginID);
info.AppendLine();
info.AppendFormat("Current User Name: {0}", currentUserInfo.FullName);
info.AppendLine();
info.AppendFormat("Current User Phone: {0}", currentUserInfo.Telephone);
info.AppendLine();
info.AppendFormat("Current User Email: {0}", currentUserInfo.Email);
info.AppendLine();
}
break;
}
return info.ToString();
}
///
/// Gets information about the current application.
///
/// Application information in text.
private static string GetApplicationInfo()
{
Assembly parentAssembly;
switch (Common.GetApplicationType())
{
case ApplicationType.Web:
// For a web site in .Net 2.0 we don't have an entry assembly. However, at this point the
// calling assembly will be consumer of this function (i.e. one of the web site DLLs).
// See: http://msdn.microsoft.com/msdnmag/issues/06/01/ExtremeASPNET/
parentAssembly = Assembly.GetCallingAssembly();
break;
default:
// For a windows application the entry assembly will be the executable.
parentAssembly = Assembly.GetEntryAssembly();
break;
}
StringBuilder info = new StringBuilder();
AssemblyInfo parentAssemblyInfo = new AssemblyInfo(parentAssembly);
info.AppendFormat("Application Domain: {0}", AppDomain.CurrentDomain.FriendlyName);
info.AppendLine();
info.AppendFormat("Assembly Codebase: {0}", parentAssemblyInfo.CodeBase);
info.AppendLine();
info.AppendFormat("Assembly Full Name: {0}", parentAssemblyInfo.FullName);
info.AppendLine();
info.AppendFormat("Assembly Version: {0}", parentAssemblyInfo.Version);
info.AppendLine();
info.AppendFormat("Assembly Build Date: {0}", parentAssemblyInfo.BuildDate.ToString());
info.AppendLine();
info.AppendFormat(".Net Runtime Version: {0}", Environment.Version);
info.AppendLine();
return info.ToString();
}
///
/// Gets common information about an .
///
/// whose common information is to be retrieved.
/// Common information in text.
private static string GetExceptionGeneralInfo(Exception ex)
{
StringBuilder info = new StringBuilder();
info.AppendFormat("Exception Source: {0}", ex.Source);
info.AppendLine();
info.AppendFormat("Exception Type: {0}", ex.GetType().FullName);
info.AppendLine();
info.AppendFormat("Exception Message: {0}", ex.Message);
info.AppendLine();
if ((object)ex.TargetSite != null)
{
info.AppendFormat("Exception Target Site: {0}", ex.TargetSite.Name);
info.AppendLine();
}
return info.ToString();
}
///
/// Gets stack trace information about an .
///
/// whose stack trace information is to be retrieved.
/// stack trace in text.
private static string GetExceptionStackTrace(Exception ex)
{
StringBuilder trace = new StringBuilder();
StackTrace stack = new StackTrace(ex, true);
for (int i = 0; i < stack.FrameCount; i++)
{
StackFrame stackFrame = stack.GetFrame(i);
MemberInfo method = stackFrame.GetMethod();
string codeFileName = stackFrame.GetFileName();
if (method == null) {
trace.AppendFormat(" Missing Method");
continue;
}
// Build method name.
if ((object)method.DeclaringType != null)
trace.AppendFormat(" {0}.{1}.{2}", method.DeclaringType.Namespace, method.DeclaringType.Name, method.Name);
// Build method parameters
trace.Append("(");
int parameterCount = 0;
foreach (ParameterInfo parameter in stackFrame.GetMethod().GetParameters())
{
parameterCount++;
if (parameterCount > 1)
trace.Append(", ");
trace.AppendFormat("{0} As {1}", parameter.Name, parameter.ParameterType.Name);
}
trace.Append(")");
trace.AppendLine();
// If source code is available, append location info.
trace.Append(" ");
if (!string.IsNullOrEmpty(codeFileName))
{
trace.Append(FilePath.GetFileName(codeFileName));
trace.AppendFormat(": Ln {0:#0000}", stackFrame.GetFileLineNumber());
trace.AppendFormat(", Col {0:#00}", stackFrame.GetFileColumnNumber());
// If IL is available, append IL location info.
if (stackFrame.GetILOffset() != StackFrame.OFFSET_UNKNOWN)
{
trace.AppendFormat(", IL {0:#0000}", stackFrame.GetILOffset());
}
}
else
{
try
{
trace.Append(FilePath.GetFileName(Assembly.GetEntryAssembly().CodeBase));
}
catch
{
trace.Append("(unknown file)");
}
// Native code offset is always available.
trace.AppendFormat(": N {0:#00000}", stackFrame.GetNativeOffset());
}
trace.AppendLine();
}
return trace.ToString();
}
#endregion
}
}