【问题标题】:MoveNext instead of actual method/task nameMoveNext 而不是实际的方法/任务名称
【发布时间】:2014-03-23 23:03:13
【问题描述】:

使用 log4net 声明为:

private readonly ILog log = 
       LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType());

在异步方法或任务中,例如:

public async void CheckSomething()
{
    log.Info(null);
    //....
}

记录MoveNext 而不是CheckSomething。 知道如何让它记录一个实际的方法名称吗?

【问题讨论】:

  • 这是发布版本吗?编译器可以是inlining the methods。您如何获得方法名称?您的记录器名称被定义为包含记录器的类型,那么您使用的是 PatternLayout 吗?

标签: c# log4net async-await methodbase


【解决方案1】:

所有async 方法都被重写到状态机中,以满足方法中潜在的await 值。代码存在的最后一种方法是MoveNext 方法,这是log4net 所报告的。

在运行时确实没有很好的方法从MoveNext 转换到最初编写代码的实际方法。它们在元数据级别上有些脱节。您可能只需要直接记录名称即可

【讨论】:

【解决方案2】:

简短:给定MoveNext() 方法,试试这个:

private static MethodBase GetRealMethodFromAsyncMethod(MethodBase asyncMethod)
{
    var generatedType = asyncMethod.DeclaringType;
    var originalType = generatedType.DeclaringType;
    var matchingMethods = 
        from methodInfo in originalType.GetMethods() 
        let attr = methodInfo.GetCustomAttribute<AsyncStateMachineAttribute>() 
        where attr != null && attr.StateMachineType == generatedType 
        select methodInfo;

    // If this throws, the async method scanning failed.
    var foundMethod = matchingMethods.Single();
    return foundMethod;
}

长(免责声明)

不要在生产中使用它。它依赖于编译器行为,这可能会在未来版本中更改,恕不另行通知。对编译器做了以下假设:

  1. 实际运行的异步方法是在生成的类型中生成的。
  2. 生成的类型是包含原始手写方法的原始类型的嵌套类型。
  3. 原始方法获取编译器生成的属性 AsyncStateMachine,其中提供了生成的类型。

它适用于我的代码,我仅在调试/测试期间将其用于运行时代码分析。同样,请不要在生产代码中使用它

【讨论】:

  • 感谢您的建议。这也非常适合作为扩展方法。在 MethodBase asyncMethod 参数前添加“this”关键字,可以简化使用 SomeHelper.GetRealMethodFromAsyncMethod(MethodBase.GetCurrentMethod()) 变为 MethodBase.GetCurrentMethod().GetRealMethodFromAsyncMethod()
  • 看起来很有趣! :) 非常感谢您提供的明确的假设列表,这是不常见的事情。
  • 不客气。我相信我已经在您自己的代码中看到过类似的研究,在我们大约 10 年前参与的一个项目中;)
【解决方案3】:

感谢 Jacek Gorgoń 的回答,这是我提出的实用程序。它有一些改进,但要很好地使用匿名或 lambda 方法还有很长的路要走。

static string GetMethodContextName() {
    var name = new StackTrace().GetFrame(1).GetMethod().GetMethodContextName();
}

static string GetMethodContextName(this MethodBase method) {
    if (method.DeclaringType.GetInterfaces().Any(i => i == typeof(IAsyncStateMachine))) {
        var generatedType = method.DeclaringType;
        var originalType = generatedType.DeclaringType;
        var foundMethod = originalType.GetMethods(Instance | Static | Public | NonPublic | DeclaredOnly)
            .Single(m => m.GetCustomAttribute<AsyncStateMachineAttribute>()?.StateMachineType == generatedType);
        return foundMethod.DeclaringType.Name + "." + foundMethod.Name;
    } else {
        return method.DeclaringType.Name + "." + method.Name;
    }
}

这是一个示例用法:

class Program { 
    static void Main(string[] args) {
        // outputs Program.Main
        Console.WriteLine(GetMethodContextName());
        Test().Wait();
    }
    static async Task Test() { 
        // outputs Program.Test
        Console.WriteLine(GetMethodContextName());
        await Task.CompletedTask;
    }
}

【讨论】:

    【解决方案4】:

    我写了一个简单的 log4net 包装器。

    public class Logger
    {
        private ILog _Log { get; set; }
    
        public Logger(Type declaringType)
        {
            _Log = LogManager.GetLogger(declaringType);
        }
    
        public void Error(Exception exception, [CallerMemberName] string callerMemberName = "")
        {
            _Log.Error(callerMemberName, exception);
        }
    }
    

    在进行日志记录的代码中,只需:

    private Logger Log = new Logger(MethodBase.GetCurrentMethod().DeclaringType);
    

    当然,如果你想做Info、Debug等,你可以将它添加到包装类中。

    注意
    这利用了 c# 5.0 [CallerMemberName]

    【讨论】:

    • 干净而聪明。谢谢!
    【解决方案5】:

    使用这个,效果很好...

    public void Log(Microsoft.Extensions.Logging.LogLevel level, string message,
            [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
            [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
            [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
        { 
    //do your logging here....
        }
    

    【讨论】:

      【解决方案6】:

      使用仅返回 MethodBase 的调用者成员名称的扩展方法。

      public static class MemberBaseExtension
      {
          public static string GetDeclaringName(this MethodBase methodBase, [CallerMemberName] string memberName = "")
          {
              return memberName;
          }
      }
      

      【讨论】:

      • 我确认这适用于异步方法!但是写一个扩展方法对我来说有点过头了,一个简单的静态方法就足够了。
      【解决方案7】:

      您可以通过仅使用配置来执行此类操作,但这并不是您所提到的。如果您能够阅读它并且不介意奇怪的语法,您可以使用%type 模式来打印出方法的限定名称,然后使用%method 模式来打印方法。您甚至可以只打印一些限定名称来对抗长命名空间。

      来自docs

      用于输出发出日志请求的调用者的完全限定类型名称。此转换说明符可以选择后跟精度说明符,即括号中的十进制常量。

      如果给出了精度说明符,则只会打印类名最右边的相应数量的组件。默认情况下,类名以完全限定的形式输出。

      例如,对于类名“log4net.Layout.PatternLayout”,模式 %type{1} 将输出“PatternLayout”。

      例如。

      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date %5level %logger %type{2}.%method [%line] - %message%newline %exception" />
      </layout>
      

      MoveNext 打印而言,它看起来像:

      2021-03-10 11:45:29,203  INFO StackOverflowLogger SubNamespace.ClassName+<MethodNameAsync>d__15.MoveNext [123] - Logging is starting...
      

      只要有异步方法SubNamespace.ClassName+&lt;MethodNameAsync&gt;,我们就不关心d__15.MoveNext

      【讨论】:

        猜你喜欢
        • 2011-01-02
        • 2016-01-23
        • 1970-01-01
        • 1970-01-01
        • 2011-08-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-17
        相关资源
        最近更新 更多