2011-05-14 38 views
9

我已经为Log4net创建了一个包装器(我可能会放弃支持NLog;我还没有决定),并且我缩进记录的消息结果给出调用结构的想法。例如:对于C#日志记录,如何以最小开销获取调用堆栈深度?

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

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

回答

5

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

+0

感谢您的回答。我可能是错的,但我假设当你创建一个StackTrace对象时,所有的StackFrame对象也被创建。你说这不是这种情况? – RenniePet 2011-05-14 01:56:44

+3

新的StackFrame()对象并不昂贵,如果你不使用它,但如果你真的需要顶级性能,请阅读一些扭曲Reflection.Emit的东西:http://ayende.com/blog/3879/reducing-成本 - 收益 - 追踪 – 2011-05-14 02:09:17

+0

哇,我印象深刻。也感觉像我的能力不了解你做了什么。稍后当我有更多时间时,我会尽量分析和理解它。但是“Func ”是什么意思? (我正在使用.Net 2,并且该构造标记为未定义。) – RenniePet 2011-05-14 13:10:49

2

感谢Teoman Soygul,尤其是Ooman 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(); 
     } 
    } 

编辑:此版本不适用.Net框架4.5的工作由微软在2017年后期的mscorlib.dll的更新后,见另一种答案我已经发布了一个新版本。 (为了后代的缘故,我离开了这个答案 - 它仍然适用于.Net Framework 2.0和3.5)。

+0

+1,那里的工作很好。 – 2011-05-15 02:27:45

+0

不是真的,它几乎是100%Oren Eini的代码,只是回归到.Net 2。 – RenniePet 2011-05-15 10:30:08

2

经过6年半的可靠服务,我突然体验到我的许多程序是在应用了来自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(); 
     } 
    }