对于C#日志记录,如何以最小的开销获取调用堆栈深度?

我已经为Log4net创建了一个包装器(我可能会支持NLog;我还没有决定),并且我缩进了记录的消息结果以给出调用结构的概念。 例如:

2011-04-03 00:20:30,271 [CT] DEBUG - Merlinia.ProcessManager.CentralThread.ProcessAdminCommand - ProcStart - User Info Repository 2011-04-03 00:20:30,271 [CT] DEBUG - Merlinia.ProcessManager.CentralThread.StartOneProcess - User Info Repository 2011-04-03 00:20:30,411 [CT] DEBUG - Merlinia.ProcessManager.CentralThread.SetProcessStatus - Process = User Info Repository, status = ProcStarting 2011-04-03 00:20:30,411 [CT] DEBUG - Merlinia.ProcessManager.CentralThread.SendProcessStatusInfo 2011-04-03 00:20:30,411 [CT] DEBUG - Merlinia.CommonClasses.MhlAdminLayer.SendToAllAdministrators - ProcessTable 2011-04-03 00:20:30,411 [CT] DEBUG - Merlinia.CommonClasses.MReflection.CopyToBinary 2011-04-03 00:20:30,411 [CT] DEBUG - Merlinia.CommonClasses.MReflection.CopyToBinary - False 2011-04-03 00:20:30,411 [CT] DEBUG - Merlinia.CommonClasses.MhlBasicLayer.SendToAllConnections - 228 - True - False 2011-04-03 00:20:30,411 [CT] DEBUG - Merlinia.CommonClasses.MmlNonThreaded.SendObject - 228 2011-04-03 00:20:30,411 [CT] DEBUG - Merlinia.CommonClasses.MllTcpSocket.SendMessage - 228 - True 2011-04-03 00:20:32,174 [10] DEBUG - Merlinia.CommonClasses.MReflection.CreateFromBinary 2011-04-03 00:20:32,174 [10] DEBUG - Merlinia.CommonClasses.MReflection.CopyFromBinary - Bytes = 71 2011-04-03 00:20:32,174 [CT] DEBUG - Merlinia.ProcessManager.CentralThread.MessagingCallback - User Info Repository - ProcessInfoAndRequests 2011-04-03 00:20:32,174 [CT] DEBUG - Merlinia.ProcessManager.CentralThread.ProcessProcessInfoAndRequests - User Info Repository 

我使用System.Diagnostics.StackTrace并计算StackFrames。

现在问题是:有没有更有效的方法呢? 我只需要确定(相对)调用堆栈深度,即当前深度加上或减去上次调用日志包装器时的深度。 (请注意,我实际上并没有使用StackFrame对象 – 否则我会得到方法名称。)

我希望有一些简单的高性能方法来查询调用堆栈深度或堆栈使用情况。

只需使用StackTrace.FrameCount属性,并将其与之前记录的FrameCount进行比较。 仅供参考, FrameCount可能是检索实际帧数的最快方法,因为它只会将内部m_iNumOfFrames字段返回给您。

经过六年半的可靠服务,我突然意识到我的许多程序在应用Microsoft的更新(包括对.Net Framework 4.5的更改)后于2017年末崩溃。 这就是编写依赖于mscorlib.dll中内部未记录数据结构的代码所能获得的。

