只有累積,沒有奇蹟

2024年3月24日 星期日

[NET] .NET Aspire From 0 To 1 : 可觀測性儀錶板

前言
上一篇與大家介紹關於 .NET Asipre 方案基本組成與概念,今天要介紹自己覺得 .NET Aspire 框架中重要的儀表板功能,此系列文目前會分為三篇分別是
  • .NET Aspire 快速入門 : 介紹 .NET Aspirea 的基礎知識,包括其設計理念、主要功能
  • .NET Aspire 可觀測性儀錶板 : 解釋 .NET Aspire 如何提供應用程式的可觀測性,包括如何蒐集遙測數據、監控和日誌記錄
  • .NET Aspire 整合 : 說明 .NET Aspire 如何與其他微軟雲服務(如 Azure)整合
希望可以這系列文的介紹與分享,讓有興趣的開發夥伴們可以更進一步了解對 .NET Aspire 框架的認識,與若對於上述內容有問題或是不清楚的地方,歡迎提出來一起討論。


探索 .NET Aspire 儀錶板
在 Visual Studio 執行後即可以看到 .NET Aspire Dashboard,Dashboard 是透過 Blazor 所撰寫的應用程式,開發者可以在 .NET Aspire 儀表板頁面顯示正在執行的應用程式,透過此儀表板可以看到應用程式的各項資訊,包括日誌 (Logs)、遙測數據、Metrics 和環境配置等,提供開發者對於應用程式的狀態更進一步的理解與掌握即時資訊。
Resource
如同前一篇介紹的在專案 Apphost 中所設定 Redis、apiService 跟 webFrontend 名字,三個各自型別 (Type) 也有顯示在畫面的欄位上,其中 Redis 是透過 docker 啟動,開啟 Docker Desktop Dashboard 可以看到對應到相同的資料與狀態,分別是 in use 跟 Running,還有應用程式啟動時間與位置資訊
Logs
點擊 Logs 欄位可以顯示應用程式的 log 資訊,以 webfrondend 為例會看到 Log 內容是應用程式相關的 console logs 資訊,還包含 log 的 level 與 message 與 log 紀錄的時間。另外 Watch logs 旁邊還可以進行專案的切換,可以更方便的看到不同 instance 的 console log 重要資訊
Detail
在 .NET Aspire 會自動解析應用程式相關的 endpoint 與環境變數等 config 設定資訊,在將其資訊呈現在 Dashboard 頁面,像是跟第三方串接時串接的 endpoint url、feature toogle 的開關 flag 設定,預設這些內容是隱藏看不到的,需要在點擊右方的 icon 才看到的。這邊 OTEL 開頭的到專案中 config 檔可以發現找不到這些相關的設定參數,OTEL 是與 opentelemetry 相關的設定資訊,這些資訊說明晚點會在後面介紹。

執行應用程式
前面介紹了 Resources 與 Console 之後,下面三個重要功能 Structure、Traces 與 Metrics 是可觀測性常提到的三大支柱,在 .NET Aspire 之所以可以輕鬆地蒐集這些資訊,主要是因為在 .NET 8 底層及組件實做 OpenTelemetry 的標準並提供相關的 API,並在應用程式啟動時設定蒐集相關遙測數據資料,才可以在 .NET Aspire dashboard 中讓開發者看到相關的資訊。因此在介紹 Structure、Traces 與 Metrics 前要先透過執行應用程式 API 與功能,才可以蒐集相關遙測數據在 Dashboard 上觀察到應用程式執行的狀況。
透過 Resources 中 webfrontend 點擊 endpoint 可以看到上面畫面,這是一個簡單的畫面資訊是透過後端載入回傳當天天氣相關的資料。我們可以重新整理畫面試著多呼叫 API,測試中可以發現有設定 cache 功能將資料 cache 10 秒後失效。接著我們回到功能看各數據收集的狀況。

Structured logs
Structured logs 功能可以查看應用程式所記錄的 Log 資訊,像是 log 的來源 (Resources)、Log Level 等級、發生的時間與 log 內容等重要資訊,以下是該功能中自己覺得實用的功能
  • 專案切換 : 進行 resources 的切換,當應用程式變多時這個功能就很需要
  • 搜尋 : 針對 log message 內容進行搜尋的動作,方便快速過濾不必要的資訊
  • 篩選 : 提供 log level 的篩選
  • Trace : 切換畫面到 Trace,找到此 request 相關的請求 log
  • Log entry detail : log 詳細內容,將所有 log 相關的 attribute 資訊全部呈現在畫面上
