共用方式為


新增分散式追蹤檢測

本文適用於:✔️ .NET Core 2.1 和更新版本 ✔️ .NET Framework 4.5 和更新版本

您可以使用 API 來檢測 System.Diagnostics.Activity .NET 應用程式,以產生分散式追蹤遙測。 某些檢測內建於標準 .NET 連結庫,但您可能想要新增更多,讓您的程式代碼更容易診斷。 在本教學課程中,您將新增新的自定義分散式追蹤檢測。 請參閱 收集教程 ,以了解如何記錄此儀器產生的遙測數據。

先決條件

建立初始應用程式

首先,您將建立一個使用 OpenTelemetry 收集遙測的範例應用程式,但目前尚無任何儀器化。

dotnet new console

以 .NET 5 和更新版本為目標的應用程式已經包含必要的分散式追蹤 API。 針對以舊版 .NET 為目標的應用程式,請新增 System.Diagnostics.DiagnosticSource NuGet 套件 第 5 版或更新版本。 針對以 netstandard 為目標的連結庫,建議您參考仍受支援且包含連結庫所需 API 的最舊套件版本。

dotnet add package System.Diagnostics.DiagnosticSource

新增 OpenTelemetry 和OpenTelemetry.Exporter.Console NuGet 套件,以用來收集遙測。

dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console

以下列範例來源取代產生的Program.cs內容:

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Threading.Tasks;

namespace Sample.DistributedTracing
{
    class Program
    {
        static async Task Main(string[] args)
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
                .AddSource("Sample.DistributedTracing")
                .AddConsoleExporter()
                .Build();

            await DoSomeWork("banana", 8);
            Console.WriteLine("Example work done");
        }

        // All the functions below simulate doing some arbitrary work
        static async Task DoSomeWork(string foo, int bar)
        {
            await StepOne();
            await StepTwo();
        }

        static async Task StepOne()
        {
            await Task.Delay(500);
        }

        static async Task StepTwo()
        {
            await Task.Delay(1000);
        }
    }
}

應用程式還沒有檢測,因此沒有可顯示的追蹤資訊:

> dotnet run
Example work done

最佳做法

只有應用程式開發人員需要參考選擇性的第三方連結庫來收集分散式追蹤遙測,例如此範例中的 OpenTelemetry。 .NET 類庫作者可以完全依賴 System.Diagnostics.DiagnosticSource 中的 API,這是 .NET 運行時的一部分。 這可確保連結庫會在各種 .NET 應用程式中執行,而不論應用程式開發人員對於要用於收集遙測之連結庫或廠商的喜好設定為何。

新增基本儀器配置

應用程式和程式庫會使用 System.Diagnostics.ActivitySourceSystem.Diagnostics.Activity 類別來新增分散式追蹤功能。

活動源

