# Jaeger搭配Docker來追蹤NET 7
### 前情提要 先介紹一下Jaeger、OpenTelemetry
#### 1. Jaeger
Jaeger 是一種可用於監控和解決互連軟體元件 (稱為微型服務) 問題的軟體。多個微型服務可彼此通訊,以完成單一軟體功能。開發人員使用 Jaeger 來視覺化這些微型服務互動中的事件鏈結,以便在出現問題時隔離問題。Jaeger 也稱為 Jaeger Tracing,因為其可透過一系列的微型服務互動來跟蹤或追蹤請求的路徑。
- Jaeger Client:
Jaeger client的Library, 有對OpenTelemetry和OpenTracing進行了實現.
- Jaeger Agent
也是Sidecar模式的實現, 負責把client透過UDP發出的spans給批量推送到Collector上.
主要是為了屏蔽client對於collector的路由實做細節.
- Jaeger Collector
收集Spans, 把Span經過驗證、轉換、索引並且寫入DB內,Colletcor能設定Sampling採樣邏輯, 根據Sampling的設定進行收集和處理
因為這組件是無狀態的, 所以可以建立很多個Collector加速寫入到DB,DB支援了Cassandra、Elasticsearch、Kafka.
官方建議是用Cassandra, 原因有2. Cassanda是一個K-V資料庫, 對於用TracdID來搜尋的場景效率很高. 且寫入吞吐量相當好,但若是為了分析查詢, 還是Elasticsearch較好.
- Jaeger Query
接收查詢請求, 然後從DB中檢索, 並透過UI展示, Jaeger Query也是無狀態的, 所以可以啟動多個實例.
#### 2. OprnTelemetry
OpenTelemetry 為遙測資料建立單一開放式標準,並透過該技術收集及匯出雲端原生應用程式的資料,藉此監控及分析資料。
- Span (跨度):Span 是 OpenTelemetry 中的一個基本概念,代表應用程序中的一個操作或事件。例如,HTTP 請求、資料庫查詢等。Span 包含了一些元數據,如開始時間、結束時間、標籤(tags)、事件(events)等。
- Tracer (追蹤器):Tracer 是 OpenTelemetry 提供的用於創建和管理 Span 的介面。通過 Tracer,你可以在應用程式中創建新的 Span 並將它們連接起來,以形成分散式追蹤的整體視圖。
- Context (上下文):Context 是 OpenTelemetry 中的一個概念,用於將追蹤信息(如 Span)傳遞給應用程序中的不同部分。Context 包含了一些關鍵信息,例如追蹤 ID、span ID 等。在應用程式中,Context 可以透過不同的方式(例如 HTTP headers)傳遞。
- Instrumentation (儀器化):Instrumentation 是 OpenTelemetry 中的一個模組,用於自動捕獲應用程序中的事件並創建相應的 Span。這樣的自動化有助於減少手動儀器化的工作。
- Exporter (導出器):Exporter 是 OpenTelemetry 中的一個組件,用於將收集的追蹤數據輸出到不同的目的地,如後端存儲、分析工具等。常見的 Exporter 包括將數據發送到 Zipkin、Jaeger、Prometheus 等。
- Sampler (取樣器):Sampler 是一個用於確定是否應該捕獲特定 Span 的組件。它可以幫助控制追蹤數據的量,以防止過度膨脹。Sampler 可以按比例抽樣或者根據其他策略進行抽樣。
- Attribute (屬性):Attribute 是 Span 或其他 OpenTelemetry 元件上的一對鍵值對,用於存儲與該元件相關的任意額外信息。這些屬性可以用來細化、標記和描述 Span。
### 建立基本架構 - Jaeger all-in-one持久化
[參考資料](https://jckling.github.io/2021/05/10/Jaeger/Jaeger%20+%20Elasticsearch%20+%20Kibana/index.html)
1. 先在docker中建立簡易All in one 版本 Jaeger 搭配 Elasticsearch、Kibana
建立docker-compose,執行docker-compose後initial_jaeger在initial完之後就會關閉了
```yml=
version: '3'
services:
jaeger:
image: jaegertracing/all-in-one
container_name: jaeger
environment:
- SPAN_STORAGE_TYPE=elasticsearch
- COLLECTOR_ZIPKIN_HOST_PORT=:9411
- ES_SERVER_URLS=http://elasticsearch:9200
- ES_TAGS_AS_FIELDS_ALL=true
ports:
- "5775:5775/udp"
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
- "16686:16686"
- "14268:14268"
- "14250:14250"
- "9411:9411"
- "4317:4317"
depends_on:
- elasticsearch
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
container_name: elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
- "9300:9300"
kibana:
image: docker.elastic.co/kibana/kibana:7.12.1
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- "5601:5601"
depends_on:
- elasticsearch
networks:
jaeger-net:
```
### 針對.Net進行撰寫
1. 請先安裝NuGet套件,利用dotnet cli安裝,注意套件版本
``` =
dotnet add package OpenTelemetry --version 1.7.0
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol --version 1.7.0
dotnet add package OpenTelemetry.Extensions.Hosting --version 1.7.0
dotnet add package OpenTelemetry.Instrumentation.AspNetCore --version 1.7.0
```
2. 在Net7專案中的新增JaegerHelper.cs,建立一個Jaeger連線(JaegerHelper請用Singleton)
```csharp=
tracer = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Service_Name"))
.AddAspNetCoreInstrumentation(o =>
{
o.EnrichWithHttpRequest = (activity, httpRequest) =>
{
activity.SetTag("requestProtocol", httpRequest.Protocol);
};
o.EnrichWithHttpResponse = (activity, httpResponse) =>
{
activity.SetTag("responseLength", httpResponse.ContentLength);
};
o.EnrichWithException = (activity, exception) =>
{
activity.SetTag("exceptionType", exception.GetType().ToString());
};
})
.AddSource("*")
.AddOtlpExporter(opt =>
{
opt.Endpoint = new Uri(JaegerUri);
opt.Protocol = OtlpExportProtocol.Grpc;
})
.Build();
```
3. 在需要追蹤的地方呼叫Singleton JaegerHelper
```csharp=
using (var childSpan = JaegerHelper.Instance.GetInstance().tracer.GetTracer("Service_Name").StartActiveSpan("MethodName"))
{
childSpan.SetAttribute("Result", "OK");
//可放需要新增的Attribute
}
```
4. 用DispatchProxy來攔截需要被追蹤的程式
```csharp=
public class InterceptorProxy<T> : DispatchProxy
{
public T Target { get; set; }
JsonSerializerOptions options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve };
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
try
{
if (Array.IndexOf(MethodFilter, targetMethod.Name) < 0)
{
using (var childSpan = JaegerHelper.Instance.GetInstance().tracer.GetTracer("Service_Name").StartActiveSpan(targetMethod.Name))
{
object result = targetMethod.Invoke(Target, args);
childSpan.SetAttribute("Programs", Target.ToString());
childSpan.SetAttribute("Parameter", JsonSerializer.Serialize(args, options));
if (result is Task resultTask)
{
resultTask.Wait(); // 等待非泛型 Task 的完成
if (resultTask.GetType().GetProperty("Result") is PropertyInfo resultProperty && resultProperty.PropertyType != typeof(void))
{
object taskResult = resultProperty.GetValue(resultTask);
childSpan.SetAttribute("Result", JsonSerializer.Serialize(resultTask, options));
return result;
}
}
else
{
return result;
}
}
}
return targetMethod.Invoke(Target, args);
}
catch (Exception ex)
{
return ex;
}
finally
{
}
}
}
```
5. 隊要被追蹤的程式進行Proxy,呼叫Proxy後的程式才會被DispatchProxy攔截到
```csharp=
var ProxyName = DispatchProxy.Create<Interface, InterceptorProxy<Interface>>();
((InterceptorProxy<Interface>)ProxyName).Target = class;
//說明 第一行是在建立一個由Interface去Proxy的變數,注意只有Interface才能被Proxy所以要追蹤class的話請讓他繼承一個interface。
//第二行是將Proxy後的變數設定一個指向目標的class當執行到這個變數時他會去找它的Target的方法,此處Target就是你的class。
```
### 最後結果
1. 完成上述步驟後應該有個雛型了,這邊的追蹤方法偏向對現有專案進行追蹤,網路上有許多簡單的追蹤方法這邊就不多做說明。

2. 各追蹤內容,可以看到相較簡單的設置這邊多了幾層的span讓他可以追蹤到更下層。