Traces
Traces 可以看到該請求所相依的服務或是依賴 service,當服務越切越細時當異常發生我們要找到其中哪個服務異常就會是一個重要的議題,舉例來說我們今天系統中有個登入 login 服務,系統背後可能經過自己系統的服務、第三方 SSO 像是 github、微軟 AAD 等第三方系統整合,最後再將資訊整合到既有系統的 Auth 服務,在將整體登入的結果回到前端的 Client Device,即使各應用程式有紀錄 log 但彼此這些 log 是沒有關聯,在盤查問題實就會花費很多時間,Trace 就是透過 TraceID 來將各自的 log 資訊串聯再一起,讓開發者可以透過類似上列畫面更快的定位可能發生異常的服務。
上圖是 sample 專案中 /weather 請求作為範例進行簡單介紹
  • 請求資訊 : 該請求資訊所發生的時間、花費多久 (Duration)、經過多少服務 (Resource) 及經過多少個 Span
  • /weather API 的請求路徑,可以看到在此請求路徑(Span)中的 endpoint 及 httpmethod 方法
  • 呈現這個請求中每個 Span 的細節,各自占比以及所花費的時間,並以 Dashboard 方式呈現讓大家更容易理解可能的瓶頸點
  • Detail : 分別記錄 Span、Application 與 Event 三個資訊的 log 資訊,Span 是紀錄請求類的相關資訊像是 spanID、http method、使用的 Url 及 port。Application 類則是該 service 相關資訊,像是服務名稱、instance ID (多個服務時方便識別)、收集遙測數據實使用的 sdk Name / language 及版本,如下圖所示。
Metrics
Metrics 可以做為監控應用程式即時的狀況,在 .NET Aspire 中會依據應用程式列出需要的 metrics 指標內容
以上圖為例是查看 http.client.request.durtion 的資訊,詳細資訊可以參考 MSDN ASP.NET Core metrics 了解更多資訊。

Dashboard 實作原理
.NET Aspire 提供可視化的 dashboard,讓開發人員在開發雲端應用程式時變得更簡單,那麼在背後是如何做到的呢 ? 我們可以看到 AspireSampleApp.ApiService 與 AspireSampleApp.Web 兩個專案中的啟動呼叫 builder.AddServiceDefaults(); 方法,在程式一開始透過 ConfigureOpenTelemetry 來蒐集應用程式相關的遙測數據資料,其內容如下
public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
{
    builder.Logging.AddOpenTelemetry(logging =>
    {
        logging.IncludeFormattedMessage = true;
        logging.IncludeScopes = true;
    });

    builder.Services.AddOpenTelemetry()
        .WithMetrics(metrics =>
        {
            metrics.AddAspNetCoreInstrumentation()
                   .AddHttpClientInstrumentation()
                   .AddProcessInstrumentation()
                   .AddRuntimeInstrumentation();
        })
        .WithTracing(tracing =>
        {
            if (builder.Environment.IsDevelopment())
            {
                // We want to view all traces in development
                tracing.SetSampler(new AlwaysOnSampler());
            }

            tracing.AddAspNetCoreInstrumentation()
                   .AddGrpcClientInstrumentation()
                   .AddHttpClientInstrumentation();
        });

    builder.AddOpenTelemetryExporters();

    return builder;
}  
  

OpenTelemetry 是什麼
由於在 Dashboard 分為三塊分別是 Logging、Traces 與 Metrics 三者,因此在上述程式碼的說明我分為三部分來說明,首先先來看一下程式方法中提到的 OpenTelemetry。如果提到可觀測性 Observability 一定會討論到 OpenTelemetry 這套收集遙測數據的標準,OpenTelemetry 是什麼呢
OpenTelemetry 提供單一的開放原始碼標準和技術組合,可從雲端原生應用程式和基礎架構中擷取及匯出指標、追蹤記錄和記錄檔。
目的是為了解決雲端原生應用程式在分散式系統中收集應用程式的遙測據據資料問題,像是指標和追蹤在過去有不同種蒐集方式,透過 OpenTelemetry 統一的標準,可以簡化及更有效地蒐集需要的資料內容,彙整到相關的技術廠商或是 open Source 專案,各程式語言可以透過 OpenTelemetry 所定義的標準將其其程式語言的方法實做出來,提供給各自的開發者使用,如果想要了解更多關於 OpenTelemetry 介紹,可以參考我之前在 Will 保哥粉絲團直播分享的 初探 OpenTelemetry 工具組:蒐集遙測數據的新標準,這裡就不在多加介紹