首先建立 ActivitySource 的實例。 ActivitySource 提供 API 來建立和啟動 Activity 物件。 在 Main()using System.Diagnostics; 上方新增靜態 ActivitySource 變數至 using 指令。

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Sample.DistributedTracing
{
    class Program
    {
        private static ActivitySource source = new ActivitySource("Sample.DistributedTracing", "1.0.0");

        static async Task Main(string[] args)
        {
            // ...

最佳做法

  • 建立 ActivitySource 一次,將其儲存在靜態變數中,並視需要使用該實例。 每個程式庫或程式庫子元件都可以(而且通常應該)建立自己的原始碼。 如果您預期應用程式開發人員能夠獨立啟用和停用來源中的活動遙測,請考慮建立新的來源,而不是重複使用現有的來源。

  • 傳遞至建構函式的來源名稱必須是唯一的,以避免與任何其他來源發生衝突。 如果相同元件內有多個來源,請使用包含元件名稱和選擇性元件名稱的階層式名稱,例如 Microsoft.AspNetCore.Hosting 如果某個元件要在第二個獨立的元件中加入程式碼的檢測工具,其名稱應以定義 ActivitySource 的元件為基礎,而不是以被檢測程式碼的元件為基礎。

  • 版本參數是選擇性的。 建議您提供版本號,以便您釋出多個版本的函式庫並修改具備儀器化的遙測。

備註

OpenTelemetry 使用替代詞彙 'Tracer' 和 'Span'。 在 .NET 'ActivitySource' 中,是 Tracer 的實作,而 Activity 是 'Span' 的實作。 .NET 的活動類型遠早於 OpenTelemetry 規格出現,且原始 .NET 命名已被保留,以維持.NET 生態系統和 .NET 應用程式相容性的一致性。

活動

使用 ActivitySource 物件,在有意義的工作單位周圍啟動和停止活動物件。 使用如下所示的程式代碼更新 DoSomeWork(:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        await StepOne();
        await StepTwo();
    }
}

執行應用程式現在會顯示正在記錄的新活動:

> dotnet run
Activity.Id:          00-f443e487a4998c41a6fd6fe88bae644e-5b7253de08ed474f-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:36:51.4720202Z
Activity.Duration:    00:00:01.5025842
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 067f4bb5-a5a8-4898-a288-dec569d6dbef

備註

  • ActivitySource.StartActivity 會同時建立並啟動活動。 列出的程序代碼模式是使用 using 區塊,這會在執行 區塊之後自動處置建立的 Activity 物件。 處置 Activity 物件將會使其停止,因此不需要明確呼叫 Activity.Stop() 以停止程式碼的運行。 這可簡化程式代碼撰寫模式。

  • ActivitySource.StartActivity 內部決定是否有任何監聽器正在監控活動。 如果沒有已註冊的接聽程式,或有不感興趣的接聽程式, StartActivity() 將會傳回 null 並避免建立 Activity 物件。 這是效能優化,因此程式代碼模式仍可用於經常呼叫的函式中。

選擇性:填入標記

活動支持稱為 Tags 的索引鍵/值數據,通常用來儲存可能適用於診斷之工作的任何參數。 更新 DoSomeWork() 以包含它們:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        await StepTwo();
    }
}
> dotnet run
Activity.Id:          00-2b56072db8cb5a4496a4bfb69f46aa06-7bc4acda3b9cce4d-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:37:31.4949570Z
Activity.Duration:    00:00:01.5417719
Activity.TagObjects:
    foo: banana
    bar: 8
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 25bbc1c3-2de5-48d9-9333-062377fea49c

Example work done

最佳做法

  • 如上所述,activityActivitySource.StartActivity 傳回的可能為 null。 C# 中的空合併運算子 ?. 是一種簡便的簡寫,僅在 Activity.SetTag 不是 null 時才會調用 activity。 行為模式與寫入資料的方式相同:
if(activity != null)
{
    activity.SetTag("foo", foo);
}
  • OpenTelemetry 提供一組建議的 慣例 ,可用來設定代表常見應用程式工作類型的活動標記。

  • 如果您正在為具有高效能需求的函式設置檢測,Activity.IsAllDataRequested 是個提示,指示接收活動的程式碼是否計畫讀取標籤等輔助資訊。 如果沒有監聽器會讀取它,則不需要插裝的程式碼消耗 CPU 週期來填充它。 為了簡單起見,此範例不會套用該優化。

選擇性:新增事件

事件是具備時間戳的訊息,可以將任意額外的診斷資料流附加到活動上。 將一些事件新增至活動:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        activity?.AddEvent(new ActivityEvent("Part way there"));
        await StepTwo();
        activity?.AddEvent(new ActivityEvent("Done now"));
    }
}
> dotnet run
Activity.Id:          00-82cf6ea92661b84d9fd881731741d04e-33fff2835a03c041-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:39:10.6902609Z
Activity.Duration:    00:00:01.5147582
Activity.TagObjects:
    foo: banana
    bar: 8
Activity.Events:
    Part way there [3/18/2021 10:39:11 AM +00:00]
    Done now [3/18/2021 10:39:12 AM +00:00]
Resource associated with Activity:
    service.name: MySample
    service.instance.id: ea7f0fcb-3673-48e0-b6ce-e4af5a86ce4f

Example work done

最佳做法

  • 事件會儲存在記憶體內部清單中,直到可以傳輸,讓此機制只適合記錄少量的事件。 對於大型或無限制的事件量,最好使用專門為此任務設計的記錄 API,例如 ILogger。 不論應用程式開發人員是否選擇使用分散式追蹤,ILogger 也會確保記錄資訊可供使用。 ILogger 支援自動擷取使用中的活動標識碼,因此透過該 API 記錄的訊息仍可與分散式追蹤相互關聯。

