270

I have code that is logging Exception.Message. However, I read an article which states that it's better to use Exception.ToString(). With the latter, you retain more crucial information about the error.

Is this true, and is it safe to go ahead and replace all code logging Exception.Message?

I'm also using an XML based layout for log4net. Is it possible that Exception.ToString() may contain invalid XML characters, which may cause issues?

2
  • 1
    You should also look at ELMAH (code.google.com/p/elmah) - A very easy-to-use framework for Error Logging for ASP.NET. Commented Jan 20, 2015 at 20:26
  • if you're logging from an asynchronous thread, elmah won't work. You'll throw a null context error. Commented May 4, 2023 at 14:10

8 Answers 8

338

Exception.Message contains only the message (doh) associated with the exception. Example:

Object reference not set to an instance of an object

The Exception.ToString() method will give a much more verbose output, containing the exception type, the message (from before), a stack trace, and all of these things again for nested/inner exceptions. More precisely, the method returns the following:

ToString returns a representation of the current exception that is intended to be understood by humans. Where the exception contains culture-sensitive data, the string representation returned by ToString is required to take into account the current system culture. Although there are no exact requirements for the format of the returned string, it should attempt to reflect the value of the object as perceived by the user.

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 a null reference (Nothing in Visual Basic), 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 a null reference (Nothing in Visual Basic).

Sign up to request clarification or add additional context in comments.

7 Comments

+1 Its very painful to see ONLY that "Object reference not set to an instance of an object" in the logs. You feel really helpless. :-)
For the last part, there are Exceptions that don't come with Exception.Message. In function of what you do in the error handling part, you can get to have problems because of the Exception.Message.
It is very painful to see that I wrote code that essentially does the exact same thing that ToString() does.
@KunalGoel If the log comes from prod and you have no indication what the input was, then no, you can't just "debug through by turning on the CLR exception".
Note, it is the "default implementation of ToString"...(emphasis on "default")..it does not mean that everyone has followed that practice with any custom exceptions. #learnedTheHardWay
|
71

In addition to what's already been said, don't use ToString() on the exception object for displaying to the user. Just the Message property should suffice, or a higher level custom message.

In terms of logging purposes, definitely use ToString() on the Exception, not just the Message property, as in most scenarios, you will be left scratching your head where specifically this exception occurred, and what the call stack was. The stacktrace would have told you all that.

1 Comment

If you are using ToString() in logs, ensure not include sensitive info in ToString
43

Converting the WHOLE Exception To a String

Calling Exception.ToString() gives you more information than just using the Exception.Message property. However, even this still leaves out lots of information, including:

  1. The Data collection property found on all exceptions.
  2. Any other custom properties added to the exception.

There are times when you want to capture this extra information. The code below handles the above scenarios. It also writes out the properties of the exceptions in a nice order. It's using C# 7 but should be very easy for you to convert to older versions if necessary. See also this related answer.

public static class ExceptionExtensions { public static string ToDetailedString(this Exception exception) => ToDetailedString(exception, ExceptionOptions.Default); public static string ToDetailedString(this Exception exception, ExceptionOptions options) { if (exception == null) { throw new ArgumentNullException(nameof(exception)); } var stringBuilder = new StringBuilder(); AppendValue(stringBuilder, "Type", exception.GetType().FullName, options); foreach (PropertyInfo property in exception .GetType() .GetProperties() .OrderByDescending(x => string.Equals(x.Name, nameof(exception.Message), StringComparison.Ordinal)) .ThenByDescending(x => string.Equals(x.Name, nameof(exception.Source), StringComparison.Ordinal)) .ThenBy(x => string.Equals(x.Name, nameof(exception.InnerException), StringComparison.Ordinal)) .ThenBy(x => string.Equals(x.Name, nameof(AggregateException.InnerExceptions), StringComparison.Ordinal))) { var value = property.GetValue(exception, null); if (value == null && options.OmitNullProperties) { if (options.OmitNullProperties) { continue; } else { value = string.Empty; } } AppendValue(stringBuilder, property.Name, value, options); } return stringBuilder.ToString().TrimEnd('\r', '\n'); } private static void AppendCollection( StringBuilder stringBuilder, string propertyName, IEnumerable collection, ExceptionOptions options) { stringBuilder.AppendLine($"{options.Indent}{propertyName} ="); var innerOptions = new ExceptionOptions(options, options.CurrentIndentLevel + 1); var i = 0; foreach (var item in collection) { var innerPropertyName = $"[{i}]"; if (item is Exception) { var innerException = (Exception)item; AppendException( stringBuilder, innerPropertyName, innerException, innerOptions); } else { AppendValue( stringBuilder, innerPropertyName, item, innerOptions); } ++i; } } private static void AppendException( StringBuilder stringBuilder, string propertyName, Exception exception, ExceptionOptions options) { var innerExceptionString = ToDetailedString( exception, new ExceptionOptions(options, options.CurrentIndentLevel + 1)); stringBuilder.AppendLine($"{options.Indent}{propertyName} ="); stringBuilder.AppendLine(innerExceptionString); } private static string IndentString(string value, ExceptionOptions options) { return value.Replace(Environment.NewLine, Environment.NewLine + options.Indent); } private static void AppendValue( StringBuilder stringBuilder, string propertyName, object value, ExceptionOptions options) { if (value is DictionaryEntry) { DictionaryEntry dictionaryEntry = (DictionaryEntry)value; stringBuilder.AppendLine($"{options.Indent}{propertyName} = {dictionaryEntry.Key} : {dictionaryEntry.Value}"); } else if (value is Exception) { var innerException = (Exception)value; AppendException( stringBuilder, propertyName, innerException, options); } else if (value is IEnumerable && !(value is string)) { var collection = (IEnumerable)value; if (collection.GetEnumerator().MoveNext()) { AppendCollection( stringBuilder, propertyName, collection, options); } } else { stringBuilder.AppendLine($"{options.Indent}{propertyName} = {value}"); } } } public struct ExceptionOptions { public static readonly ExceptionOptions Default = new ExceptionOptions() { CurrentIndentLevel = 0, IndentSpaces = 4, OmitNullProperties = true }; internal ExceptionOptions(ExceptionOptions options, int currentIndent) { this.CurrentIndentLevel = currentIndent; this.IndentSpaces = options.IndentSpaces; this.OmitNullProperties = options.OmitNullProperties; } internal string Indent { get { return new string(' ', this.IndentSpaces * this.CurrentIndentLevel); } } internal int CurrentIndentLevel { get; set; } public int IndentSpaces { get; set; } public bool OmitNullProperties { get; set; } } 