Logging
程式碼中 3~7 行,設定 logging 相關資訊主要執行程式碼如下
builder.Logging.AddOpenTelemetry(logging =>
{
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
});  
在 logging 中主要透過程式碼中的 Logging.AddOpenTelemetry 往下查看是使用底層 AddOpenTelemetryInternal 方法,透過 Visual Studio IDE 查看定義 sourece code 來探討背後做了哪些事情
private static ILoggingBuilder AddOpenTelemetryInternal(
	ILoggingBuilder builder,
	Action? configureBuilder,
	Action? configureOptions)
{
	Guard.ThrowIfNull(builder);

	builder.AddConfiguration();

	var services = builder.Services;

	// Note: This will bind logger options element (eg "Logging:OpenTelemetry") to OpenTelemetryLoggerOptions
	RegisterLoggerProviderOptions(services);

	services.AddOpenTelemetrySharedProviderBuilderServices();

	if (configureOptions != null)
	{
		// Note: Order is important here so that user-supplied delegate
		// fires AFTER the options are bound to Logging:OpenTelemetry
		// configuration.
		services.Configure(configureOptions);
	}

	var loggingBuilder = new LoggerProviderBuilderBase(services).ConfigureBuilder(
		(sp, logging) =>
		{
			var options = sp.GetRequiredService>().CurrentValue;

			if (options.ResourceBuilder != null)
			{
				logging.SetResourceBuilder(options.ResourceBuilder);

				options.ResourceBuilder = null;
			}

			foreach (var processorFactory in options.ProcessorFactories)
			{
				logging.AddProcessor(processorFactory);
			}

			options.ProcessorFactories.Clear();
		});

	configureBuilder?.Invoke(loggingBuilder);

	services.TryAddEnumerable(
		ServiceDescriptor.Singleton(
			sp => new OpenTelemetryLoggerProvider(
				sp.GetRequiredService(),
				sp.GetRequiredService>().CurrentValue,
				disposeProvider: false)));

	return builder;

#if NET6_0_OR_GREATER
	[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "OpenTelemetryLoggerOptions contains only primitive properties.")]
	[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "OpenTelemetryLoggerOptions contains only primitive properties.")]
#endif
	static void RegisterLoggerProviderOptions(IServiceCollection services)
	{
		LoggerProviderOptions.RegisterProviderOptions(services);
	}
}    
上述程式碼重點是在 .NET 程式中與 OpenTelemetry 進行配置日誌紀錄,通過 services.AddOpenTelemetrySharedProviderBuilderServices() 向 DI 加上 OpenTelemetry 相關服務,並在 RegisterLoggerProviderOptions(services) 綁定 OpenTelemetryLoggerOptions,使用 Singleton 方式新增到 service 中 (IServiceCollection)。

Metrics
程式碼 9~16 行,是用來設定 Metrics 相關資訊主要執行程式碼如下
builder.Services.AddOpenTelemetry()
	.WithMetrics(metrics =>
	{
		metrics.AddAspNetCoreInstrumentation()
			   .AddHttpClientInstrumentation()
			   .AddProcessInstrumentation()
			   .AddRuntimeInstrumentation();
	})
AddOpenTelemetry() 是 IServiceCollection 的擴充方法,在這擴充方法中允許在 IServiceCollection 的 instance 直接使用,並在 service 中新增 TelemetryHostedService 的服務 (DI Lifecycle 同樣為 Singleton),最後方法回傳 OpenTelemetryBuilder 實例。並調用其 WithMetrics 方法,我們來看看該方法做了哪些事情
/// 
/// Adds metric services into the builder.
/// 
/// 
/// Notes:
/// 
/// This is safe to be called multiple times and by library authors.
/// Only a single  will be created for a given
/// .
/// This method automatically registers an  named 'OpenTelemetry' into the .
/// 
/// 
/// The supplied  for chaining
/// calls.
public OpenTelemetryBuilder WithMetrics()
    => this.WithMetrics(b => { });

/// 
/// Adds metric services into the builder.
/// 
/// 
/// 
/// configuration callback.
/// The supplied  for chaining
/// calls.
public OpenTelemetryBuilder WithMetrics(Action configure)
{
    OpenTelemetryMetricsBuilderExtensions.RegisterMetricsListener(
        this.Services,
        configure);

    return this;
}
透過上述代碼我們可以得知下列資訊
  • WithMetrics 是 OpenTelemetryBuilder 的擴充方法,用於在 .NET 應用程式中加上 Metrics 中的標準服務 (Builder)
  • 方法一的 WithMetrics() 沒有參數,第二個 WithMetrics(Action configure) 接收一個參數 configure,用於將 Metrics 度量標準服務添加到建構器中
  • 內部調用 RegisterMetricsListener 來執行實際的註冊跟配置

