问题 对于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对象 - 否则我会得到方法名称。)

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


1412
2018-05-14 01:05


起源



答案:


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


6
2018-05-14 01:42



感谢您的回答。我可能错了,但我假设在创建StackTrace对象时也会创建所有StackFrame对象。你说的不是这样吗? - RenniePet
新的StackFrame()对象并不昂贵,如果你不使用它,但如果你真的需要最高性能,请继续阅读一些扭曲的Reflection.Emit东西: ayende.com/blog/3879/reducing-the-cost-of-getting-a-stack-trace - Teoman Soygul
哇,我印象深刻。也觉得我的能力不能理解你做了什么。当我有更多时间时,我会尝试分析并理解它。但是“Func <object>”是什么意思? (我正在使用.Net 2,并且该构造被标记为未定义。) - RenniePet
这是一个C#3.0功能,它是一个速记通用委托。 Func<object>类似于旧式委托 public delegate object MyDelegate(object o)。顺便说一下,我没有编写代码片段,信用额归原作者所有。 - Teoman Soygul
@Teoman,对,我错误地认为这是你的博客,但仍然赞扬你指点我的方向。我现在已经运行了代码,并做了一些研究,我认为这可能是我正在寻找的解决方案。但我还有一个问题。我需要访问StackFrameHelper.iFrameCount字段。现在,Visual Studio调试器知道这个字段并愿意显示它,没问题。但Visual Studio C#编译器拒绝授予我访问权限,因为StackFrameHelper是“内部”。您对我应该如何访问该字段有什么建议吗?谢谢。 - RenniePet


答案:


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


6
2018-05-14 01:42



感谢您的回答。我可能错了,但我假设在创建StackTrace对象时也会创建所有StackFrame对象。你说的不是这样吗? - RenniePet
新的StackFrame()对象并不昂贵,如果你不使用它,但如果你真的需要最高性能,请继续阅读一些扭曲的Reflection.Emit东西: ayende.com/blog/3879/reducing-the-cost-of-getting-a-stack-trace - Teoman Soygul
哇,我印象深刻。也觉得我的能力不能理解你做了什么。当我有更多时间时,我会尝试分析并理解它。但是“Func <object>”是什么意思? (我正在使用.Net 2,并且该构造被标记为未定义。) - RenniePet
这是一个C#3.0功能,它是一个速记通用委托。 Func<object>类似于旧式委托 public delegate object MyDelegate(object o)。顺便说一下,我没有编写代码片段,信用额归原作者所有。 - Teoman Soygul
@Teoman,对,我错误地认为这是你的博客,但仍然赞扬你指点我的方向。我现在已经运行了代码,并做了一些研究,我认为这可能是我正在寻找的解决方案。但我还有一个问题。我需要访问StackFrameHelper.iFrameCount字段。现在,Visual Studio调试器知道这个字段并愿意显示它,没问题。但Visual Studio C#编译器拒绝授予我访问权限,因为StackFrameHelper是“内部”。您对我应该如何访问该字段有什么建议吗?谢谢。 - RenniePet


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

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

   /// <summary>
   /// 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.
   /// </summary>
   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();
      }
   }

4
2017-12-10 23:53





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

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

   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。)


2
2018-05-15 02:21



+1,那里的工作很棒。 - Teoman Soygul
不是真的,它几乎是100%Oren Eini的代码,只是回归到.Net 2。 - RenniePet