MoveNext вместо фактического имени метода / задачи
Использование log4net объявлено как:
private readonly ILog log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType());
В асинхронном методе или задаче, например:
public async void CheckSomething()
{
log.Info(null);
//....
}
бревна MoveNext
вместо CheckSomething
, Есть идеи, как сделать так, чтобы в журнал входило реальное имя метода?
7 ответов
Все 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;
}
Long (Отказ от ответственности)
Не используйте это в производстве. Это зависит от поведения компилятора, которое может измениться в будущей версии без предварительного уведомления. Следующие предположения о компиляторе сделаны:
- Фактически запущенный асинхронный метод генерируется внутри сгенерированного типа.
- Сгенерированный тип - это вложенный тип исходного типа, содержащий оригинальный рукописный метод.
- Исходный метод получает сгенерированный компилятором атрибут AsyncStateMachine с предоставленным в нем сгенерированным типом.
Это работает в моем коде, где я использую его для анализа кода во время выполнения только во время отладки / тестирования. Опять же, пожалуйста, не используйте его в рабочем коде.
Благодаря ответу Яцека Горгоня, вот утилита, которую я придумала. Он имеет несколько улучшений, но ему еще предстоит пройти долгий путь, чтобы хорошо работать с анонимными или лямбда-методами.
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
шаблон для печати метода. Вы даже можете напечатать только часть квалифицированного имени для борьбы с длинными пространствами имен.
Из документов:
Используется для вывода полного имени типа вызывающего объекта, выдающего запрос на регистрацию. За этим спецификатором преобразования может дополнительно следовать спецификатор точности, то есть десятичная константа в скобках.
Если задан спецификатор точности, то будет напечатано только соответствующее количество самых правых компонентов имени класса. По умолчанию имя класса выводится в полной форме.
Например, для имени класса "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...
Мы не заботились о
d__15.MoveNext
пока был асинхронный метод
SubNamespace.ClassName+<MethodNameAsync>
.