C# - 取得目前的呼叫堆疊(StackTrace應用)

"呼叫堆疊(Call Stack)"意即程式執行時從觸發的方法到最後回傳,把整個呼叫的一連串方法紀錄在一個Stack裡。呼叫堆疊可以協助開發人員Debug,快速找到問題發生位置並解決。在軟體測試中,呼叫堆疊通常我們會把它轉成Call Graph,以視覺化表示。像限量這次在寫Log模組時就需要記錄是哪裡觸發寫Log,這樣出問題才會比較好找。所以這篇限量就要來展示一下如何使用C#取得呼叫堆疊資訊。


.NET提供了一個StackTrace的類別會記錄呼叫堆疊, 利用這個類別就可以簡單的實作相關的方法來取得呼叫堆疊資訊。要如何利用StackTrace取得呼叫堆疊呢?最簡單的方式就是只要new一個StackTrace的實體,然後把它ToString回傳就OK了,當然還有其他進階的應用,這就要來了解一下StackTrace的結構。

StackTrace內部其實是StackFrame的集合容器,而StackFrame則是每個呼叫記錄的結構化物件,內容包含了MethodBase物件, 來源檔案行列數位置, 來源檔案名稱等,可以說是整個StackTrace的精隨。我們可以透過StackTrace的GetFrames取得所有StackFrame或GetFrame取得特定的StackFrame,只要取得StackFrame就可以利用它的一些方法如:GetMethod, GetFileName, GetFileLineNumber來取得來源資訊。

StackTrace的建構子有許多種參數:
  • bool fNeedFileInfo:是否要擷取程式來源檔案資訊。
  • StackFrame frame:使用提供的StackFrame進行初始化。
  • Exception e:使用提供的Exception進行初始化。
  • int skipFrames:要略過的StackFrame數量。
在預設的StackTrace建構子執行,會去擷取目前方法之前的呼叫堆疊。如果加入skipFrames的參數就會先Pop skipFrames數量的StackFrame再擷取。如果只想針對單一個StackFrame進行初始化,可以將目標的StackFrame傳入,這時只會有這個StackFrame的呼叫資訊。如果不想讓呼叫資訊那麼透明化,可以將fNeedFileInfo設為false傳入,這樣StackFrame就不會包含來源檔案資訊了。

在限量實作了一個簡單的Helper讓開發人員更方便使用StackTrace。Helper裡有兩個方法。WhoCallMe可以取得呼叫目標方法的位置;CallStack可以取得目標方法之前的呼叫堆疊。程式碼如下:

MyHelper.cs
public class MyHelper
{
 public static string WhoCallMe()
 {
  // 2:省略目前位置與呼叫目前Function的位置
  // true:顯示檔案資訊
  var stackTrace = new StackTrace(2, true);
  var target = stackTrace.GetFrame(0);
  return new StackTrace(target).ToString();
 }

 public static string CallStack()
 {
  // 1:省略目前位置
  // true:顯示檔案資訊
  var stackTrace = new StackTrace(1, true);
  return stackTrace.ToString();
 }
}

接下來就來用一個範例來看看StackTrace如何運作,範例程式碼如下:


Program.cs
class Program
{
 static void Main(string[] args)
 {
  (new ClassA()).FunctionA01();
  
  Console.Read();
 }
}

class ClassA
{
 public void FunctionA01()
 {
  Console.WriteLine(
   string.Format(
    "Method: {0}, WhoCallMe: {1}", 
    MethodBase.GetCurrentMethod().Name,
    MyHelper.WhoCallMe()
   )
  );
  FunctionA02();
 }

 public void FunctionA02()
 {
  Console.WriteLine(
   string.Format(
    "Method: {0}, WhoCallMe: {1}",
    MethodBase.GetCurrentMethod().Name,
    MyHelper.WhoCallMe()
   )
  );
  ClassB.FunctionB01();
 }
}

class ClassB
{
 public static void FunctionB01()
 {
  Console.WriteLine(
   string.Format(
    "Method: {0}, WhoCallMe: {1}",
    MethodBase.GetCurrentMethod().Name,
    MyHelper.WhoCallMe()
   )
  );
  FunctionB02();
 }

 public static void FunctionB02()
 {
  Console.WriteLine(
   string.Format(
    "Method: {0}, WhoCallMe: {1}",
    MethodBase.GetCurrentMethod().Name,
    MyHelper.WhoCallMe()
   )
  );

  Console.WriteLine(MyHelper.CallStack());
 }
}

執行結果:

圖中上方紅框為呼叫WhoCallMe的結果,可以看到每個方法呼叫WhoCallMe後會取得是誰呼叫它的路徑;下方紅框為呼叫CallStack的結果,在範例程式中,我們在FunctionB2呼叫CallStack,此時會以FunctionB2為基準,抓取在這之前的呼叫堆疊。

加碼放送

限量在開發Web專案時,有應用到StackTrace,因為限量把fNeedFileInfo設為true,在開發時Log會包含行號與檔案資訊。但是當限量利用Visual Studio Publish功能把網站發佈出去後,寫出來的Log竟然沒有行號與檔案資訊。後來限量查了一下,發現因為開發模式中,我們的DLL有對應.pdb檔案,這才可以抓到來源檔案的行號與其他資訊。發佈後的網站DLL沒有包含.pdb檔案,所以如果要有檔案資訊的話就要把.pdb檔案複製到DLL的資料夾中才可以。



參考來源:
MSDN - StackTrace 類別






留言