200

What is the proper way to show my full InnerException.

I found that some of my InnerExceptions has another InnerException and that go's on pretty deep.

Will InnerException.ToString() do the job for me or do I need to loop through the InnerExceptions and build up a String with StringBuilder?

Pang
  • 9,564
  • 146
  • 81
  • 122
Willem
  • 9,166
  • 17
  • 68
  • 92
  • Why do you need to show the inner exception ?? – Akram Shahda May 08 '11 at 17:22
  • 30
    @Akram because most of the time it is the inner exception that is interesting. One example is the XmlSerializer which just throws an InvalidOperationException whenever something goes wrong. What went wrong is in the inner exception. – adrianm May 08 '11 at 17:28
  • 4
    @AkramShahda Well, maybe you want to user this method in your logging? – cederlof Oct 04 '12 at 09:15
  • Related post - [Exception.Message vs Exception.ToString()](https://stackoverflow.com/q/2176707/465053) – RBT Jun 08 '18 at 09:25

11 Answers11

292

You can simply print exception.ToString() -- that will also include the full text for all the nested InnerExceptions.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • 30
    This includes a load of other crap too, not just the exception message and inner exception messages – ᴍᴀᴛᴛ ʙᴀᴋᴇʀ Oct 25 '16 at 14:26
  • 2
    just for succinctness you don't actually need the .ToString(), just using exception will do the same. – Alex Stephens Nov 24 '17 at 11:15
  • 4
    @AlexStephens you are right, but only if you have an implicit casting "to string" for some reason, like preceding string: "bla" + exception – oo_dev Jun 25 '18 at 11:17
  • 2
    FYI: it will not call a custom `ToString` methods for inner exceptions as detailed in [Why doesn't System.Exception.ToString call virtual ToString for inner exceptions?](https://stackoverflow.com/q/20601776/945456). – Jeff B Feb 03 '20 at 20:27
  • Works for most cases, but if you're using Entity Framework, this will not include ValidationErrors in DbEntityValidationException. See my response below. – Sylvain Rodrigue Aug 02 '20 at 10:18
66

I usually do like this to remove most of the noise:

void LogException(Exception error) {
    Exception realerror = error;
    while (realerror.InnerException != null)
        realerror = realerror.InnerException;

    Console.WriteLine(realerror.ToString())
}    

Edit: I forgot about this answer and is surprised no one pointed out that you can just do

void LogException(Exception error) {
    Console.WriteLine(error.GetBaseException().ToString())
}    
adrianm
  • 14,468
  • 5
  • 55
  • 102
  • 1
    This method hides everything except for the deepest inner exception. If that was something mundane like a "Divide by zero" error, it would be unclear where it occurred and what lead to it. Obviously, a full stack trace is usually a messy overkill, but only reading the inner exception is the other extreme. user3016982's answer is far better. You get every exception message in the stack without the nasty trace. – JamesHoux Aug 03 '18 at 14:47
  • 1
    @JamesHoux Which one is "user3016982" answer? Can't find him here. – maracuja-juice Sep 04 '18 at 11:10
  • The user3016982 is ThomazMoura, see: https://stackoverflow.com/users/3016982/thomazmoura – Coden Jan 24 '19 at 09:17
  • @JamesHoux, the inner exception has a full stacktrace showing where the error occured and what led to it. Don't understand what extra information you get from the removed stack traces. Exception messages are another thing and it might be useful to collect all of them. – adrianm Jan 31 '19 at 08:47
  • That "noise" you suggest to remove is generally where the interesting contextual information is. Assuming of course the application/library has been written to add it. Such as telling you which high-level object was affected by the low-level "format exception", etc. – Oskar Berggren May 16 '19 at 08:44
55

Just use exception.ToString()

https://learn.microsoft.com/en-us/dotnet/api/system.exception.tostring#remarks

The default implementation of ToString obtains the name of the class that threw the current exception, the message, the result of calling ToString on the inner exception, and the result of calling Environment.StackTrace. If any of these members is null, its value is not included in the returned string.

If there is no error message or if it is an empty string (""), then no error message is returned. The name of the inner exception and the stack trace are returned only if they are not null.

exception.ToString() will also call .ToString() on that exception's inner exception, and so on...

Pang
  • 9,564
  • 146
  • 81
  • 122
Rob P.
  • 14,921
  • 14
  • 73
  • 109
47

@Jon's answer is the best solution when you want full detail (all the messages and the stack trace) and the recommended one.

However, there might be cases when you just want the inner messages, and for these cases I use the following extension method:

public static class ExceptionExtensions
{
    public static string GetFullMessage(this Exception ex)
    {
        return ex.InnerException == null 
             ? ex.Message 
             : ex.Message + " --> " + ex.InnerException.GetFullMessage();
    }
}

I often use this method when I have different listeners for tracing and logging and want to have different views on them. That way I can have one listener which sends the whole error with stack trace by email to the dev team for debugging using the .ToString() method and one that writes a log on file with the history of all the errors that happened each day without the stack trace with the .GetFullMessage() method.

Michael
  • 1,453
  • 3
  • 20
  • 28
ThomazMoura
  • 571
  • 4
  • 3
15

To pretty print just the Messages part of deep exceptions, you could do something like this:

public static string ToFormattedString(this Exception exception)
{
    IEnumerable<string> messages = exception
        .GetAllExceptions()
        .Where(e => !String.IsNullOrWhiteSpace(e.Message))
        .Select(e => e.Message.Trim());
    string flattened = String.Join(Environment.NewLine, messages); // <-- the separator here
    return flattened;
}

public static IEnumerable<Exception> GetAllExceptions(this Exception exception)
{
    yield return exception;

    if (exception is AggregateException aggrEx)
    {
        foreach (Exception innerEx in aggrEx.InnerExceptions.SelectMany(e => e.GetAllExceptions()))
        {
            yield return innerEx;
        }
    }
    else if (exception.InnerException != null)
    {
        foreach (Exception innerEx in exception.InnerException.GetAllExceptions())
        {
            yield return innerEx;
        }
    }
}

This recursively goes through all inner exceptions (including the case of AggregateExceptions) to print all Message property contained in them, delimited by line break.

E.g.

var outerAggrEx = new AggregateException(
    "Outer aggr ex occurred.",
    new AggregateException("Inner aggr ex.", new FormatException("Number isn't in correct format.")),
    new IOException("Unauthorized file access.", new SecurityException("Not administrator.")));
Console.WriteLine(outerAggrEx.ToFormattedString());

Outer aggr ex occurred.
Inner aggr ex.
Number isn't in correct format.
Unauthorized file access.
Not administrator.


You will need to listen to other Exception properties for more details. For e.g. Data will have some information. You could do:

foreach (DictionaryEntry kvp in exception.Data)

To get all derived properties (not on base Exception class), you could do:

exception
    .GetType()
    .GetProperties()
    .Where(p => p.CanRead)
    .Where(p => p.GetMethod.GetBaseDefinition().DeclaringType != typeof(Exception));
nawfal
  • 70,104
  • 56
  • 326
  • 368
  • +1, This is almost exactly the same thing as I do. Do consider to look for a property implementing `IEnumerable` instead of hard coding `AggregrateException` to handle other similar types. Also exclude `p.IsSpecialName` and `pi.GetIndexParameters().Length != 0` to avoid problems. Including the exception type name in the output is also a good idea – adrianm Sep 05 '18 at 08:04
  • @adrianm good point about property info checks. Regarding checking for collection of exceptions, it's all about where you want to draw the line. Sure that can be done too.. – nawfal Sep 06 '18 at 10:01
6

I do:

namespace System {
  public static class ExtensionMethods {
    public static string FullMessage(this Exception ex) {
      if (ex is AggregateException aex) return aex.InnerExceptions.Aggregate("[ ", (total, next) => $"{total}[{next.FullMessage()}] ") + "]";
      var msg = ex.Message.Replace(", see inner exception.", "").Trim();
      var innerMsg = ex.InnerException?.FullMessage();
      if (innerMsg is object && innerMsg!=msg) msg = $"{msg} [ {innerMsg} ]";
      return msg;
    }
  }
}

This "pretty prints" all inner exceptions and also handles AggregateExceptions and cases where InnerException.Message is the same as Message

kofifus
  • 17,260
  • 17
  • 99
  • 173
5

If you're using Entity Framework, exception.ToString() will not give you the details of DbEntityValidationException exceptions. You might want to use the same method to handle all your exceptions, like:

catch (Exception ex)
{
   Log.Error(GetExceptionDetails(ex));
}

Where GetExceptionDetails contains something like this:

public static string GetExceptionDetails(Exception ex)
{
    var stringBuilder = new StringBuilder();

    while (ex != null)
    {
        switch (ex)
        {
            case DbEntityValidationException dbEx:
                var errorMessages = dbEx.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage);
                var fullErrorMessage = string.Join("; ", errorMessages);
                var message = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);

                stringBuilder.Insert(0, dbEx.StackTrace);
                stringBuilder.Insert(0, message);
                break;

            default:
                stringBuilder.Insert(0, ex.StackTrace);
                stringBuilder.Insert(0, ex.Message);
                break;
        }

        ex = ex.InnerException;
    }

    return stringBuilder.ToString();
}
Sylvain Rodrigue
  • 4,751
  • 5
  • 53
  • 67
