此範例會將 Prometheus 用於計量收集、將 Grafana 用於建立儀表板,以及將 Jaeger 用來顯示分散式追蹤。
1. 建立專案
使用 Visual Studio 中的 ASP.NET Core Empty 範本或使用下列 .NET CLI 命令,建立簡單的 Web API 專案:
dotnet new web
2.新增計量和活動定義
下列程式碼會針對呼叫 API 的次數定義新的計量 (greetings.count),以及定義新的活動來源 (OtPrGrYa.Example)。
// Custom metrics for the application
var greeterMeter = new Meter("OtPrGrYa.Example", "1.0.0");
var countGreetings = greeterMeter.CreateCounter<int>("greetings.count", description: "Counts the number of greetings");
// Custom ActivitySource for the application
var greeterActivitySource = new ActivitySource("OtPrGrJa.Example");
3.建立 API 端點
app.MapGet("/", SendGreeting);
async Task<string> SendGreeting(ILogger<Program> logger)
{
// Create a new Activity scoped to the method
using var activity = greeterActivitySource.StartActivity("GreeterActivity");
// Log a message
logger.LogInformation("Sending greeting");
// Increment the custom counter
countGreetings.Add(1);
// Add a tag to the Activity
activity?.SetTag("greeting", "Hello World!");
return "Hello World!";
}
注意
API 定義並未使用任何針對 OpenTelemetry 的具體內容。 其會針對可檢視性使用 .NET API。
4.參考 OpenTelemetry 套件
使用 NuGet 套件管理員或命令列來新增下列 NuGet 套件:
dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore --prerelease
dotnet add package OpenTelemetry.Exporter.Zipkin
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
5.使用正確的提供者設定 OpenTelemetry
var tracingOtlpEndpoint = builder.Configuration["OTLP_ENDPOINT_URL"];
var otel = builder.Services.AddOpenTelemetry();
// Configure OpenTelemetry Resources with the application name
otel.ConfigureResource(resource => resource
.AddService(serviceName: builder.Environment.ApplicationName));
// Add Metrics for ASP.NET Core and our custom metrics and export to Prometheus
otel.WithMetrics(metrics => metrics
// Metrics provider from OpenTelemetry
.AddAspNetCoreInstrumentation()
.AddMeter(greeterMeter.Name)
// Metrics provides by ASP.NET Core in .NET 8
.AddMeter("Microsoft.AspNetCore.Hosting")
.AddMeter("Microsoft.AspNetCore.Server.Kestrel")
// Metrics provided by System.Net libraries
.AddMeter("System.Net.Http")
.AddMeter("System.Net.NameResolution")
.AddPrometheusExporter());
// Add Tracing for ASP.NET Core and our custom ActivitySource and export to Jaeger
otel.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation();
tracing.AddHttpClientInstrumentation();
tracing.AddSource(greeterActivitySource.Name);
if (tracingOtlpEndpoint != null)
{
tracing.AddOtlpExporter(otlpOptions =>
{
otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
});
}
else
{
tracing.AddConsoleExporter();
}
});
此程式碼會使用 ASP.NET Core 檢測,從 ASP.NET Core 取得計量和活動。 其也會針對計量和追蹤分別註冊 Metrics 和 ActivitySource 提供者。
程式碼會針對計量使用 Prometheus 匯出工具,其會使用 ASP.NET Core 來裝載端點,因此您也需要新增:
// Configure the Prometheus scraping endpoint
app.MapPrometheusScrapingEndpoint();
6.執行專案
執行專案,然後使用瀏覽器或 curl 存取 API。
curl -k http://localhost:7275
每次你申請該頁面時,問候次數都會增加。 你可以用相同的基礎網址(路徑 /metrics)存取指標端點。
6.1 記錄輸出
來自程式碼的記錄陳述式是使用 ILogger 的輸出。 根據預設,會啟用主控台提供者,以便將輸出導向至主控台。
有幾種方式可以讓日誌從 .NET 出口:
-
stdout和stderr輸出是由 Kubernetes 等容器系統重新導向至記錄檔。 - 使用與 ILogger 整合的日誌函式庫。 這些函式庫包括 Serilog 和 NLog。
- 使用 OTel 日誌提供者,例如 OTLP 或後面展示的 Azure Monitor 匯出器。
6.2 存取計量
您可以使用 /metrics 端點來存取計量。
curl -k https://localhost:7275/
Hello World!
curl -k https://localhost:7275/metrics
# TYPE greetings_count counter
# HELP greetings_count Counts the number of greetings
greetings_count 1 1686894204856
# TYPE current_connections gauge
# HELP current_connections Number of connections that are currently active on the server.
current_connections{endpoint="127.0.0.1:7275"} 1 1686894204856
current_connections{endpoint="[::1]:7275"} 0 1686894204856
current_connections{endpoint="[::1]:5212"} 1 1686894204856
...
計量輸出是要求端點時計量的快照集。 結果是以 Prometheus 說明格式提供,這是人類可讀取,但 Prometheus 更加了解的格式。 下一個階段將討論該主題。
6.3 存取追蹤
如果查看伺服器的主控台,您會看到主控台追蹤匯出工具的輸出,此匯出工具會以人類可讀取的格式輸出資訊。 這應該會顯示兩個活動,一個來自您的自訂 ActivitySource,另一個則來自 ASP.NET Core:
Activity.TraceId: 2e00dd5e258d33fe691b965607b91d18
Activity.SpanId: 3b7a891f55b97f1a
Activity.TraceFlags: Recorded
Activity.ParentSpanId: 645071fd0011faac
Activity.ActivitySourceName: OtPrGrYa.Example
Activity.DisplayName: GreeterActivity
Activity.Kind: Internal
Activity.StartTime: 2023-06-16T04:50:26.7675469Z
Activity.Duration: 00:00:00.0023974
Activity.Tags:
greeting: Hello World!
Resource associated with Activity:
service.name: OTel-Prometheus-Grafana-Jaeger
service.instance.id: e1afb619-bc32-48d8-b71f-ee196dc2a76a
telemetry.sdk.name: opentelemetry
telemetry.sdk.language: dotnet
telemetry.sdk.version: 1.5.0
Activity.TraceId: 2e00dd5e258d33fe691b965607b91d18
Activity.SpanId: 645071fd0011faac
Activity.TraceFlags: Recorded
Activity.ActivitySourceName: Microsoft.AspNetCore
Activity.DisplayName: /
Activity.Kind: Server
Activity.StartTime: 2023-06-16T04:50:26.7672615Z
Activity.Duration: 00:00:00.0121259
Activity.Tags:
net.host.name: localhost
net.host.port: 7275
http.method: GET
http.scheme: https
http.target: /
http.url: https://localhost:7275/
http.flavor: 1.1
http.user_agent: curl/8.0.1
http.status_code: 200
Resource associated with Activity:
service.name: OTel-Prometheus-Grafana-Jaeger
service.instance.id: e1afb619-bc32-48d8-b71f-ee196dc2a76a
telemetry.sdk.name: opentelemetry
telemetry.sdk.language: dotnet
telemetry.sdk.version: 1.5.0
第一個是您建立的內部自訂活動。 第二個是由 ASP.NET 針對要求所建立的,其中包含 HTTP 要求屬性的標籤。 你會看到兩者有相同的 TraceId,這代表單一交易。 在分散式系統中,追蹤 ID 可用來關聯交易中各服務的追蹤。 ID 是以 HTTP 標頭的形式傳輸。 ASP.NET Core 會在收到要求時指派 TraceId (如果沒有的話)。 根據預設,HttpClient 會在輸出要求上包含標頭。 每個活動都有 SpanId,這是 TraceId 和 SpanId 的組合,專門識別每個活動。
Greeter 活動會透過其 ParentSpanId 成為 HTTP 活動的父代,這會對應至 HTTP 活動的 SpanId。
在稍後的階段中,您會將此資料饋送至Jaeger,以視覺化分散式追蹤。
7.使用 Prometheus 收集計量
Prometheus 是計量集合、彙總和時間序列資料庫系統。 您可以使用每個服務的計量端點對其進行設定,且其會定期抓取值,並將這些值儲存在時間序列資料庫中。 然後,您可以視需要分析和處理它們。
以 Prometheus 格式公開的計量資料是流程計量的時間點快照集。 每次向指標端點提出請求時,它會回報目前的數值。 雖然目前的值很有趣,但相較於歷史值,這些值會變得更有價值,可查看趨勢並偵測值是否異常。 通常,服務會根據一天中的時間或世界事件 (例如假日購物潮) 出現使用高峰。 透過將數值與歷史趨勢比較,你可以判斷這些指標是否異常,或是某項指標隨時間逐漸惡化。
流程不會儲存這些計量快照集的任何歷程記錄。 將該功能新增至流程可能會耗用大量資源。 此外,在分散式系統中,您通常具有每個節點的多個執行個體,因此您希望能夠從所有節點中收集計量,然後將其彙總並與其歷程記錄值進行比較。
7.1 安裝和設定 Prometheus
從 https://prometheus.io/download/ 下載適用於您平台的 Prometheus,並解壓縮下載內容。
看看你正在執行的伺服器輸出頂部,取得 HTTP 端點的埠號。 例如:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7275
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5212
修改 Prometheus YAML 組態檔,以指定 HTTP 抓取端點的連接埠,並設定較低的抓取間隔。 例如:
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: "prometheus"
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
scrape_interval: 1s # poll very quickly for a more responsive demo
static_configs:
- targets: ["localhost:5212"]
啟動 Prometheus,並在輸出中尋找其執行所在的連接埠,通常是 9090:
>prometheus.exe
...
ts=2023-06-16T05:29:02.789Z caller=web.go:562 level=info component=web msg="Start listening for connections" address=0.0.0.0:9090
在瀏覽器中開啟此 URL。 在 Prometheus 的 UI 中,你現在應該可以查詢你的指標。 使用下圖中的醒目提示按鈕來開啟計量總管,其中顯示所有可用的計量。
選取 greetings_count 計量來查看值的圖表。
8.使用 Grafana 建立計量儀表板
Grafana 是一項儀表板產品,可根據 Prometheus 或其他資料來源建立儀表板和警示。
遵循您平台的指示,從 https://grafana.com/oss/grafana/ 下載並安裝 Grafana 的 OSS 版本。 一旦安裝,Grafana 通常就會在連接埠 3000 上執行,因此請在瀏覽器中開啟 http://localhost:3000。 你需要登入;預設的使用者名稱和密碼都是 admin。
在漢堡選單中選擇連接,然後輸入文字 prometheus 以選擇你的端點類型。 選取 [建立 Prometheus 資料來源] 以新增資料來源。
將 Prometheus 伺服器網址設為 http://localhost:9090/,並依適用方式更改埠口。
選取 [儲存並測試] 以驗證設定。
一旦取得成功訊息,您就可以設定儀表板。 請選擇跳出視窗中顯示的 建立儀表板 連結以顯示成功訊息。
選取 [新增視覺效果],然後選擇您剛才新增為資料來源的 Prometheus 資料來源。
儀表板面板設計工具應該會出現。 在畫面的下半部,您可以定義查詢。
選取 greetings_count 計量,然後選取 [執行查詢] 以查看結果。
使用 Grafana,您可以設計複雜的儀表板,追蹤任意數目的計量。
.NET 中的每個計量都可以具有額外的維度,這些維度是可以用來分割資料的機碼值組。 ASP.NET 計量全都配有一些適用於計數器的維度。 例如,來自 current-requests 的 Microsoft.AspNetCore.Hosting 計數器具有下列維度:
| 屬性 | 類型 | 描述 | 範例 | 目前狀態 |
|---|---|---|---|---|
method |
string |
HTTP 要求方法。 |
GET }, |
永遠 |
scheme |
string |
識別已用通訊協定的 URI 配置。 | % | 永遠 |
host |
string |
收到要求的本機 HTTP 伺服器名稱。 | localhost |
永遠 |
port |
int |
收到要求的本機 HTTP 伺服器連接埠。 | 8080 |
如果不是預設值,則新增 (80 用於 http 或 443 用於 https) |
Grafana 中的圖表通常會根據每個唯一維度組合來分割。 維度可以用於 Grafana 查詢,以篩選或彙總資料。 例如,如果您繪製 current_requests 的圖表,則會看到根據每個維度組合分割的值。 若要僅根據主機進行篩選,請新增 Sum 的作業,並使用 host 做為標籤值。
9.使用 Jaeger 的分散式追蹤
在步驟 6 中,您看到分散式追蹤資訊正在公開至主控台。 此資訊會追蹤工作單位和活動。 某些活動是由平台自動建立,例如 ASP.NET 建立的活動,代表要求的處理,而程式庫和應用程式程式碼也可以建立活動。 問候語範例具有 Greeter 活動。 活動會使用 TraceId、SpanId 和 ParentId 標籤相互關聯。
分散式系統中的每個流程都會產生自己的活動資訊串流,且與計量一樣,您需要系統來收集、儲存及相互關聯活動,才能視覺化針對每項交易完成的工作。 Jaeger 是一項開放原始碼專案,可啟用此集合和視覺效果。
從 https://www.jaegertracing.io/download/ 為您的平台下載 Jaeger 的最新二進位散發封存。
然後,將下載項目解壓縮到易於存取的本機位置。 執行 jaeger-all-in-one(.exe) 可執行檔:
./jaeger-all-in-one --collector.otlp.enabled
查看主控台輸出,以尋找其透過 gRPC 接聽 OTLP 流量的連接埠。 例如:
{"level":"info","ts":1686963686.3854616,"caller":"otlpreceiver@v0.78.2/otlp.go:83","msg":"Starting GRPC server","endpoint":"0.0.0.0:4317"}
此輸出會告訴您其正在 0.0.0.0:4317 進行接聽,因此您可以將該連接埠設定為 OTLP 匯出工具的目的地。
為我們的專案開啟 AppSettings.json 檔案,然後新增下列一行,變更連接埠 (如果適用)。
"OTLP_ENDPOINT_URL" : "http://localhost:4317/"
重新啟動問候語流程,以便其可以挑選屬性變更,並開始將追蹤資訊導向至 Jaeger。
現在,您應該能夠從網頁瀏覽器於 http://localhost:16686/ 看到 Jaeger UI。
若要查看追蹤清單,請從 [服務]OTel-Prometheus-grafana-Jaeger 下拉式清單中選取 。 選取一個追蹤應該會顯示該追蹤活動的甘特圖。 按一下每個作業會顯示更多有關活動的詳細資料。
在分散式系統中,您想要將追蹤從所有流程傳送至相同的 Jaeger 安裝,讓其可將整個系統的交易相互關聯。
您可以使應用程式自行進行 HTTP 呼叫,讓應用程式更有趣一點。
在應用程式中新增工廠
HttpClient:builder.Services.AddHttpClient();新增一個端點來進行巢狀問候通話:
app.MapGet("/NestedGreeting", SendNestedGreeting);實作此端點,讓其進行也可以追蹤的 HTTP 呼叫。 在這種情況下,它會以人工迴圈的方式回喚自己(實際上只適用於示範場景):
async Task SendNestedGreeting(int nestlevel, ILogger<Program> logger, HttpContext context, IHttpClientFactory clientFactory) { // Create a new Activity scoped to the method using var activity = greeterActivitySource.StartActivity("GreeterActivity"); if (nestlevel <= 5) { // Log a message logger.LogInformation("Sending greeting, level {nestlevel}", nestlevel); // Increment the custom counter countGreetings.Add(1); // Add a tag to the Activity activity?.SetTag("nest-level", nestlevel); await context.Response.WriteAsync($"Nested Greeting, level: {nestlevel}\r\n"); if (nestlevel > 0) { var request = context.Request; var url = new Uri($"{request.Scheme}://{request.Host}{request.Path}?nestlevel={nestlevel - 1}"); // Makes an http call passing the activity information as http headers var nestedResult = await clientFactory.CreateClient().GetStringAsync(url); await context.Response.WriteAsync(nestedResult); } } else { // Log a message logger.LogError("Greeting nest level {nestlevel} too high", nestlevel); await context.Response.WriteAsync("Nest level too high, max is 5"); } }
這會產生一個更有趣的圖表,其中包含要求的金字塔形狀,因為每一層都會等待上一個呼叫的回應。