Friday, December 22, 2006

Logging Method Name in C#


logMessage("functionName", "something bad happened");

Yes the above works if IF *if* you remember to update "functionName". However, remembering to update the parameter after copying from another method is often not done. It is also a pain and not necessary. A little reflection easily solves this problem:

// start one up so that we don't get the current
// method but the one that called this one
StackFrame sf = new StackFrame(1, true);
System.Reflection.MethodBase mb = sf.GetMethod();
string methodName = mb != null ? mb.Name : "";
// fileName can be null, if unable to determine
string fileName = sf.GetFileName();
// we only want the filename not the complete path
if (fileName != null) fileName = fileName.Substring(fileName.LastIndexOf('\\') + 1);
int lineNumber = sf.GetFileLineNumber();

However the above does not work if you are using RichException or other proxies. So something a bit more complicated is needed:

private StackFrame GetCallingStackFrame()
{
// Determine the method that called the one that is calling this one.
// This is not just two up the Stack because of RichException support.
StackFrame sf = null;
// Start at 2. 1 for this one and another for the one above that.
StackTrace st = new StackTrace(2, true);
Type thisType = GetType();
foreach (StackFrame sfi in st.GetFrames())
{
// Find a calling method that is not part of this log class but is part
// of the same Namespace.
Type callType = sfi.GetMethod().DeclaringType;
if (callType != thisType &&
callType.Namespace == thisType.Namespace &&
!callType.IsInterface)
{
sf = sfi;
break;
}
}
return sf;
}

This can then be used like so:

StackFrame sf = GetCallingStackFrame();
if (sf != null) // if found add info to log message
{
System.Reflection.MethodBase mb = sf.GetMethod();
string methodName = mb != null ? mb.Name : "";
string fileName = sf.GetFileName();
if (fileName != null) fileName = fileName.Substring(fileName.LastIndexOf('\\') + 1);
int lineNumber = sf.GetFileLineNumber();

if (fileName != null)
{
strMsg = fileName + "(" + lineNumber + ") - " + methodName + " - " + strMsg;
}
else
{
strMsg = "unknown - " + methodName + " - " + strMsg;
}
}

6 comments:

Anonymous said...

You should not search the string for \ to find the filename. System.IO.Path.GetFileName() is easier to read, portable, and handles corner cases you may not have considered. The other Path functions are also useful, and sometimes File and FileInfo are what you want.

Heifner said...

Thanks Justin. I changed to the following:
if (fileName != null) fileName = System.IO.Path.GetFileName(fileName);

Unknown said...

What about using System.Reflection.MethodBase.GetCurrentMethod() instead of all that complexity? ;)

Heifner said...

Its been awhile since I looked at this. However, what is desired is not the current method but a method up the call stack.

Anonymous said...

Will these methods that make use of reflection and the StackFrame still work in an asp .net application that is deployed without debugging information (i.e. release mode)?

Anonymous said...

If you're logging something bad then I will make the assumption that it's wrapped in a try catch.
You could easily the exception StackTrace and have all of this information:

try{
// bad code
}
catch (exception ex){
logbadstuff(ex.StackTrace);
}