For logging purposes
__LINE__
__FILE__
were my friends in C/C++. In Java to get that information I had to throw an exception and catch
With Caller Information (introduced in .NET 4.5) you can create the equivalent of __LINE__
and __FILE__
in C#:
static int __LINE__([System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0)
{
return lineNumber;
}
static string __FILE__([System.Runtime.CompilerServices.CallerFilePath] string fileName = "")
{
return fileName;
}
The only thing to remember is that these are functions and not compiler directives.
So for example:
MessageBox.Show("Line " + __LINE__() + " in " + __FILE__());
If you were to use this in practise then I'd suggest different names. I've used the C/C++ names just to make it clearer what they are returning, and something like CurrentLineNumber()
and CurrentFileName()
might be better names.
The advantage of using Caller Information over any solution that uses the StackTrace
is that the line and file information is available for both debug and release.
The closest thing to those is the fact that you can create a StackTrace
object and find out the name of the method at the top of the stack, so you can get close to the functionality of the __FUNCTION__
macro.
StackTrace stackTrace = new StackTrace();
StackFrame[] stackFrames = stackTrace.GetFrames();
foreach (StackFrame stackFrame in stackFrames)
Console.WriteLine(stackFrame.GetMethod().Name);
To reduce the cost of typing this out by hand, and also the runtime code, you can write a helper method:
[Conditional("Debug")]
public void LogMethodName()
{
Trace.WriteLine("Entering:" + new StackTrace().GetFrame(1).GetMethod().Name);
}
Note how we get frame 1, as frame 0 would be LogMethodName
itself. By marking it as Conditional("Debug") we ensure that the code is removed from release builds, which is one way to avoid the runtime cost where it may not be needed.
Having used the FILE and LINE macros for many years for logging in C/C++ I really wanted a similar solution in C#. This is my solution. I prefer it to @fostandy suggestion of creating many overloads with varying number of parameters. This seems the less intrusive and does not limit the number of formatted parameters. You just have to be willing to accept the addition of the F.L() parameter at start of every Log.Msg() call.
using System;
using System.Runtime.CompilerServices;
namespace LogFileLine
{
public static class F
{
// This method returns the callers filename and line number
public static string L([CallerFilePath] string file = "", [CallerLineNumber] int line = 0)
{
// Remove path leaving only filename
while (file.IndexOf("\\") >= 0)
file = file.Substring(file.IndexOf("\\")+1);
return String.Format("{0} {1}:", file, line);
}
}
public static class Log
{
// Log a formatted message. Filename and line number of location of call
// to Msg method is automatically appended to start of formatted message.
// Must be called with this syntax:
// Log.Msg(F.L(), "Format using {0} {1} etc", ...);
public static void Msg(string fileLine, string format, params object[] parms)
{
string message = String.Format(format, parms);
Console.WriteLine("{0} {1}", fileLine, message);
}
}
class Program
{
static void Main(string[] args)
{
int six = 6;
string nine = "nine";
string dog = "dog";
string cat = "cats";
Log.Msg(F.L(), "The {0} chased the {1} {2}", dog, 5, cat);
Log.Msg(F.L(), "Message with no parameters");
Log.Msg(F.L(), "Message with 8 parameters {0} {1} {2} {3} {4} {5} {6} {7}",
1, 2, 3, "four", 5, 6, 7, 8.0);
Log.Msg(F.L(), "This is a message with params {0} and {1}", six, nine);
}
}
}
Here's the output from this code above
Program.cs 41: The dog chased the 5 cats
Program.cs 43: Message with no parameters
Program.cs 45: Message with 8 parameters 1 2 3 four 5 6 7 8
Program.cs 48: This is a message with params 6 and nine
Here's a way to get the line number: http://askville.amazon.com/SimilarQuestions.do?req=line-numbers-stored-stack-trace-C%2523-application-throws-exception
If you use log4net, you can get the line number and file name in your logs, but:
It is uglier, but you can do something like this in C# using the StackTrace and StackFrame classes:
StackTrace st = new StackTrace(new StackFrame(true));
Console.WriteLine(" Stack trace for current level: {0}", st.ToString());
StackFrame sf = st.GetFrame(0);
Console.WriteLine(" File: {0}", sf.GetFileName());
Console.WriteLine(" Method: {0}", sf.GetMethod().Name);
Console.WriteLine(" Line Number: {0}", sf.GetFileLineNumber());
Console.WriteLine(" Column Number: {0}", sf.GetFileColumnNumber());
Of course, this comes with some overhead.
Caller Information has been added to .NET 4.5. This will be compiled, a big improvement over having to examine the stack trace manually.
public void Log(string message,
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
// Do logging
}
Simply call it in this manner. The compiler will fill in the file name and line number for you:
logger.Log("Hello!");