接者在回到 sample 專案程式碼中可以看到 WithMetrics 有四個設定值分別是 AddAspNetCoreInstrumentationAddHttpClientInstrumentationAddProcessInstrumentationAddRuntimeInstrumentation,透過上述程式碼可以蒐集應用程式的內建指標包括 Process、Memory、GC、HttpClient 與伺服器相關指標等資訊。提供開發者可以使用簡單幾行程式簡化啟用所有內建重要指標的過程,因此可以呼應上面在介紹 .NET Aspire Dashboard 時為何可以蒐集到這麼多與應用程式相關的 metrics 資訊,就是在這段程式碼所設定的才可以在 dashboard 看到。 如果對於 ASP.NTE Core Metrics 有興趣探索可以參考 built in metrics aspnetcore

Traces
程式碼 17~28 行,是用來設定 Traces 相關資訊主要執行程式碼如下
.WithTracing(tracing =>
{
    if (builder.Environment.IsDevelopment())
    {
        // We want to view all traces in development
        tracing.SetSampler(new AlwaysOnSampler());
    }

    tracing.AddAspNetCoreInstrumentation()
           .AddGrpcClientInstrumentation()
           .AddHttpClientInstrumentation();
});
並調用其 WithTracing 方法,我們來看看該方法做了哪些事情
/// 
/// Adds tracing services into the builder.
/// 
/// 
/// Note: This is safe to be called multiple times and by library authors.
/// Only a single  will be created for a given
/// .
/// 
/// The supplied  for chaining
/// calls.
public OpenTelemetryBuilder WithTracing()
    => this.WithTracing(b => { });

/// 
/// Adds tracing services into the builder.
/// 
/// 
/// 
/// configuration callback.
/// The supplied  for chaining
/// calls.
public OpenTelemetryBuilder WithTracing(Action configure)
{
    Guard.ThrowIfNull(configure);

    var builder = new TracerProviderBuilderBase(this.Services);

    configure(builder);

    return this;
}
  
與 withMetrics 相似,兩者都是 OpenTelemetryBuilder 的公開擴充方法。並且都是一個方法沒參數,第二個方法提供 config 設定參數,在 withTraces 中會給特定的 IServiceCollection 建立 TracerProvider。

在回到前面程式碼,預設會希望在開發環境自動將 trace 資料蒐集起來,方便開發者在測試環境時可以捕捉相關資訊,因此有類似判斷當符合條件時將採集器設定為 AlwaysOnSampler
  • AddAspNetCoreInstrumentation : 加上與 ASP.NET Core 相關的 tracing 資訊,這有助於開發者追蹤 Web 應用程式的請求和回應。
  • AddGrpcClientInstrumentation : 加上 gRPC 使 gRPC 呼叫可以被追蹤。
  • AddHttpClientInstrumentation : 加上 HttpClient 相關的 tracing 資訊,它有助於追蹤 HTTP 客戶端請求。

Exporters
上面提到很多蒐集遙測數據資料的設定與方法,最後這些資料會蒐集到何處呢 ? 在程式後面第 30 行透過 builder.AddOpenTelemetryExporters() 方法設定 OpenTelemetry 加上一種或是多種的導出器(Exporter),設定完後將蒐集到的各項資訊透過 config 設定發送到各個後端系統,例如 Azure Monitor、 Prometheus、Jaeger、Zipkin、Elasticsearch、Grafana 等。從 source code 可以看到蒐集位置是透過 config 的 OTEL_EXPORTER_OTLP_ENDPOINT,我們可以在透過 dashboard 中的 resource 查看此 config 設定位置為何。
補充 : 如果有需要調整可以過 config 中的設定來修改。

小結
以上快速介紹了關於 .NET Aspire 提供好用的框架 dashboard,除了功能介紹之外也一起探索了其背後關於可觀測性三支柱 logging、Tracing、Metrics 在 .NET Aspire 的實現背後原理與程式碼說明。相信透過今天的文章各位夥伴對於 .NET Aspire dashboard 有更進一步的理解,身為開發者的我們在了解後相信也可以在自己所開發的應用程式中加上相關好用的功能,但 .NET Aspire 與 .NET 8 對於 Cloud Native 所提供的強大功能不僅於如此,在下篇文章我們將再繼續介紹其他好玩的功能與講解背後程式碼原理,happy Coding !

參考
.NET Aspire documentation (Preview)

0 意見:

張貼留言

Copyright © m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com