C#中OnExceptionAsync如何获取抛出异常的原始方法名称
问题原因
异步/await方法在编译阶段会被编译器转换为状态机类,抛出异常时栈顶的方法实际是状态机的MoveNext方法,并非你编写的原始业务方法。
解决方案
你可以通过识别状态机类型的元数据,反向解析出原始的业务方法名和类名,以下是可直接复用的实现:
第一步:实现栈帧解析扩展方法
using System.Reflection; using System.Diagnostics; using System.Runtime.CompilerServices; public static class StackTraceExtensions { public static MethodBase GetOriginalMethod(this StackFrame stackFrame) { var method = stackFrame.GetMethod(); if (method == null) return null; // 判断是否是异步状态机的MoveNext方法 var declaringType = method.DeclaringType; if (declaringType == null) return method; // 状态机类会实现IAsyncStateMachine接口,且是编译器生成的类型 if (typeof(IAsyncStateMachine).IsAssignableFrom(declaringType) && Attribute.IsDefined(declaringType, typeof(CompilerGeneratedAttribute))) { // 状态机类名格式通常是 <原始方法名>d__x,提取尖括号中间的原始方法名 var declaringTypeName = declaringType.Name; if (declaringTypeName.StartsWith('<') && declaringTypeName.Contains('>')) { var originalMethodName = declaringTypeName[1..declaringTypeName.IndexOf('>')]; // 状态机类是原始方法所属类的嵌套类,取外层类型即为原始类 var originalType = declaringType.DeclaringType; if (originalType != null) { var originalMethod = originalType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) .FirstOrDefault(m => m.Name == originalMethodName); return originalMethod ?? method; } } } return method; } }
第二步:修改异常过滤器中的调用逻辑
public class ExceptionFilter : ExceptionFilterAttribute { public override Task OnExceptionAsync(HttpActionExecutedContext context, CancellationToken cancellationToken) { var stackTrace = new StackTrace(context.Exception, true); MethodBase originalMethod = null; // 遍历栈帧找到第一个非编译器生成的原始业务方法 for (int i = 0; i < stackTrace.FrameCount; i++) { var frame = stackTrace.GetFrame(i); var method = frame.GetOriginalMethod(); if (method != null && !Attribute.IsDefined(method.DeclaringType, typeof(CompilerGeneratedAttribute))) { originalMethod = method; break; } } // 获取最终的原始类名、方法名 var methodName = originalMethod?.Name ?? "UnknownMethod"; var className = originalMethod?.DeclaringType?.FullName ?? "UnknownClass"; // 后续业务逻辑 Logger.LogError(context.Exception, $"类[{className}]的方法[{methodName}]抛出异常:{context.Exception.Message}"); context.Response = context.Request.CreateResponse(HttpStatusCode.OK, res); return Task.CompletedTask; } }
注意事项
- 如果发布模式开启了代码优化、裁剪或者混淆,可能会导致状态机类名格式变化、解析失败,生产环境使用前建议先做兼容性测试
- 若需要更高的解析成功率,可以调整遍历逻辑,跳过系统命名空间下的栈帧,取第一个业务代码对应的栈帧即可
内容的提问来源于stack exchange,提问作者Shahzad Ahamad
相关产品推荐
相关产品推荐

