前言
在 使用 OpenTracing - Jaeger (AP 整合) 之中,我們定義 OpenTracing 的區塊,然後將需要的資訊寫進去,如下,
1 | var tracer = GlobalTracer.Instance; |
這些程式碼跟 Exception Handle 類似,所以我們可以透過 Dynamic Proxy 的方式來包裝起來,讓程式開發人員專注在程式的邏輯之中,不用去理會這煩雜的程式碼。
實作
加入 Autofac.Extras.DynamicProxy2 套件
我們可以依 Autofac Type Interceptors 從 Nuget 套件中加入 Autofac.Extras.DynamicProxy2 ,如下,
建立 Interceptor 物件
依 Autofac Type Interceptors 的範例,建立實作 Interceptor 的物件,然後將 OpenTracing 的程式碼加進去,然後註冊要使用的 Interceptor ,結果從 Jaeger UI 看到的圖卻無法呈現真正的時間,如下,
這是因為在 C# 中大多的程式是用 async/await ,所以同步的 Interceptor 無法取得真正的執行時間。
為了 Handle async/await 的 Method ,我們可以再加入 Castle.Core.AsyncInterceptor 套件,如下,
所以我們就可以建立同步及非同步的 Interceptor 物件,如下,
1 | public class OpenTracingInterceptor : IInterceptor |
向 Autofac 註冊 及 要使用的 Interceptor
再來就是跟 Autofac 註冊及說明類別要用 EnableClassInterceptors() 或是 EnableInterfaceInterceptors(),如果使用 EnableClassInterceptors 的話,該類別的 Method 必需為 virtual methods 哦!
1 | //autofac codes... |
- 註: InterceptedBy 也可以改用 宣告式的。例如在 Class 加入 [Intercept(typeof(OpenTracingInterceptor))] 設定。
建立物件
因為在 Autofac 中設定 EnableClassInterceptors ,所以取得物件時,需要從 Autofac Container 取得,所以在取得 RootDialog 時,原本是直接 new 它,現在要改成如下,
1 | await Conversation.SendAsync(activity, () => Conversation.Container.Resolve<Dialogs.RootDialog>()); |
運行結果
有了 AsyncInterceptor 設定之後,透過 Jaeger UI 來查,就可以看到該 Method 真正的花費時間,如下,
結論
透過 Interceptor 的方式,可以讓大部份的 Class 都可以加入 OpenTracing 記錄之中,如果在過程中,發現某個 Span 需要多 Log 資料的話,就可以到該 Span 的 Method 去加入更詳細的資訊。
另外,在接收時的 MessagesController 的入口點,我們還是需要手動去加入 Extract 的 Http Header 資料,來讓整個 Path 可以串起來。
補充說明
OpenTracing basics
一個 Trace 通常是一個 Request -> Response 的過程(例如,使用者登入->驗證服務->取得帳號資訊->可使用功能 …->畫面呈現),所以它會包含一個或多個 Span ,這些 Span 會有著一致的 Tracer Id。
一個 Span 代表一個單元的工作,例如 Client call Server,發送一個 Query 到 DB。
每個 Span,會有著唯一的 Id,它們之間可能會有著上、下關係(ChildOf)或是 FollowsFrom。
如果一個 Span 的 Parent Span 是空的,我們會稱它為 Root Span。
Span 通常還會包含 operation name, start 及 end 的時間,也可 Tag 一些資訊及記錄錯誤 、 Log。
所以我們可以透過這些資訊來發現效能或是錯誤的地方。
參考資料
Tag 命名 Semantic Conventions
Autofac Type Interceptors
github Castle.Core.AsyncInterceptor