選擇性:新增狀態

OpenTelemetry 可讓每個活動報告一個狀態,該狀態代表工作通過或失敗的結果。 .NET 具有適用於此用途的強型別 API:

這些ActivityStatusCode值會表示為、 UnsetOkError

更新 DoSomeWork() 以設定狀態:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        activity?.AddEvent(new ActivityEvent("Part way there"));
        await StepTwo();
        activity?.AddEvent(new ActivityEvent("Done now"));

        // Pretend something went wrong
        activity?.SetStatus(ActivityStatusCode.Error, "Use this text give more information about the error");
    }
}

選擇性:新增其他活動

活動可以用來嵌套方式來描述更大工作單位的一部分。 這在當部分程式碼可能無法快速執行,或更好地定位來自特定外部依賴的失敗時,可能會有價值。 雖然此範例會在每個方法中使用 Activity,但這是因為額外的程式代碼已最小化。 在大型且現實的專案中,在每個方法中使用 Activity 會產生極其冗長的追蹤,因此不建議這麼做。

更新 StepOne 和 StepTwo,以便在這些步驟中增加更多的追蹤功能:

static async Task StepOne()
{
    using (Activity activity = source.StartActivity("StepOne"))
    {
        await Task.Delay(500);
    }
}

static async Task StepTwo()
{
    using (Activity activity = source.StartActivity("StepTwo"))
    {
        await Task.Delay(1000);
    }
}
> dotnet run
Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-39cac574e8fda44b-01
Activity.ParentId:    00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: StepOne
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.4278822Z
Activity.Duration:    00:00:00.5051364
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-4ccccb6efdc59546-01
Activity.ParentId:    00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: StepTwo
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.9441095Z
Activity.Duration:    00:00:01.0052729
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.4256627Z
Activity.Duration:    00:00:01.5286408
Activity.TagObjects:
    foo: banana
    bar: 8
    otel.status_code: ERROR
    otel.status_description: Use this text give more information about the error
Activity.Events:
    Part way there [3/18/2021 10:40:51 AM +00:00]
    Done now [3/18/2021 10:40:52 AM +00:00]
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Example work done

StepOne 和 StepTwo 都包含指向 SomeWork 的 ParentId,請注意。 控制台不是工作的巢狀樹狀結構的絕佳視覺效果,但許多 GUI 檢視者,例如 Zipkin 可以將此顯示為甘特圖:

Zipkin 甘特圖

可選:ActivityKind

活動具有 Activity.Kind 屬性,其描述Activity、其父系和子系之間的關聯性。 根據預設,所有新的活動都會設定為 Internal,這適用於沒有遠端父系或子系之應用程式內部作業的活動。 您可以使用 上的 ActivitySource.StartActivitykind 參數來設定其他種類。 如需其他選項,請參閱 System.Diagnostics.ActivityKind

當批次處理系統中發生工作時,單一 Activity 可能會代表許多不同的要求同時運作,每個要求都有自己的追蹤 ID。雖然 Activity 限制為具有單一父系,但它可以使用 System.Diagnostics.ActivityLink 連結至其他追蹤 ID。 每個 ActivityLink 都會填入 ActivityContext ,其會儲存所連結之活動的標識碼資訊。 ActivityContext 可以從同進程的 Activity 物件使用 Activity.Context 擷取,也可以使用 ActivityContext.Parse(String, String) 從序列化的標識符資訊中解析。

void DoBatchWork(ActivityContext[] requestContexts)
{
    // Assume each context in requestContexts encodes the trace-id that was sent with a request
    using(Activity activity = s_source.StartActivity(name: "BigBatchOfWork",
                                                     kind: ActivityKind.Internal,
                                                     parentContext: default,
                                                     links: requestContexts.Select(ctx => new ActivityLink(ctx))
    {
        // do the batch of work here
    }
}

不同於可隨選新增的事件和標籤,必須在期間 StartActivity() 新增連結,且之後是不可變的。

這很重要

根據 OpenTelemetry 規格,鏈接數目的建議限製為 128。 不過,請務必注意,不會強制執行此限制。