I am creating a custom exception that derives from ApplicationException to
automatically capture additional information that is not included in the base
Exception class. One of the pieces of information I am wanting to capture is
a more complete call stack than the one provided by the StackTrace property
in the base Exception class. By "more complete", I mean two things:
1) Include the entire call stack when the exception was generated. The base
Exception class only captures the call stack from where it is thrown, to the
method where you catch the exception and call the property. For intsance, if
I had the following:
Method1 calls Method 2
Method 2 calls Method 3
Method 3 calls Method 4
Method 4 raises an exception
The exception is caught in Method 2
In the StackTrace, the lowest call on the stack is Method 4, and the highest
on the stack is Method 2.
What I want to capture is the entire call stack for a more complete picture
of what is occurring. This information will be automatically logged to a
central repository.
2) An easier way to programatically examine the stack. The StackTrace
property on the base Exception class is a simple string that is formatted for
you. I would like to expose a property called "CallStack" that is an array
of what I call StackRecords that have the method, type, line number and file
information in a convenient to use format.
To accomplish this, I've created a function that is called in the
constructor of my custom exception (I'll call it MyException from now on).
Here is the code that gets the information:
private void CreateCallStack()
{
StackTrace st = new StackTrace(true);
Type baseType = typeof(MyException);
int i = 0;
// Skip over any of the stack frames for the MyException class or any
derived class.
// We don't want to include those in the call stack we create.
for (; i < st.FrameCount; i++)
{
// This should always work, right?
Type methodType = st.GetFrame(i).GetMethod().DeclaringType;
if (!(methodType.Equals(baseType) || methodType.IsSubclassOf(baseType)))
break;
}
// TODO: Review: Should the callStack variable be null if we don't have any
StackTrace due to
// code optimization or should it be a zero length array?
if (i < st.FrameCount)
{
callStack = new StackRecord[st.FrameCount - i];
int start = i;
for (; i < st.FrameCount; i++)
{
StackFrame sf = st.GetFrame(i);
MethodBase method = sf.GetMethod();
Type methodType = method.DeclaringType;
callStack[i - start] = new StackRecord(method.Name, methodType.FullName,
sf.GetFileName(), sf.GetFileLineNumber().ToString());
}
}
}
(My advance apologies if this code isn't formatted well, I'm using the
web-based newsgroup reader from Microsoft, so I'm not sure how to control
formatting)
I'm using the System.Diagnostics.StackTrace class to get access to the full
StackTrace of the call. Note that the first for loop is present to skip over
any activation records for the current method call (CreateCallStack) or the
constructors for this base MyException class or any derived exception class.
I only want to capture the call stack up to the method that created my
exception.
Everything seems to work perfectly, except for one thing. For some reason
the line numbers don't seem to be properly listed. I know that using
StackTrace, especially the line numbers and file names, is not always going
to give me the proper stack trace due to code optimizations, etc. However,
when I examine the StackTrace property of this exception when testing, it
contains the proper line numbers. Here is some example output:
StackTrace for my exception (uses base Exception StackTrace):
at MyNamespace.MainClass.Level5() in [removed file path
stuff]\mainclass.cs:line 80
at MyNamespace.MainClass.Level4() in [removed file path
stuff]\mainclass.cs:line 69
at MyNamespace.MainClass.Level3() in [removed file path
stuff]\mainclass.cs:line 50
CallStack for my exception (uses the CallStack property I've created for
MyException)
MyNamespace.MainClass.Level5 in [removed file path stuff]\mainclass.cs at
line 80
MyNamespace.MainClass.Level4 in [removed file path stuff]\mainclass.cs at
line 71
MyNamespace.MainClass.Level3 in [removed file path stuff]\mainclass.cs at
line 50
MyNamespace.MainClass.Level2 in [removed file path stuff]\mainclass.cs at
line 44
MyNamespace.MainClass.Level1 in [removed file path stuff]\mainclass.cs
at line 39
MyNamespace.MainClass.Main in [removed file path stuff]\mainclass.cs at
line 17
My test code has just a few simple routines that call each other to fill up
the call stack. The exception is raised in Level5() and these two messages
are printed to the console in Level3(). Here is the test code:
/// <summary>
/// Summary description for MainClass.
/// </summary>
public class MainClass
{
public static void Main(string[] args)
{
Level1();
// Blah!
} // <--- This is line 17
public static void ReflectProperties(Object o)
{
Type t = o.GetType();
Console.WriteLine("Reflecting properties for type: {0}", t.Name);
PropertyInfo[] properties = t.GetProperties(BindingFlags.Instance |
BindingFlags.Public);
for (int i = 0; i < properties.Length; i++)
Console.WriteLine("\t{0}: {1}", properties[i].Name,
properties[i].GetValue(o, null));
}
public static void Level1()
{
// dude
Level2();
// Messing up lines
} // <--- This is line 39
public static void Level2()
{
Level3();
} // <--- This is line 44
public static void Level3()
{
try
{
Level4(); // <--- This is line 50
}
catch (MyException e)
{
Console.WriteLine("Caught MyException or derived.");
ReflectProperties(e);
Console.WriteLine("Writing out Call Stack:");
for (int i = 0; i < e.CallStack.Length; i++)
{
StackRecord sr = e.CallStack[i];
Console.WriteLine("{0}{1}", new String(' ', i), sr.ToString());
}
}
}
public static void Level4()
{
Level5(); // <--- This is line 69
// blah
} // <--- This is line 71
public static void Level5()
{
/*
TestException e = new TestException("Blah");
e.CreateCallStack();
throw e;
*/
throw new TestException("Blah");
}
}
I marked the line numbers that appear in both stack traces. The StackTrace
for the Exception is "more" correct in that the line numbers it give point to
where they should. It looks as if the stack information I collect has the
line numbers pointing to the end of the method, rather than where the
execution currently is located within the method.
I looked at the code for the base Exception object in Rotor for help, and it
looks like it pretty much uses the StackTrace also to retrieve information.
The StackTrace implementation is mostly internal to CLR and way above my
head, so that was a dead end.
My question is why would the base Exception class be reporting different
(and correct) line numbers from my code? I would like to have my code return
the proper line numbers for accuracy.
Thanks for any help, and for reading through this huge post. :-)
Michael McGuire
David Levine - 02 Sep 2004 10:40 GMT
You can use Environment.StackTrace to get the entire stack trace. This will
include everything, not just the stack between where the exception was
thrown and caught. I think the way you are doing it is also fine, and it
gives you access to all the info you need..
I can't help you about the line numbers - it sounds like an interesting
problem. Have you tested out your code on both debug and release builds?
> I am creating a custom exception that derives from ApplicationException to
> automatically capture additional information that is not included in the base
[quoted text clipped - 217 lines]
>
> Michael McGuire
"Peter Huang" - 02 Sep 2004 11:04 GMT
Hi,
I am researching the issue, and I will update you with new information ASAP.
Best regards,
Peter Huang
Microsoft Online Partner Support