3

If you want information about all exceptions then use exception.ToString(). It will collect data from all inner exceptions.

If you want only the original exception then use exception.GetBaseException().ToString(). This will get you the first exception, e.g. the deepest inner exception or the current exception if there is no inner exception.

Example:

try {
    Exception ex1 = new Exception( "Original" );
    Exception ex2 = new Exception( "Second", ex1 );
    Exception ex3 = new Exception( "Third", ex2 );
    throw ex3;
} catch( Exception ex ) {
    // ex => ex3
    Exception baseEx = ex.GetBaseException(); // => ex1
}
dkostas
  • 57
  • 3
2

buildup on nawfal 's answer.

when using his answer there was a missing variable aggrEx, I added it.

file ExceptionExtenstions.class:

// example usage:
// try{ ... } catch(Exception e) { MessageBox.Show(e.ToFormattedString()); }

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace YourNamespace
{
    public static class ExceptionExtensions
    {

        public static IEnumerable<Exception> GetAllExceptions(this Exception exception)
        {
            yield return exception;

            if (exception is AggregateException )
            {
                var aggrEx = exception as AggregateException;
                foreach (Exception innerEx in aggrEx.InnerExceptions.SelectMany(e => e.GetAllExceptions()))
                {
                    yield return innerEx;
                }
            }
            else if (exception.InnerException != null)
            {
                foreach (Exception innerEx in exception.InnerException.GetAllExceptions())
                {
                    yield return innerEx;
                }
            }
        }


        public static string ToFormattedString(this Exception exception)
        {
            IEnumerable<string> messages = exception
                .GetAllExceptions()
                .Where(e => !String.IsNullOrWhiteSpace(e.Message))
                .Select(exceptionPart => exceptionPart.Message.Trim() + "\r\n" + (exceptionPart.StackTrace!=null? exceptionPart.StackTrace.Trim():"") );
            string flattened = String.Join("\r\n\r\n", messages); // <-- the separator here
            return flattened;
        }
    }
}
Shimon Doodkin
  • 4,310
  • 34
  • 37