这个版本的代码再次运行,并且面对mscorlib.dll可能的未来更新,它的设计也会稍微强一些 – 它希望只是优雅地失败并且总是返回零。 但是仍然无法保证将来更改mscorlib.dll导致此代码中的未来崩溃。

  ///  /// This test program demonstrates a faster way of getting call stack depth by avoiding getting a /// StackTrace object. But you can't get the calling method names this way. /// /// See http://stackoverflow.com/questions/5999177/for-c-logging-how-to-obtain-call-stack-depth-with-minimal-overhead /// and http://ayende.com/blog/3879/reducing-the-cost-of-getting-a-stack-trace /// /// Update, late 2017, .Net mscorlib.dll has been changed for .Net 4.5. In the code below the two /// possibilities are called "old .Net" and "new .Net". The two versions can be tested by setting /// the target for this project to either .Net Framework 2.0 or .Net Framework 4.5. ///  class TestProgram { static void Main() { OneTimeSetup(); int i = GetCallStackDepth(); // i = 10 on my test machine for old .Net, 12 for new .Net int j = AddOneToNesting(); Console.WriteLine(j == i + 1 ? "Test succeeded!" : "Test failed!!!!!!!!"); Console.ReadKey(); } private delegate object DGetStackFrameHelper(); private static DGetStackFrameHelper _getStackFrameHelper = null; private static FieldInfo _frameCount = null; private static void OneTimeSetup() { try { Type stackFrameHelperType = typeof(object).Assembly.GetType("System.Diagnostics.StackFrameHelper"); // ReSharper disable once PossibleNullReferenceException MethodInfo getStackFramesInternal = Type.GetType("System.Diagnostics.StackTrace, mscorlib").GetMethod( "GetStackFramesInternal", BindingFlags.Static | BindingFlags.NonPublic); if (getStackFramesInternal == null) return; // Unknown mscorlib implementation DynamicMethod dynamicMethod = new DynamicMethod( "GetStackFrameHelper", typeof(object), new Type[0], typeof(StackTrace), true); ILGenerator generator = dynamicMethod.GetILGenerator(); generator.DeclareLocal(stackFrameHelperType); bool newDotNet = false; ConstructorInfo constructorInfo = stackFrameHelperType.GetConstructor(new Type[] {typeof(bool), typeof(Thread)}); if (constructorInfo != null) generator.Emit(OpCodes.Ldc_I4_0); else { constructorInfo = stackFrameHelperType.GetConstructor(new Type[] {typeof(Thread)}); if (constructorInfo == null) return; // Unknown mscorlib implementation newDotNet = true; } generator.Emit(OpCodes.Ldnull); generator.Emit(OpCodes.Newobj, constructorInfo); generator.Emit(OpCodes.Stloc_0); generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldc_I4_0); if (newDotNet) generator.Emit(OpCodes.Ldc_I4_0); // Extra parameter generator.Emit(OpCodes.Ldnull); generator.Emit(OpCodes.Call, getStackFramesInternal); generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ret); _getStackFrameHelper = (DGetStackFrameHelper) dynamicMethod.CreateDelegate(typeof(DGetStackFrameHelper)); _frameCount = stackFrameHelperType.GetField("iFrameCount", BindingFlags.NonPublic | BindingFlags.Instance); } catch {} // _frameCount remains null, indicating unknown mscorlib implementation } private static int GetCallStackDepth() { if (_frameCount == null) return 0; // Unknown mscorlib implementation return (int)_frameCount.GetValue(_getStackFrameHelper()); } private static int AddOneToNesting() { return GetCallStackDepth(); } } 

感谢Teoman Soygul,特别是Oren Eini,他的博客Teoman提供了链接。

以下是一些“概念validation”代码,我认为这是我将要使用的解决方案 – 尽管我必须承认我没有做任何时序测试。

  class TestProgram { static void Main(string[] args) { OneTimeSetup(); int i = GetCallStackDepth(); // i = 10 on my test machine i = AddOneToNesting(); // Now i = 11 } private delegate object DGetStackFrameHelper(); private static DGetStackFrameHelper _getStackFrameHelper; private static FieldInfo _frameCount; private static void OneTimeSetup() { Type stackFrameHelperType = typeof(object).Assembly.GetType("System.Diagnostics.StackFrameHelper"); MethodInfo getStackFramesInternal = Type.GetType("System.Diagnostics.StackTrace, mscorlib").GetMethod( "GetStackFramesInternal", BindingFlags.Static | BindingFlags.NonPublic); DynamicMethod dynamicMethod = new DynamicMethod( "GetStackFrameHelper", typeof(object), new Type[0], typeof(StackTrace), true); ILGenerator generator = dynamicMethod.GetILGenerator(); generator.DeclareLocal(stackFrameHelperType); generator.Emit(OpCodes.Ldc_I4_0); generator.Emit(OpCodes.Ldnull); generator.Emit(OpCodes.Newobj, stackFrameHelperType.GetConstructor(new Type[] { typeof(bool), typeof(Thread) })); generator.Emit(OpCodes.Stloc_0); generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldc_I4_0); generator.Emit(OpCodes.Ldnull); generator.Emit(OpCodes.Call, getStackFramesInternal); generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ret); _getStackFrameHelper = (DGetStackFrameHelper)dynamicMethod.CreateDelegate(typeof(DGetStackFrameHelper)); _frameCount = stackFrameHelperType.GetField( "iFrameCount", BindingFlags.NonPublic | BindingFlags.Instance); } private static int GetCallStackDepth() { return (int)_frameCount.GetValue(_getStackFrameHelper()); } private static int AddOneToNesting() { return GetCallStackDepth(); } } 

编辑:此版本在2017年末由Microsoft更新mscorlib.dll后不适用于.Net Framework 4.5。请参阅我发布的另一个更新版本的答案。 (我为了后代而离开了这个答案 – 它仍然适用于.Net Framework 2.0和3.5。)