//****************************************************************************************************** // ErrorLogger.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 08/23/2013 - J. Ritchie Carroll // Migrated to be a Windows Forms specific error logging engine (instead of part of core). // //****************************************************************************************************** using System; using System.ComponentModel; using System.Configuration; using System.Drawing; using System.Drawing.Imaging; using System.Text; using System.Threading; using System.Windows.Forms; using GSF.Configuration; using GSF.IO; using GSF.Net.Smtp; using GSF.Windows.Forms; namespace GSF.Windows.ErrorManagement { /// /// Represents a logger that can be used for logging handled as well as unhandled exceptions across multiple /// application types (Windows Application, Console Application, Windows Service, Web Application, Web Service). /// /// /// This example shows how to use the component to log handled and unhandled exceptions: /// /// using System; /// using System.IO; /// using GSF.Windows.ErrorManagement; /// /// class Program /// { /// static ErrorLogger s_logger; /// /// static Program() /// { /// s_logger = new ErrorLogger(); /// s_logger.LogToUI = true; // Show exception info on the UI. /// s_logger.LogToFile = true; // Log exception info to a file. /// s_logger.LogToEmail = true; // Send exception info in an e-mail. /// s_logger.LogToEventLog = true; // Log exception info to the event log. /// s_logger.LogToScreenshot = true; // Take a screenshot of desktop on exception. /// s_logger.ContactEmail = "dev@email.com"; // Provide an e-mail address. /// s_logger.HandleUnhandledException = true; // Configure to handle unhandled exceptions. /// s_logger.PersistSettings = true; // Save settings to the config file. /// s_logger.Initialize(); // Initialize ErrorLogger component for use. /// } /// /// static void Main(string[] args) /// { /// try /// { /// // This may cause a handled FileNotFoundException if the file doesn't exist. /// string data = File.ReadAllText(@"c:\NonExistentFile.txt"); /// } /// catch (Exception ex) /// { /// // When logging handled exceptions we want to disable loggers (UI logger and E-mail logger) that /// // may require interaction either directly or indirectly as it can be annoying. All the loggers /// // are enabled automatically after the handled exception has been logged. /// s_logger.SuppressInteractiveLogging(); /// s_logger.Log(ex); /// } /// /// int numerator = 1; /// int denominator = 0; /// int result = numerator / denominator; // This will cause an unhandled DivideByZeroException. /// /// Console.ReadLine(); /// } /// } /// /// /// [ToolboxBitmap(typeof(GSF.ErrorManagement.ErrorLogger))] public class ErrorLogger : GSF.ErrorManagement.ErrorLogger { #region [ Members ] // Constants /// /// Specifies the default value for the property. /// public const bool DefaultLogToScreenshot = false; // Fields private bool m_logToScreenshot; private bool m_logToScreenshotOK; #endregion #region [ Constructors ] /// /// Initializes a new instance of the class. /// public ErrorLogger() { m_logToScreenshot = DefaultLogToScreenshot; // Initialize all logger methods. Loggers.Add(ExceptionToScreenshot); } #endregion #region [ Properties ] /// /// Gets or sets a boolean value that indicates whether a screenshot of the user's desktop is to be taken /// when an is logged. /// /// /// This setting is ignored in Web Application and Web Service application types. /// [Category("Logging"), DefaultValue(DefaultLogToScreenshot), Description("Indicates whether a screenshot of the user's desktop is to be taken when an Exception is logged.")] public bool LogToScreenshot { get { return m_logToScreenshot; } set { m_logToScreenshot = value; } } /// /// Gets the descriptive status of the object. /// public override string Status { get { StringBuilder status = new StringBuilder(); status.Append(base.Status); status.Append(" Error to Screenshot: "); status.Append(m_logToScreenshot ? "Enabled" : "Disabled"); status.AppendLine(); return status.ToString(); } } #endregion #region [ Methods ] /// /// Saves settings for the object to the config file if the /// property is set to true. /// /// has a value of null or empty string. public override void SaveSettings() { if (PersistSettings) { // Ensure that settings category is specified. if (string.IsNullOrEmpty(SettingsCategory)) throw new ConfigurationErrorsException("SettingsCategory property has not been set"); // Save settings under the specified category. ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory]; settings["LogToScreenshot", true].Update(m_logToScreenshot); base.SaveSettings(); } } /// /// Loads saved settings for the object from the config file if the /// property is set to true. /// /// has a value of null or empty string. public override void LoadSettings() { if (PersistSettings) { base.LoadSettings(); // Ensure that settings category is specified. if (string.IsNullOrEmpty(SettingsCategory)) throw new ConfigurationErrorsException("SettingsCategory property has not been set"); // Load settings from the specified category. ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory]; settings.Add("LogToScreenshot", m_logToScreenshot, "True if a screenshot is to be taken when an exception is encountered; otherwise False."); LogToScreenshot = settings["LogToScreenshot"].ValueAs(m_logToScreenshot); } } /// /// Registers the object to handle unhandled if the /// property is set to true. /// /// true if handlers were registered; otherwise false. protected override bool Register() { if (base.Register()) { Application.ThreadException += ThreadException; return true; } return false; } /// /// Unregister the object from handling unhandled . /// /// true if handlers were unregistered; otherwise false. protected override bool Unregister() { if (base.Unregister()) { Application.ThreadException -= ThreadException; return true; } return false; } /// /// Shows information in a Windows Application. /// protected override void ExceptionToWindowsGui() { // Use the ErrorDialog to show exception information. ErrorDialog dialog = new ErrorDialog(); dialog.Text = string.Format(dialog.Text, ApplicationName, null); dialog.PictureBoxIcon.Image = SystemIcons.Error.ToBitmap(); dialog.RichTextBoxError.Text = ErrorTextMethod(); dialog.RichTextBoxScope.Text = ScopeTextMethod(); dialog.RichTextBoxAction.Text = ActionTextMethod(); dialog.RichTextBoxMoreInfo.Text = MoreInfoTextMethod(); dialog.ShowDialog(); } /// /// Takes a screenshot of the user's desktop when the is encountered. /// /// that was encountered. protected virtual void ExceptionToScreenshot(Exception exception) { // Log if enabled. if (m_logToScreenshot && (ApplicationType == ApplicationType.WindowsCui || ApplicationType == ApplicationType.WindowsGui)) { m_logToScreenshotOK = false; using (Bitmap screenshot = ScreenArea.Capture(ImageFormat.Png)) { screenshot.Save(GetScreenshotFileName()); } m_logToScreenshotOK = true; } } /// /// Gets or sets the comma-separated or semicolon-separated list of file names to be attached to the message. /// protected override string GetEmailAttachments() { // If logging to screen shot, attach it to e-mail exceptions if (m_logToScreenshot) return GetScreenshotFileName(); return null; } /// /// Allows other loggers to extend "more info text". /// /// Type of bullet to use for extended info text. protected override string GetExtendedMoreInfoText(string bullet) { if (m_logToScreenshot) { StringBuilder moreInfoText = new StringBuilder(); moreInfoText.AppendFormat(" {0} ", bullet); if (m_logToScreenshotOK) { moreInfoText.Append("a screenshot was taken of the desktop at:"); moreInfoText.AppendLine(); moreInfoText.Append(" "); moreInfoText.Append(GetScreenshotFileName()); } else { moreInfoText.Append("a screenshot could NOT be taken of the desktop."); } moreInfoText.AppendLine(); return moreInfoText.ToString(); } return null; } private string GetScreenshotFileName() { return FilePath.GetAbsolutePath(ApplicationName + ".ErrorState.png"); } private void ThreadException(object sender, ThreadExceptionEventArgs e) { SuppressInteractiveLogging = false; Log(new Exception("ThreadException", e.Exception), ExitOnUnhandledException); } #endregion } }