0

This one is better I think

public static string GetCompleteMessage(this Exception error)
    {
        System.Text.StringBuilder builder = new StringBuilder();
        Exception realerror = error;
        builder.AppendLine(error.Message);
        while (realerror.InnerException != null)
        {
            builder.AppendLine(realerror.InnerException.Message);
            realerror = realerror.InnerException;
        }
        return builder.ToString();
    }
Ali
  • 1,015
  • 14
  • 40
0

This code generates a formatted HTML representation of the exception:

const string _HTML_TAB = "&nbsp;&nbsp;&nbsp;";

public static string ToHtmlString(this Exception ex, int level = 0)
{
    string message = GetText("Message", ex.Message, level);

    if (ex.InnerException != null && level < 30)
    {
        message += ToHtmlString(ex.InnerException, level + 1);
    }
    else
    {
        message += GetText("StackTrace", ex.StackTrace, level); ;
        message += GetText("Source", ex.Source, level); ;
        message += GetText("TargetSite", ex.TargetSite.ToString(), level);
    }

    return message;
}

private static string GetText(string headline, string text, int level)
{
    var indentText = string.Join(_HTML_TAB, new string[level + 1]);
    var newLine = $"<br />{indentText}{_HTML_TAB}";

    return $"{indentText}<b>{headline}</b>{newLine}"
            + $"{text.Replace(Environment.NewLine, newLine)}<br /><br />";
}
RoJaIt
  • 451
  • 3
  • 10