14

I want to write my own logger.

This is my logger manager:

public static class LoggerManager
{
    public static void Error(string name)
    {

    }
}

I am calling Error(string name); method as below:

public class Foo
{
    public void Test()
    {
        LoggerManager.Error(this.GetType().FullName);
    }
}

In this way, I am geting the caller class name in my method called Error.

But I don't want to pass the name to error method every time. I want to make my logger (Or another logger methods: Info(), Warn()) method get the name by itself.

Thanks for your best practice...

LuizLoyola
  • 386
  • 3
  • 18
  • See the duplicate but instead of calling `.Name` call `.DeclaringType` instead to get the `Type` instance, you can then get the name, full name, assembly info, whatever. – Igor Feb 01 '18 at 19:53
  • I voted reopen the question. The OP wants to get caller class instead of method. – lucky Feb 01 '18 at 19:56
  • But you can easily get the class a method is defined in, can´t you? If we´d reopen this question, one may also ask how to get the namespace or also how to get a class a property is defined in. Only because the question doesn´t fit the exact use-case it can answer the question. – MakePeaceGreatAgain Feb 01 '18 at 20:00
  • @HimBromBeere, I don't agree with you. Of course, we can easily get the class name with referenced question but the question is pretty clear about getting the class name. Referenced answer doesn't fit exact answer anyway. – lucky Feb 01 '18 at 20:14
  • The point is, that if we are looking for duplicates that *exactly* match the context, then we are unlikely to ever find *any* duplicate, as the context may slightly be ifferent from those in the duplicates. – MakePeaceGreatAgain Feb 01 '18 at 20:21
  • Why do you want to reinvent the wheel? There are many loggers that will solve this and lots of other problems you will come across. All of a sudden, you will be asked to write the logs to different files depending on the date, then you will need to write the current thread into each log line, then a different format to a error log and a debug log, and by then, you've probably duplicated nlog or log4net. – Neil Feb 01 '18 at 21:55

3 Answers3

25

You can use StackFrame of System.Diagnostics and MethodBase of System.Reflection.

StackFrame(Int32, Boolean) initializes a new instance of the StackFrame class that corresponds to a frame above the current stack frame, optionally capturing source information.

MethodBase, provides information about methods and constructors.

public static string NameOfCallingClass()
{
    string fullName;
    Type declaringType;
    int skipFrames = 2;
    do
    {
        MethodBase method = new StackFrame(skipFrames, false).GetMethod();
        declaringType = method.DeclaringType;
        if (declaringType == null)
        {
            return method.Name;
        }
        skipFrames++;
        fullName = declaringType.FullName;
    }
    while (declaringType.Module.Name.Equals("mscorlib.dll", StringComparison.OrdinalIgnoreCase));

    return fullName;
}

Your logger classes call this method:

public static void Error()
{
    string name = NameOfCallingClass();
}
LuizLoyola
  • 386
  • 3
  • 18
Mehmet Topçu
  • 1,550
  • 1
  • 12
  • 28
  • 1
    Thank you for this. This looks to handle more then just grabbing Method.DeclaringType - could you please add some explanations for why the While loop is needed and also why checking for "mscorlib.dll" is needed? – AlexVPerl Nov 28 '19 at 16:59
  • 2
    From the C# chat, @TomW helped: `declaringType = method.DeclaringType` gets the class name. The `while` loop looks up the stack by incrementing `skipFrames`, looking for the first frame where the calling type isn't in `mscorlib.dll`. In other words, it's not a Microsoft class but one of your own. – Alex Aug 26 '20 at 11:44
  • I have written a unit test for this code. I get ```System.RuntimeMethodHandle``` instead of the class name of the test. Does anyone know why this is? (The test is in another linked project). – DaveVentura Feb 10 '22 at 16:26
11

You could grab it from StackTrace instance.

public static class LoggerManager
{
    public static void Error()
    {
        var methodInfo = new StackTrace().GetFrame(1).GetMethod();
        var clasName = methodInfo.ReflectedType.Name;
    }
}
lucky
  • 12,110
  • 4
  • 20
  • 41
11

You can use CallerMemberName and CallerFilePath attributes

public static void Log(string text, [CallerMemberName] string caller = "", [CallerFilePath] string file = "")
{
   WriteLog(text, caller, file);
}


Log("Something happened");

See also CallerLineNumber


BTW: You may want to compare the speed of costly StackFrame with built-in attributes.

int num = 500000;
var t1 = Measure(() => NameOfCallingClass(), num); //<-- 9000
var t2 = Measure(() => Log("aa"), num); //<--26


long Measure(Action func, int num)
{
    func();
    var sw = Stopwatch.StartNew();
    for (int i = 0; i < num; i++)
    {
        func();
    }
    return sw.ElapsedMilliseconds;
}
Pang
  • 9,073
  • 146
  • 84
  • 117
L.B
  • 110,417
  • 19
  • 170
  • 215
  • 10
    Caller info attributes does not answer the question. He wants the class name, not the member name or the file path of the residing class. Using stackframe voodoo is currently the only known way to do this. Also, using [CallerFilePath] will get the path of the file when it was compiled. All in all, in a production log this will look like garbage. – Oyvind May 09 '19 at 07:15
  • Something like $(SolutionDir)=.\ in csproj fixes that last objection by Oyvind. – Jannes May 26 '20 at 14:10
  • 2
    Or `string callerClassName = Path.GetFileNameWithoutExtension(callerFilePath);` will work for class name, assuming file names correspond to contained class names. – Sepster Apr 28 '21 at 06:03