MoveNext而不是实际的方法/任务名称
本文关键字:方法 任务 MoveNext | 更新日期: 2023-09-27 17:51:25
使用log4net声明为:
private readonly ILog log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType());
在异步方法或任务中,像这样:
public async void CheckSomething()
{
log.Info(null);
//....
}
日志MoveNext
而不是CheckSomething
。知道如何让它记录一个实际的方法名吗?
将所有async
方法重写为状态机,以满足方法中潜在的await
值。代码存在的最后一个方法是MoveNext
方法,这是log4net
报告的。
在运行时确实没有好的方法从MoveNext
转换到代码最初编写的实际方法。它们在元数据级别上是不连接的。您可能只需要直接记录名称
短:给定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;
}
长(免责声明)
不要在生产环境中使用。它依赖于编译器的行为,这些行为可能会在不通知的情况下在未来的版本中发生变化。对编译器做如下假设:
- 实际运行的异步方法是在生成的类型中生成的。
- 生成的类型是原始类型的嵌套类型,包含原始的手写方法。
- 原始方法获得编译器生成的属性AsyncStateMachine,其中提供了生成的类型。
它在我的代码中工作,我只在调试/测试期间使用它进行运行时代码分析。同样,请不要在产品代码中使用
感谢Jacek gorgoski的回答,这里是我想到的实用程序。它有一些改进,但要很好地与匿名或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;
}
}
我为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]
使用这个,效果很好…
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....
}
使用一个扩展方法返回MethodBase的调用者成员名。
public static class MemberBaseExtension
{
public static string GetDeclaringName(this MethodBase methodBase, [CallerMemberName] string memberName = "")
{
return memberName;
}
}
您可以只使用配置来做类似的事情,但这并不是您所提到的。如果您能够阅读它并且不介意奇怪的语法,您可以使用%type
模式打印出方法的限定名称,然后使用%method
模式打印方法。您甚至可以只打印部分限定名称,以对抗长名称空间。
From the docs:
用于输出发出日志请求的调用方的完全限定类型名称。此转换说明符可选后跟精度说明符,精度说明符是括号中的十进制常量。
如果给出了精度说明符,则只会打印类名中最右边组件的相应数量。默认情况下,类名以完全限定的形式输出。
例如,对于类名"log4net.Layout.PatternLayout",模式%类型{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...
我们不关心d__15.MoveNext
,只要async方法在那里SubNamespace.ClassName+<MethodNameAsync>
.