Top Tip - Logging Exceptions

Most people will be using this code for logging. Consider using Serilog with my Serilog.Exceptions NuGet package which also logs all properties of an exception but does it faster and without reflection in the majority of cases. Serilog is a very advanced logging framework which is all the rage at the time of writing.

Top Tip - Human Readable Stack Traces

You can use the Ben.Demystifier NuGet package to get human readable stack traces for your exceptions or the serilog-enrichers-demystify NuGet package if you are using Serilog.

Comments

12

I'd say @Wim is right. You should use ToString() for logfiles - assuming a technical audience - and Message, if at all, to display to the user. One could argue that even that is not suitable for a user, for every exception type and occurance out there (think of ArgumentExceptions, etc.).

Also, in addition to the StackTrace, ToString() will include information you will not get otherwise. For example the output of fusion, if enabled to include log messages in exception "messages".

Some exception types even include additional information (for example from custom properties) in ToString(), but not in the Message.

Comments

8

Depends on the information you need. For debugging the stack trace & inner exception are useful:

 string message = "Exception type " + ex.GetType() + Environment.NewLine + "Exception message: " + ex.Message + Environment.NewLine + "Stack trace: " + ex.StackTrace + Environment.NewLine; if (ex.InnerException != null) { message += "---BEGIN InnerException--- " + Environment.NewLine + "Exception type " + ex.InnerException.GetType() + Environment.NewLine + "Exception message: " + ex.InnerException.Message + Environment.NewLine + "Stack trace: " + ex.InnerException.StackTrace + Environment.NewLine + "---END Inner Exception"; } 

7 Comments

This is more or less what Exception.ToString() will give you, right?
@Matt: Constructing an instance of StringBuilder in this scenario may well be more expensive than two new string allocations, it's highly debatable it would be more efficient here. It's not like we're dealing with iterations. Horses for courses.
Problem here is, that you'll only get the "InnerException" of the outermost exception. IOW, if InnerException itself has an InnerException set, you'll not dump it (assuming that you want to in the first place). I'd really stick with ToString().
Just use ex.ToString. It gets you all the details.
@Christian: The compiler is sane with multiple +s. See for example "The + operator is easy to use and makes for intuitive code. Even if you use several + operators in one statement, the string content is copied only once." from msdn.microsoft.com/en-us/library/ms228504.aspx
|
3

In terms of the XML format for log4net, you need not worry about ex.ToString() for the logs. Simply pass the exception object itself and log4net does the rest do give you all of the details in its pre-configured XML format. The only thing I run into on occasion is new line formatting, but that's when I'm reading the files raw. Otherwise parsing the XML works great.

Comments

2

Ideally, you're best to serialize the entire exception object versus .ToString(). This will encapsulate the entire exception object (all inner exceptions, message, stack trace, data, keys, etc.).

You can then be sure nothing is left out. Plus, you also have the object in a universal format that you can utilize in any application.

 public static void LogError(Exception exception, int userId) { LogToDB(Newtonsoft.Json.JsonConvert.SerializeObject(exception), userId); } 

Comments

0

Well, I'd say it depends what you want to see in the logs, doesn't it? If you're happy with what ex.Message provides, use that. Otherwise, use ex.toString() or even log the stack trace.

1 Comment

ex.ToString includes the stack trace

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.