source: trunk/eraser6/Eraser.Util/BlackBox.cs @ 1611

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

Merged the BlackBox? branch to trunk. Completes #317: Merge the BlackBox? branch

  • 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.