source: branches/eraser6/BlackBox/Eraser.Util/BlackBox.cs @ 1609

Revision 1609, 23.2 KB checked in by lowjoel, 5 years ago (diff)

A few BlackBox? behavioural changes which should fix #315: Fix handling of corrupt crash reports

  • Crash Reports are named using UTC time
  • We will handle exceptions thrown during the creation of the error report. However, if we do not manage to even write a stack trace, then delete the whole report and abort generation of the report. This is supposed to deal with low-disk space events (which Eraser is good at triggering...)
  • Upon restart, if the Report cannot have its own stack trace loaded, indicative of an error deleting the incomplete report, an InvalidDataException? will be thrown and the report will delete itself (as unanticipated cleanup)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2009 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25using System.Windows.Forms;
26using System.IO;
27using System.Runtime.InteropServices;
28using Microsoft.Win32.SafeHandles;
29using System.Diagnostics;
30using System.Threading;
31using System.Drawing;
32using System.Drawing.Imaging;
33using System.Reflection;
34using System.Collections.ObjectModel;
35using System.Globalization;
36
37namespace Eraser.Util
38{
39    /// <summary>
40    /// Handles application exceptions, stores minidumps and uploads them to the
41    /// Eraser server.
42    /// </summary>
43    public class BlackBox
44    {
45        /// <summary>
46        /// Stores DLL references for this class.
47        /// </summary>
48        private static class NativeMethods
49        {
50            /// <summary>
51            /// Writes user-mode minidump information to the specified file.
52            /// </summary>
53            /// <param name="hProcess">A handle to the process for which the information
54            /// is to be generated.</param>
55            /// <param name="ProcessId">The identifier of the process for which the information
56            /// is to be generated.</param>
57            /// <param name="hFile">A handle to the file in which the information is to be
58            /// written.</param>
59            /// <param name="DumpType">The type of information to be generated. This parameter
60            /// can be one or more of the values from the MINIDUMP_TYPE enumeration.</param>
61            /// <param name="ExceptionParam">A pointer to a MiniDumpExceptionInfo structure
62            /// describing the client exception that caused the minidump to be generated.
63            /// If the value of this parameter is NULL, no exception information is included
64            /// in the minidump file.</param>
65            /// <param name="UserStreamParam">Not supported. Use IntPtr.Zero</param>
66            /// <param name="CallbackParam">Not supported. Use IntPtr.Zero</param>
67            /// <returns>If the function succeeds, the return value is true; otherwise, the
68            /// return value is false. To retrieve extended error information, call GetLastError.
69            /// Note that the last error will be an HRESULT value.</returns>
70            [DllImport("dbghelp.dll", SetLastError = true)]
71            [return: MarshalAs(UnmanagedType.Bool)]
72            public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId,
73                SafeFileHandle hFile, MiniDumpType DumpType,
74                ref MiniDumpExceptionInfo ExceptionParam, IntPtr UserStreamParam,
75                IntPtr CallbackParam);
76
77            /// <summary>
78            /// Identifies the type of information that will be written to the minidump file
79            /// by the MiniDumpWriteDump function.
80            /// </summary>
81            public enum MiniDumpType
82            {
83                /// <summary>
84                /// Include just the information necessary to capture stack traces for all
85                /// existing threads in a process.
86                /// </summary>
87                MiniDumpNormal = 0x00000000,
88
89                /// <summary>
90                /// Include the data sections from all loaded modules. This results in the
91                /// inclusion of global variables, which can make the minidump file significantly
92                /// larger. For per-module control, use the ModuleWriteDataSeg enumeration
93                /// value from MODULE_WRITE_FLAGS.
94                /// </summary>
95                MiniDumpWithDataSegs = 0x00000001,
96
97                /// <summary>
98                /// Include all accessible memory in the process. The raw memory data is
99                /// included at the end, so that the initial structures can be mapped directly
100                /// without the raw memory information. This option can result in a very large
101                /// file.
102                /// </summary>
103                MiniDumpWithFullMemory = 0x00000002,
104
105                /// <summary>
106                /// Include high-level information about the operating system handles that are
107                /// active when the minidump is made.
108                /// </summary>
109                MiniDumpWithHandleData = 0x00000004,
110
111                /// <summary>
112                /// Stack and backing store memory written to the minidump file should be
113                /// filtered to remove all but the pointer values necessary to reconstruct a
114                /// stack trace. Typically, this removes any private information.
115                /// </summary>
116                MiniDumpFilterMemory = 0x00000008,
117
118                /// <summary>
119                /// Stack and backing store memory should be scanned for pointer references
120                /// to modules in the module list. If a module is referenced by stack or backing
121                /// store memory, the ModuleWriteFlags member of the MINIDUMP_CALLBACK_OUTPUT
122                /// structure is set to ModuleReferencedByMemory.
123                /// </summary>
124                MiniDumpScanMemory = 0x00000010,
125
126                /// <summary>
127                /// Include information from the list of modules that were recently unloaded,
128                /// if this information is maintained by the operating system.
129                /// </summary>
130                MiniDumpWithUnloadedModules = 0x00000020,
131
132                /// <summary>
133                /// Include pages with data referenced by locals or other stack memory.
134                /// This option can increase the size of the minidump file significantly.
135                /// </summary>
136                MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
137
138                /// <summary>
139                /// Filter module paths for information such as user names or important
140                /// directories. This option may prevent the system from locating the image
141                /// file and should be used only in special situations.
142                /// </summary>
143                MiniDumpFilterModulePaths = 0x00000080,
144
145                /// <summary>
146                /// Include complete per-process and per-thread information from the operating
147                /// system.
148                /// </summary>
149                MiniDumpWithProcessThreadData = 0x00000100,
150
151                /// <summary>
152                /// Scan the virtual address space for PAGE_READWRITE memory to be included.
153                /// </summary>
154                MiniDumpWithPrivateReadWriteMemory = 0x00000200,
155
156                /// <summary>
157                /// Reduce the data that is dumped by eliminating memory regions that are not
158                /// essential to meet criteria specified for the dump. This can avoid dumping
159                /// memory that may contain data that is private to the user. However, it is
160                /// not a guarantee that no private information will be present.
161                /// </summary>
162                MiniDumpWithoutOptionalData = 0x00000400,
163
164                /// <summary>
165                /// Include memory region information. For more information, see
166                /// MINIDUMP_MEMORY_INFO_LIST.
167                /// </summary>
168                MiniDumpWithFullMemoryInfo = 0x00000800,
169
170                /// <summary>
171                /// Include thread state information. For more information, see
172                /// MINIDUMP_THREAD_INFO_LIST.
173                /// </summary>
174                MiniDumpWithThreadInfo = 0x00001000,
175
176                /// <summary>
177                /// Include all code and code-related sections from loaded modules to capture
178                /// executable content. For per-module control, use the ModuleWriteCodeSegs
179                /// enumeration value from MODULE_WRITE_FLAGS.
180                /// </summary>
181                MiniDumpWithCodeSegs = 0x00002000,
182
183                /// <summary>
184                /// Turns off secondary auxiliary-supported memory gathering.
185                /// </summary>
186                MiniDumpWithoutAuxiliaryState = 0x00004000,
187
188                /// <summary>
189                /// Requests that auxiliary data providers include their state in the dump
190                /// image; the state data that is included is provider dependent. This option
191                /// can result in a large dump image.
192                /// </summary>
193                MiniDumpWithFullAuxiliaryState = 0x00008000,
194
195                /// <summary>
196                /// Scans the virtual address space for PAGE_WRITECOPY memory to be included.
197                /// </summary>
198                MiniDumpWithPrivateWriteCopyMemory = 0x00010000,
199
200                /// <summary>
201                /// If you specify MiniDumpWithFullMemory, the MiniDumpWriteDump function will
202                /// fail if the function cannot read the memory regions; however, if you include
203                /// MiniDumpIgnoreInaccessibleMemory, the MiniDumpWriteDump function will
204                /// ignore the memory read failures and continue to generate the dump. Note that
205                /// the inaccessible memory regions are not included in the dump.
206                /// </summary>
207                MiniDumpIgnoreInaccessibleMemory = 0x00020000,
208
209                /// <summary>
210                /// Adds security token related data. This will make the "!token" extension work
211                /// when processing a user-mode dump.
212                /// </summary>
213                MiniDumpWithTokenInformation = 0x00040000
214            }
215
216            /// <summary>
217            /// Contains the exception information written to the minidump file by the
218            /// MiniDumpWriteDump function.
219            /// </summary>
220            [StructLayout(LayoutKind.Sequential, Pack = 4)]
221            public struct MiniDumpExceptionInfo
222            {
223                /// <summary>
224                /// The identifier of the thread throwing the exception.
225                /// </summary>
226                public uint ThreadId;
227
228                /// <summary>
229                ///  A pointer to an EXCEPTION_POINTERS structure specifying a
230                ///  computer-independent description of the exception and the processor
231                ///  context at the time of the exception.
232                /// </summary>
233                public IntPtr ExceptionPointers;
234
235                /// <summary>
236                /// Determines where to get the memory regions pointed to by the
237                /// ExceptionPointers member. Set to TRUE if the memory resides in the
238                /// process being debugged (the target process of the debugger). Otherwise,
239                /// set to FALSE if the memory resides in the address space of the calling
240                /// program (the debugger process). If you are accessing local memory (in
241                /// the calling process) you should not set this member to TRUE.
242                /// </summary>
243                [MarshalAs(UnmanagedType.Bool)]
244                public bool ClientPointers;
245            }
246        }
247
248        /// <summary>
249        /// Initialises the BlackBox handler. Call this initialiser once throughout
250        /// the lifespan of the application.
251        /// </summary>
252        /// <returns>The global BlackBox instance.</returns>
253        public static BlackBox Get()
254        {
255            if (Instance == null)
256                Instance = new BlackBox();
257            return Instance;
258        }
259
260        /// <summary>
261        /// Creates a new BlackBox report based on the exception provided.
262        /// </summary>
263        /// <param name="e">The exception which triggered this dump.</param>
264        public void CreateReport(Exception e)
265        {
266            if (e == null)
267                throw new ArgumentNullException("e");
268
269            //Generate a unique identifier for this report.
270            string crashName = DateTime.Now.ToUniversalTime().ToString(
271                CrashReportName, CultureInfo.InvariantCulture);
272            string currentCrashReport = Path.Combine(CrashReportsPath, crashName);
273            Directory.CreateDirectory(currentCrashReport);
274
275            //Store the steps which we have completed.
276            int currentStep = 0;
277
278            try
279            {
280                //First, write a user-readable summary
281                WriteDebugLog(currentCrashReport, e);
282                ++currentStep;
283
284                //Take a screenshot
285                WriteScreenshot(currentCrashReport);
286                ++currentStep;
287
288                //Write a memory dump to the folder
289                WriteMemoryDump(currentCrashReport, e);
290                ++currentStep;
291            }
292            catch
293            {
294                //If an exception was caught while creating the report, we should just
295                //abort as that may cause a cascade. However, we need to remove the
296                //report folder if the crash report is empty.
297                if (currentStep == 0)
298                    Directory.Delete(currentCrashReport);
299            }
300        }
301
302        /// <summary>
303        /// Enumerates the list of crash dumps waiting for upload.
304        /// </summary>
305        /// <returns>A string array containing the list of dumps waiting for upload.</returns>
306        public BlackBoxReport[] GetDumps()
307        {
308            DirectoryInfo dirInfo = new DirectoryInfo(CrashReportsPath);
309            List<BlackBoxReport> result = new List<BlackBoxReport>();
310            if (dirInfo.Exists)
311                foreach (DirectoryInfo subDir in dirInfo.GetDirectories())
312                    try
313                    {
314                        result.Add(new BlackBoxReport(Path.Combine(CrashReportsPath, subDir.Name)));
315                    }
316                    catch (InvalidDataException)
317                    {
318                        //Do nothing: invalid reports are automatically deleted.
319                    }
320
321            return result.ToArray();
322        }
323
324        /// <summary>
325        /// Constructor. Use the <see cref="Initialise"/> function to use this class.
326        /// </summary>
327        private BlackBox()
328        {
329            AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
330            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
331        }
332
333        /// <summary>
334        /// Called when an unhandled exception is raised in the application.
335        /// </summary>
336        private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
337        {
338            CreateReport(e.ExceptionObject as Exception);
339        }
340
341        /// <summary>
342        /// Dumps the contents of memory to a dumpfile.
343        /// </summary>
344        /// <param name="dumpFolder">Path to the folder to store the dump file.</param>
345        /// <param name="e">The exception which is being handled.</param>
346        private void WriteMemoryDump(string dumpFolder, Exception e)
347        {
348            //Open a file stream
349            using (FileStream stream = new FileStream(Path.Combine(dumpFolder, MemoryDumpFileName),
350                FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
351            {
352                //Store the exception information
353                NativeMethods.MiniDumpExceptionInfo exception =
354                    new NativeMethods.MiniDumpExceptionInfo();
355                exception.ClientPointers = false;
356                exception.ExceptionPointers = Marshal.GetExceptionPointers();
357                exception.ThreadId = (uint)AppDomain.GetCurrentThreadId();
358
359                NativeMethods.MiniDumpWriteDump(Process.GetCurrentProcess().Handle,
360                    (uint)Process.GetCurrentProcess().Id, stream.SafeFileHandle,
361                    NativeMethods.MiniDumpType.MiniDumpWithFullMemory,
362                    ref exception, IntPtr.Zero, IntPtr.Zero);
363            }
364        }
365
366        /// <summary>
367        /// Writes a debug log to the given directory.
368        /// </summary>
369        /// <param name="screenshotPath">The path to store the screenshot into.</param>
370        /// <param name="exception">The exception to log about.</param>
371        private void WriteDebugLog(string dumpFolder, Exception exception)
372        {
373            using (FileStream file = new FileStream(Path.Combine(dumpFolder, DebugLogFileName),
374                FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
375            using (StreamWriter stream = new StreamWriter(file))
376            {
377                //Application information
378                string separator = new string('-', 76);
379                string lineFormat = "{0,15}: {1}";
380                stream.WriteLine("Application Information");
381                stream.WriteLine(separator);
382                stream.WriteLine(string.Format(lineFormat, "Version",
383                    Assembly.GetEntryAssembly().GetName().Version));
384                StringBuilder commandLine = new StringBuilder();
385                foreach (string param in Environment.GetCommandLineArgs())
386                {
387                    commandLine.Append(param);
388                    commandLine.Append(' ');
389                }
390                stream.WriteLine(string.Format(lineFormat, "Command Line",
391                    commandLine.ToString().Trim()));
392
393                //Exception Information
394                stream.WriteLine();
395                stream.WriteLine("Exception Information (Outermost to innermost)");
396                stream.WriteLine(separator);
397
398                //Open a stream to the Stack Trace Log file. We want to separate the stack
399                //trace do we can check against the server to see if the crash is a new one
400                using (StreamWriter stackTraceLog = new StreamWriter(
401                    Path.Combine(dumpFolder, BlackBoxReport.StackTraceFileName)))
402                {
403                    Exception currentException = exception;
404                    for (uint i = 1; currentException != null; ++i)
405                    {
406                        stream.WriteLine(string.Format("Exception {0}:", i));
407                        stream.WriteLine(string.Format(lineFormat, "Message", currentException.Message));
408                        stream.WriteLine(string.Format(lineFormat, "Exception Type",
409                            currentException.GetType().FullName));
410                        stackTraceLog.WriteLine(string.Format("Exception {0}: {1}", i,
411                            currentException.GetType().FullName));
412
413                        //Parse the stack trace
414                        string[] stackTrace = currentException.StackTrace.Split(new char[] { '\n' });
415                        for (uint j = 0; j < stackTrace.Length; ++j)
416                        {
417                            stream.WriteLine(string.Format(lineFormat,
418                                string.Format("Stack Trace [{0}]", j), stackTrace[j].Trim()));
419                            stackTraceLog.WriteLine(string.Format("{0}", stackTrace[j].Trim()));
420                        }
421
422                        uint k = 0;
423                        foreach (System.Collections.DictionaryEntry value in currentException.Data)
424                            stream.WriteLine(string.Format(lineFormat, string.Format("Data[{0}]", ++k),
425                                string.Format("{0} {1}", value.Key.ToString(), value.Value.ToString())));
426
427                        //End the exception and get the inner exception.
428                        stream.WriteLine();
429                        currentException = exception.InnerException;
430                    }
431                }
432            }
433        }
434
435        /// <summary>
436        /// Writes a screenshot to the given directory
437        /// </summary>
438        /// <param name="dumpFolder">The path to save the screenshot to.</param>
439        private void WriteScreenshot(string dumpFolder)
440        {
441            //Get the size of the screen
442            Rectangle rect = new Rectangle(int.MaxValue, int.MaxValue, int.MinValue, int.MinValue);
443            foreach (Screen screen in Screen.AllScreens)
444                rect = Rectangle.Union(rect, screen.Bounds);
445
446            //Copy a screen DC to the screenshot bitmap
447            Bitmap screenShot = new Bitmap(rect.Width, rect.Height);
448            Graphics bitmap = Graphics.FromImage(screenShot);
449            bitmap.CopyFromScreen(0, 0, 0, 0, rect.Size, CopyPixelOperation.SourceCopy);
450
451            //Save the bitmap to disk
452            screenShot.Save(Path.Combine(dumpFolder, ScreenshotFileName), ImageFormat.Png);
453        }
454
455        /// <summary>
456        /// The global BlackBox instance.
457        /// </summary>
458        private static BlackBox Instance;
459
460        /// <summary>
461        /// The path to all Eraser crash reports.
462        /// </summary>
463        private static readonly string CrashReportsPath = Path.Combine(Environment.GetFolderPath(
464            Environment.SpecialFolder.LocalApplicationData), @"Eraser 6\Crash Reports");
465
466        /// <summary>
467        /// The report name format.
468        /// </summary>
469        internal static readonly string CrashReportName = "yyyyMMdd HHmmss.FFF";
470
471        /// <summary>
472        /// The file name of the memory dump.
473        /// </summary>
474        ///
475        internal static readonly string MemoryDumpFileName = "Memory.dmp";
476
477        /// <summary>
478        /// The file name of the debug log.
479        /// </summary>
480        internal static readonly string DebugLogFileName = "Debug.log";
481
482        /// <summary>
483        /// The file name of the screenshot.
484        /// </summary>
485        internal static readonly string ScreenshotFileName = "Screenshot.png";
486    }
487
488    /// <summary>
489    /// Represents one BlackBox crash report.
490    /// </summary>
491    public class BlackBoxReport
492    {
493        /// <summary>
494        /// Constructor.
495        /// </summary>
496        /// <param name="path">Path to the folder containing the memory dump, screenshot and
497        /// debug log.</param>
498        internal BlackBoxReport(string path)
499        {
500            Path = path;
501
502            string stackTracePath = System.IO.Path.Combine(Path, StackTraceFileName);
503            if (!System.IO.File.Exists(stackTracePath))
504            {
505                Delete();
506                throw new InvalidDataException("The BlackBox report is corrupt.");
507            }
508
509            string[] stackTrace = null;
510            using (StreamReader reader = new StreamReader(stackTracePath))
511                stackTrace = reader.ReadToEnd().Split(new char[] { '\n' });
512
513            //Parse the lines in the file.
514            StackTraceCache = new List<BlackBoxExceptionEntry>();
515            List<string> currentException = new List<string>();
516            string exceptionType = null;
517            foreach (string str in stackTrace)
518            {
519                if (str.StartsWith("Exception "))
520                {
521                    //Add the current exception to the list of exceptions.
522                    if (currentException.Count != 0)
523                    {
524                        StackTraceCache.Add(new BlackBoxExceptionEntry(exceptionType,
525                            new List<string>(currentException)));
526                        currentException.Clear();
527                    }
528
529                    //Set the exception type for the next exception.
530                    exceptionType = str.Substring(str.IndexOf(':') + 1).Trim();
531                }
532                else if (!string.IsNullOrEmpty(str.Trim()))
533                {
534                    currentException.Add(str.Trim());
535                }
536            }
537
538            if (currentException.Count != 0)
539                StackTraceCache.Add(new BlackBoxExceptionEntry(exceptionType, currentException));
540        }
541
542        /// <summary>
543        /// Deletes the report and its contents.
544        /// </summary>
545        public void Delete()
546        {
547            Directory.Delete(Path, true);
548        }
549
550        /// <summary>
551        /// The name of the report.
552        /// </summary>
553        public string Name
554        {
555            get
556            {
557                return System.IO.Path.GetFileName(Path);
558            }
559        }
560
561        /// <summary>
562        /// The timestamp of the report.
563        /// </summary>
564        public DateTime Timestamp
565        {
566            get
567            {
568                return DateTime.ParseExact(Name, BlackBox.CrashReportName,
569                    CultureInfo.InvariantCulture).ToLocalTime();
570            }
571        }
572
573        /// <summary>
574        /// The path to the folder containing the report.
575        /// </summary>
576        public string Path
577        {
578            get;
579            private set;
580        }
581
582        /// <summary>
583        /// The files which comprise the error report.
584        /// </summary>
585        public ReadOnlyCollection<FileInfo> Files
586        {
587            get
588            {
589                List<FileInfo> result = new List<FileInfo>();
590                DirectoryInfo directory = new DirectoryInfo(Path);
591                foreach (FileInfo file in directory.GetFiles())
592                    if (!InternalFiles.Contains(file.Name))
593                        result.Add(file);
594
595                return result.AsReadOnly();
596            }
597        }
598
599        /// <summary>
600        /// Gets a read-only stream which reads the Debug log.
601        /// </summary>
602        public Stream DebugLog
603        {
604            get
605            {
606                return new FileStream(System.IO.Path.Combine(Path, BlackBox.DebugLogFileName),
607                    FileMode.Open, FileAccess.Read);
608            }
609        }
610
611        /// <summary>
612        /// Gets the stack trace for this crash report.
613        /// </summary>
614        public ReadOnlyCollection<BlackBoxExceptionEntry> StackTrace
615        {
616            get
617            {
618                return StackTraceCache.AsReadOnly();
619            }
620        }
621
622        /// <summary>
623        /// Gets or sets whether the given report has been uploaded to the server.
624        /// </summary>
625        public bool Submitted
626        {
627            get
628            {
629                byte[] buffer = new byte[1];
630                using (FileStream stream = new FileStream(System.IO.Path.Combine(Path, StatusFileName),
631                    FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read))
632                {
633                    stream.Read(buffer, 0, buffer.Length);
634                }
635
636                return buffer[0] == 1;
637            }
638
639            set
640            {
641                byte[] buffer = { Convert.ToByte(value) };
642                using (FileStream stream = new FileStream(System.IO.Path.Combine(Path, StatusFileName),
643                    FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read))
644                {
645                    stream.Write(buffer, 0, buffer.Length);
646                }
647            }
648        }
649
650        public override string ToString()
651        {
652            return Name;
653        }
654
655        /// <summary>
656        /// The backing variable for the <see cref="StackTrace"/> field.
657        /// </summary>
658        private List<BlackBoxExceptionEntry> StackTraceCache;
659
660        /// <summary>
661        /// The file name for the status file.
662        /// </summary>
663        private static readonly string StatusFileName = "Status.txt";
664
665        /// <summary>
666        /// The file name of the stack trace.
667        /// </summary>
668        internal static readonly string StackTraceFileName = "Stack Trace.log";
669
670        /// <summary>
671        /// The list of files internal to the report.
672        /// </summary>
673        private static readonly List<string> InternalFiles = new List<string>(
674            new string[] {
675                 StackTraceFileName,
676                 StatusFileName
677            }
678        );
679    }
680
681    /// <summary>
682    /// Represents one exception which can be chained <see cref="InnerException"/>
683    /// to represent the exception handled by BlackBox
684    /// </summary>
685    public class BlackBoxExceptionEntry
686    {
687        /// <summary>
688        /// Constructor.
689        /// </summary>
690        /// <param name="exceptionType">The type of the exception.</param>
691        /// <param name="stackTrace">The stack trace for this exception.</param>
692        internal BlackBoxExceptionEntry(string exceptionType, List<string> stackTrace)
693        {
694            ExceptionType = exceptionType;
695            StackTraceCache = stackTrace;
696        }
697
698        /// <summary>
699        /// The type of the exception.
700        /// </summary>
701        public string ExceptionType
702        {
703            get;
704            private set;
705        }
706
707        /// <summary>
708        /// The stack trace for this exception.
709        /// </summary>
710        public ReadOnlyCollection<string> StackTrace
711        {
712            get
713            {
714                return StackTraceCache.AsReadOnly();
715            }
716        }
717
718        /// <summary>
719        /// The backing variable for the <see cref="StackTrace"/> property.
720        /// </summary>
721        private List<string> StackTraceCache;
722    }
723}
Note: See TracBrowser for help on using the repository browser.