Signature
Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
"Peter Huang" - 06 Sep 2004 08:05 GMT
Hi,
Now we are still researching the issue, we will get back here and update
you with new information ASAP.
Best regards,
Peter Huang
Microsoft Online Partner Support

Signature
Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
Yan-Hong Huang[MSFT] - 09 Sep 2004 08:47 GMT
Hi Mike,
We did exactly reproduce the problem and reported it to the product team.
Currently it was assigned to a design engineer and we are performing
research on it. It may need some more time. Please rest assured that we
will post the update here as soon as possible.
If you have any more concerns on it, please feel free to post here. Thanks
very much for your understanding.
Best regards,
Yanhong Huang
Microsoft Community Support
Get Secure! ?C www.microsoft.com/security
Register to Access MSDN Managed Newsgroups!
-http://support.microsoft.com/default.aspx?scid=/servicedesks/msdn/nospam.as
p&SD=msdn
This posting is provided "AS IS" with no warranties, and confers no rights.
Michael McGuire - 09 Sep 2004 17:21 GMT
Ok, I will keep an eye on this thread for updates.
Thanks for your help.
> Hi Mike,
>
[quoted text clipped - 16 lines]
>
> This posting is provided "AS IS" with no warranties, and confers no rights.
Yan-Hong Huang[MSFT] - 14 Sep 2004 06:38 GMT
Hi Mike,
Our product group has confirmed it is a bug already. However, the problem
seems complicated. I have pushed them but there is no further update yet.
The issue may need some more time. But please rest assured that we are do
our best on it.
Thanks very much for your understanding.
Best regards,
Yanhong Huang
Microsoft Community Support
Get Secure! ?C www.microsoft.com/security
Register to Access MSDN Managed Newsgroups!
-http://support.microsoft.com/default.aspx?scid=/servicedesks/msdn/nospam.as
p&SD=msdn
This posting is provided "AS IS" with no warranties, and confers no rights.
Yan-Hong Huang[MSFT] - 16 Sep 2004 04:15 GMT
Hello Mike,
Our product group confirmed this is a product issue and entered it into our
bug database. However, in order to get a hotfix for it, you may need to go
through PSS support and submit some more information such as business
impace, company info, and etc.
If this issue is critical for you, could you please contact PSS on it? You
can contact Microsoft Product Support directly to discuss additional
support options you may have available, by contacting us at 1-(800)936-5800
or by choosing one of the options listed at
http://support.microsoft.com/default.aspx?scid=sz;en-us;top. Since this is
a product issue, the support request is free for you.
If you feel there is any we can do for you, please feel free to post here.
I am glad to be of assistance.
Best regards,
Yanhong Huang
Microsoft Community Support
Get Secure! ?C www.microsoft.com/security
Register to Access MSDN Managed Newsgroups!
-http://support.microsoft.com/default.aspx?scid=/servicedesks/msdn/nospam.as
p&SD=msdn
This posting is provided "AS IS" with no warranties, and confers no rights.
Michael McGuire - 17 Sep 2004 18:45 GMT
Thank you for your help. I will be contacting PSS for further support.
-Mike
> Hello Mike,
>
[quoted text clipped - 23 lines]
>
> This posting is provided "AS IS" with no warranties, and confers no rights.