深入研究 .NET 5的開放式遙測

SpringLeee發表於2021-01-21

OpenTelemetry 介紹

OpenTelemetry是一種開放的原始碼規範,工具和SDK,用於檢測,生成,收集和匯出遙測資料(指標,日誌和跟蹤),開放遙測技術得到了Cloud Native Computing Foundation(CNCF)的支援,該基金會支援一系列流行的優秀的開源專案,你可以去看一下CNCF景觀圖,https://landscape.cncf.io/ ,就明白了我的意思,這個SDK支援所有主要的程式語言,包括C#和ASP.NET Core。

在這篇文章中,我將討論OpenTelemetry的全部含義,為什麼要使用它以及如何在.NET中使用,對於典型的應用程式,通常需要記錄三組資料:指標,日誌和跟蹤。

Logging 日誌

可以監聽程式的程式發出的訊息日誌,在.NET應用程式中,如果您使用NuGet包ILogger中的日誌記錄功能,就可以輕鬆的讓OpenTelemetry支援 Microsoft.Extensions.Logging, 如果要構建ASP.NET Core應用程式,通常已經使用了此功能。

Metrics 指標

提供執行程式的指標資訊,包括計數器,儀表盤和直方圖,對OpenTelemetry中指標的支援仍在開發中, 但是已經確定下來了,指標包括以下:

  • CPU 使用百分比
  • 程式記憶體使用量
  • Http的請求數量

Tracing 追蹤

也叫做分散式跟蹤,它記錄單個操作的開始和結束時間以及與該操作相關的引數,比如在ASP.NET Core中記錄HTTP請求的跟蹤,您可能會記錄請求和響應的開始和結束時間,引數將是 Http的請求方式,請求引數,請求地址等,請求呼叫會形成鏈路,您可以深入瞭解時間耗費在哪個服務,或者服務中有異常報錯發生。

Jaeger

收集指標,日誌,追蹤資訊只是一部分,如何進行資料處理,展示是APM系統的功能,因為收集的資料遵循OpenTelemetry標準,所以可以和APM系統完美結合。

Jaeger和Zipkin是可以收集和顯示並且與Open Telemetry相容APM, Zipkin的話比較久了,並且沒有很好的UI,因此我個人推薦Jaeger,看起來像這樣:

上圖顯示了應用程式的跟蹤,您可以看到它如何使用HTTP請求對MySQL,Redis和外部API進行呼叫, 每行的長度顯示了執行所需的時間,您可以輕鬆地從頭到尾檢視跟蹤中執行的所有主要操作,您還可以深入研究每一行,並檢視與該部分跟蹤有關的其他資訊。

Spans 跨度

上面Jaeger圖中的每一行都稱為 Span,在.NET中的每一行均由System.Activities.Activity型別表示,它也具有唯一的識別符號,開始和結束時間以及父範圍的唯一識別符號,所以這些可以形成呼叫鏈,並且Span還可以包含其他引數。

不幸的是,.NET團隊的命名大大偏離了官方的OpenTelemetry規範,我有點疑惑,不過我現在已經明白了大概。

我的理解是.NET已經包含一個Activity的型別,因此.NET團隊決定重用它,而不是重新建立一個 Span的新型別,這意味著很多命名與open-telemetry規範不匹配,在.NET中,你現在可以把 Span 和 Activity身份互換。

注意:在.NET 5中才有ActivitySource,在之前可以用 Activity。

使用Span記錄行為非常簡單,首先,我們必須建立一個ActivitySource可以記錄Span或活動的物件:

private static ActivitySource activitySource = new ActivitySource(
    "companyname.product.library",
    "semver1.0.0");

然後,我們可以呼叫StartActivity開始記錄,最後呼叫Dispose停止記錄Span:

