'Microsoft.Extensions.Logging custom implementation, how to get the caller method name?

I'm in .NET 6, I implemented a custom ILogger<TService> (of Microsoft.Extensions.Logging).

Usually, when I implement a logger, I also get the caller method name with [CallerMemberName] attribute, for example:

public void LogInformation(string message, [CallerMemberName] string callerName = null)

I really don't understand how to get the same [CallerMemberName] with the ILogger<TService> interface. I checked for EventId, but it doesn't have that information.

// Dummy class, don't try this at home!
internal class CustomLogger<TService> : ILogger<TService>
{
    public IDisposable BeginScope<TState>(TState state)
        => default;

    public bool IsEnabled(LogLevel logLevel)
        => true;

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception exception,
        Func<TState, Exception, string> formatter)
    {
        // TODO How to get the caller name?
        string callerName = "Log";

        switch (logLevel)
        {
            case LogLevel.Trace:
            case LogLevel.Debug:
            case LogLevel.Information:
            case LogLevel.Warning:
                Console.WriteLine(callerName + ": " + formatter(state, exception));
                break;

            case LogLevel.Error:
            case LogLevel.Critical:
                Console.WriteLine(callerName + ": " + formatter(state, exception));
                break;
        }
    }
}

Is there a bult-in way to get it without reflection or other expensive methods?


Why I need to implement a custom ILogger<T>?

  1. I use two external libraries which I don't control that want and ILogger<T> implementation (so I can't force them to call an extension method),
  2. I need to store the logs by my own storage and there aren't any providers for what I need in this case

Why I need to add the CallerMemberName?

  • To enrich my logs. They are far more readable and informational with that information.


Solution 1:[1]

I really wish I could edit my own "comment", it was an answer converted to a comment, but, here it goes. From the answer I shared

public void Log()
{
  var stackTrace = new System.Diagnostics.StackTrace(1); // skip one frame as this is the Log function frame
  var name = stackTrace.GetFrame(0).GetMethod().Name;
}

or avoiding reflection

using (_logger.BeginScope("name of method"))
{
    // log the stuff
}

credits to the original answer

Solution 2:[2]

you could add/overload the logging functions (extension methods) to the static LoggerExtensions class and in your Log function check for the beginning of the formatted message, split..

public static class LoggerExtensions
{
    public static void LogCritical(this ILogger logger, string? message, [CallerMemberName] string callerMemberName = "")
    {
        logger.LogCritical("cmn_{message}_{caller}", message, callerMemberName);
    }
        
    public static void LogDebug(this ILogger logger, string? message, [CallerMemberName] string callerMemberName = "")
    {
        logger.LogDebug("cmn_{message}_{caller}", message, callerMemberName);
    }

in Log<TState>:

string message = formatter(state, exception);
string caller = "";
            
if (message.StartsWith("cmn"))

    string[] messageSplit = message.Split(new char[] {'_'});
    message = messageSplit[1];
    caller = messageSplit[2];
}

(unfortunatelly you cannot directly access TState state in Log<TState> since it's internal, thus the split workaround)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 j.loucao.silva
Solution 2