1

I regularly have a requirement:

How do I log an exception with as much information I need but as little text as possible?


The exception.Message has too little information, of course.
But exception.ToString() is a bit too much and unreadable:

System.NotImplementedException: Die Methode oder der Vorgang ist nicht implementiert.
   bei ConsoleApplication1.Program.Second() in C:\..\Program.cs:Line 48.
   bei ConsoleApplication1.Program.First() in C:\..\Program.cs:Line 43.
   bei ConsoleApplication1.Program.Main(String[] args) in C:\..\Program.cs:Line 21.

My idea is a pretty single line with the most relevant information, something like this:

System.NotImplementedException: Die Methode oder der Vorgang ist nicht implementiert {ConsoleApplication1.Program.Main(String[] args)[21] -> ConsoleApplication1.Program.First()[43] -> ConsoleApplication1.Program.Second()[48]}

What would be the best way to get this?
I realized that I could get the deepest exception causing method name from the TargetSite Property. But how to get the methods between? And the most important thing: How to get the line numbers?

data
  • 2,563
  • 1
  • 21
  • 25
Max
  • 108
  • 1
  • 14

3 Answers3

1

You could combine information from exception.Message with Environment.StackTrace (for example, split into lines and use only couple of the last ones) or use Diagnostics.StackTrace class to get only the inforomation you need. Good luck!

data
  • 2,563
  • 1
  • 21
  • 25
1

As far as exceptions are concerned TMI is a YMMV thing. I'd go with the Exception.Message + StackTrace. (pretty much what ToString() seems to print)

If you really think you only need the last couple of functions/only functions from user code you can split the stack trace and only get the lines that match your class/assembly name.

IMO more info is always good, and no space saving/time spent skipping a few lines during reading is worth the developer frustration/time spent of

Wow, what's going on in this piece of code, I wish I had one more line from the stack trace.

To get the line numbers you need the pdb file info: C# Exceptions Not Giving Line Numbers

Community
  • 1
  • 1
sdf
  • 189
  • 3
  • @Max the links in my answer will give you access to StackTrace in a manner that will enable you to print the error in a way you want :) – data Jun 12 '14 at 13:35
0

Based on the links of @data I make my own method, which meets my demands.

public static class ExceptionExtensions
{
    /// <summary>
    /// Return a compact exception format for logging
    /// </summary>
    /// <remarks>
    /// In a single line the exception will contains the most relevant information like
    /// Excepition type and message, the call stack methods and lines
    /// Note: call stack is only available in Debug, Release will not contain these information
    /// </remarks>
    /// <param name="ex"></param>
    /// <returns></returns>
    /// <example>System.NotImplementedException: NotImplementedException (OwnLib.WoohooClass.ThisIsTheBestMethodEver()[13] => ConsoleApplication1.OwnClass.CallMeAClass()[13] => ConsoleApplication1.Program.Second(System.TimeSpan)[68] => ConsoleApplication1.Program.First()[57] => ConsoleApplication1.Program.Main(System.String[])[25])</example>
    public static string ToCompactString(this Exception ex)
    {
        var stack = new List<string>();
        var stackTrace = new StackTrace(ex, true);
        for (int i = 0; i < stackTrace.FrameCount; i++)
        {
            StackFrame sf = stackTrace.GetFrame(i);
            var method = sf.GetMethod();
            stack.Add(string.Format("{0}[{1}]", 
                method.ToString().SubstringFromMatch(method.Name, true).Replace(method.Name, string.Format("{0}.{1}", method.ReflectedType.FullName, method.Name)), 
                sf.GetFileLineNumber()));
        }

        return string.Format("{0}: {1} ({2})", ex.GetType(), ex.Message, stack.GetDelimiterSeperatedList(" => "));
    }
}

//Helper methods
public static class StringExtensions
{
    /// <summary>
    /// Substring for a searched string. takes the whole string from the match and cuts before.
    /// </summary>
    /// <param name="searchString">string to search within</param>
    /// <param name="searchedString">string to search for</param>
    /// <param name="includeMatch"><c>true</c> = with searchString, <c>false</c> = without</param>
    /// <param name="count">which time to found (0 = first)</param>
    /// <returns>a substring for a match, the whole string when no match</returns>
    public static string SubstringFromMatch(this string searchString, string searchedString, bool includeMatch, int count = 0)
    {
        if (searchString.Contains(searchedString))
        {
            var index = searchString.IndexOf(searchedString, count, StringComparison.Ordinal) + (includeMatch ? 0 : searchedString.Length);
            return searchString.Substring(index, searchString.Length - index);
        }
        return searchString;
    }

    /// <summary>
    /// Returns a list auf values, concatted by a delimiter like ",", e.g. "1,2,abc,cde"
    /// </summary>
    /// <remarks>a maximum of 999 items could be seperated</remarks>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <param name="delimiter">a delimiter used to seperate the items</param>
    /// <example>"," => "item1,item2,item3"; " - " "item1 - item2 - item3"</example>
    public static string GetDelimiterSeperatedList<T>(this ICollection<T> list, string delimiter)
    {
        if (list != null && list.Count > 0)
        {
            var seperatedList = String.Empty;
            var listCounter = 0;
            foreach (var id in list)
            {
                seperatedList += String.Format("{0}{1}", id, delimiter);
                if (listCounter++ >= 999) break;
            }
            return seperatedList.Remove(seperatedList.LastIndexOf(delimiter, StringComparison.Ordinal));
        }
        return null;
    }
}

An example output looks like this:

System.NotImplementedException: You have forgotton to do something (AnotherLib.WoohooClass.ThisIsTheMostUselessMethodEver()[13] => ConsoleApplication1.AnotherClass.CallMeAClass()[13] => ConsoleApplication1.Program.Third(System.TimeSpan)[68] => ConsoleApplication1.Program.Second()[63] => ConsoleApplication1.Program.First()[57] => ConsoleApplication1.Program.Main(System.String[])[25])

By the way: I also validated the time measurement of my solution above and the the normal exception.ToString() method. It was interesting that there is no significant difference.

Thanks at all for your help.

Max
  • 108
  • 1
  • 14