using (var activity = activitySource.StartActivity("ActivityName")
{
    // Pretend to do some work.
    await LongRunningAsync().ConfigureAwait(false);
} // Activity gets stopped automatically at end of this block during dispose.

Events 事件

當Span開啟記錄後,我們也可以在這期間記錄事件,這些事件包含了時間戳資訊:

using (var activity = activitySource.StartActivity("ActivityName")
{
    await LongRunningOperation().ConfigureAwait(false);
}

public async Task LongRunningOperationAsync()
{
    await Task.Delay(1000).ConfigureAwait(false);

    // Log timestamped events that can take place during an activity. 
    Activity.Current?.AddEvent(new ActivityEvent("Something happened."));
}

在LongRunningOperationAsync方法中,有一個問題是,如何把activity傳入這個方法,如果我們定義了一個引數,這體驗也太差了,但是,將兩個操作分離的一個好的方法是使用Activity.Current。

一個常見的錯誤,我可以預見的是,Activity.Current可能是null,所以這裡我加了null判斷。

Attributes 屬性

屬性是資料的鍵值對,您可以將其記錄為單個Span的一部分,比如Http的請求方式,請求狀態碼等。

注意,在Open Telemetry規範中叫 Attributes,在 我們.NET 中叫Tag

using (var activity = activitySource.StartActivity("ActivityName")
{
    await LongRunningOperation().ConfigureAwait(false);
}

public async Task LongRunningOperationAsync()
{
    await Task.Delay(1000).ConfigureAwait(false);

    // Log an attribute containing arbitrary data.
    Activity.Current?.SetTag("http.method", "GET");
}

IsRecording 記錄

IsRecording是Span上的一個標誌,如果返回false,表明該Span已結束,另外,如果你的資料量比較大的話,你需要抽樣採集,比如10%,那麼你也可以手動把這些span設定為false,它不會採集。

注意:在open-telemetryg規範中叫IsRecording,在.NET Core 3.1中是 Recorded,在.NET 5 中是 IsAllDataRequested。

using (var activity = activitySource.StartActivity("ActivityName")
{
    await LongRunningOperation().ConfigureAwait(false);
}

public async Task LongRunningOperationAsync()
{
    await Task.Delay(1000).ConfigureAwait(false);

    // It's possible to optionally request more data from a particular span.
    var activity = Activity.Current;
    if (activity != null && activity.IsAllDataRequested)
    {
        activity.SetTag("http.url", "http://www.mywebsite.com");
    }
}

Trace的語義約定

注意屬性名稱http.method,http.url,我在以上示例中使用了該屬性,因為在open-telemetry規範中已經標準化了某些常用的屬性名稱,標準化常用屬性名稱可以在Jaeger等APM中很好的展示它們,屬性名稱已分類為幾個不同的類別,你可以花點時間看一下:

  • General: 可用於描述不同種類的操作的常規語義屬性
  • HTTP: 客戶端和伺服器的Http呼叫
  • Database: SQL 和 NoSql的呼叫
  • RPC/RMI: 遠端呼叫,比如gRPC等
  • Messaging: 用於與訊息傳遞系統(佇列,釋出/訂閱等)
  • Exceptions: 用於記錄與Span關聯的異常的屬性

Exporting 匯出

有很多用於匯出使用OpenTelemetry收集的資料的外掛,我將在我的下一篇部落格文章中討論有關在ASP.NET Core中使用Open Telemetry的資訊, 可以很方便的處理這些資料,您可以輕鬆地訂閱然後消費OpenTelemetry資料,如下所示:

using var subscriber = DiagnosticListener.AllListeners.Subscribe(
    listener =>
    {
        Console.WriteLine($"Listener name {listener.Name}");

        listener.Subscribe(kvp => Console.WriteLine($"Received event {kvp.Key}:{kvp.Value}"));
    });

跨程式的追蹤

為什麼這些程式會形成呼叫鏈,它們是不同的程式,這個怎麼實現的呢?

這就是W3C跟蹤上下文標準,它定義了一系列HTTP Header,這些Header將有關當前正在記錄的任何跟蹤的資訊從一個程式傳遞到另一個程式,它通過Http的Header來傳遞資訊,規範中定義了兩個HTTP Header:

  • traceparent-包含version,trace-id,parent-id和trace-flags

    • version - 在open-telemetry規範中,它始終是00
    • trace-id - 跟蹤的唯一識別符號。
    • parent-id -作為當前 patent span 的唯一識別符號。
    • trace-flags -當前跟蹤的一組標誌,用於確定是否正在取樣當前跟蹤以及跟蹤級別。
  • tracestate -由一組名稱/值對錶示的特定於供應商的資料。

traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: asp=00f067aa0ba902b7,redis=t61rcWkgMzE

Baggage

與Attributes類似,Baggage是我們可以將資料作為鍵值對新增到跟蹤的另一種方式,不同之處在於,Baggage使用W3C規範中baggage定義的HTTP Header跨程式邊界傳遞,但是Attributes的值資料只在當前Span中可用

baggage: userId=alice,serverNode=DF:28,isProduction=false

使用的話,也有些相同之處:

using (var activity = activitySource.StartActivity("ActivityName")
{
    await LongRunningOperation().ConfigureAwait(false);
}

public async Task LongRunningOperationAsync()
{
    await Task.Delay(1000).ConfigureAwait(false);

    // Log an attribute containing arbitrary data.
    Activity.Current?.AddBaggage("http.method", "GET");
}

它的用途在於,比如說我需要傳遞一個訂單ID,我就可以放到 Baggage 資料中,它在整個請求鏈路中都可以訪問。

總結

.NET團隊對OpenTelemetry非常重視,你可以看到Activity型別在.NET 5 中的增強,並且預設 HttpClient 呼叫時,它會自動傳輸W3C跟蹤上下文HTTP Header, 基於ILogger的統一日誌,也可以很好的收集和OpenTelemetry相容的日誌。

原文作者: Rehan Saeed
原文連結: https://rehansaeed.com/deep-dive-into-open-telemetry-for-net/

最後

歡迎掃碼關注我們的公眾號 【全球技術精選】,專注國外優秀部落格的翻譯和開源專案分享,也可以新增QQ群 897216102

深入研究 .NET 5的開放式遙測

相關文章