What is the proper way to display the full InnerException?
Asked Answered
S

11

201

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?

Shuster answered 8/5, 2011 at 17:16 Comment(4)
Why do you need to show the inner exception ??Dividivi
@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.Quelpart
@AkramShahda Well, maybe you want to user this method in your logging?Huggins
Related post - Exception.Message vs Exception.ToString()Foliate
C
299

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

Celt answered 8/5, 2011 at 17:18 Comment(6)
This includes a load of other crap too, not just the exception message and inner exception messagesGoulden
just for succinctness you don't actually need the .ToString(), just using exception will do the same.Goosegog
@AlexStephens you are right, but only if you have an implicit casting "to string" for some reason, like preceding string: "bla" + exceptionPeril
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?.Sinking
Works for most cases, but if you're using Entity Framework, this will not include ValidationErrors in DbEntityValidationException. See my response below.Lyssa
no. It includes the full stack trace for inner exceptions. The only clue you have as to what happened on the inner exceptions is the method and class names.Pellmell
Q
68

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())
}    
Quelpart answered 8/5, 2011 at 17:32 Comment(5)
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.Woodworm
@Woodworm Which one is "user3016982" answer? Can't find him here.Rhapsodize
The user3016982 is ThomazMoura, see: stackoverflow.com/users/3016982/thomazmouraDistrain
@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.Quelpart
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.Saransarangi
C
56

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

Conifer answered 8/5, 2011 at 17:25 Comment(1)
Except that often it won't.Pellmell
S
48

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

Spindlelegs answered 29/1, 2016 at 12:18 Comment(2)
FYI If ex is a AggregateException, none of the inner exceptions will be included in this outputFerbam
This should be a stock .NET method. Everyone should be using this.Woodworm
I
17

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));
Insect answered 27/8, 2018 at 15:39 Comment(2)
+1, This is almost exactly the same thing as I do. Do consider to look for a property implementing IEnumerable<Exception> 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 ideaQuelpart
@Quelpart 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..Insect
E
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

Effete answered 30/9, 2019 at 1:17 Comment(0)
L
6

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();
}
Lyssa answered 2/8, 2020 at 10:14 Comment(0)
L
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
}
Legion answered 16/10, 2018 at 8:22 Comment(0)
S
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;
        }
    }
}
Spikenard answered 15/1, 2019 at 13:18 Comment(2)
I had an exception because : e.StackTrace == nullStopwatch
I have updated .Select(e => e.Message.Trim() + "\r\n" + (e.StackTrace!=null?StackTrace.Trim():"") ); maybe this helpsSpikenard
M
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();
    }
Mikiso answered 13/8, 2021 at 1:5 Comment(0)
A
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 />";
}
Approve answered 24/11, 2022 at 13:25 Comment(1)
See https://mcmap.net/q/127248/-what-is-the-proper-way-to-display-the-full-innerexception on how to use this code...Approve

© 2022 - 2024 — McMap. All rights reserved.