選單
GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
4 分鐘閱讀時間 (756 個字)

使用 OpenTracing - Jaeger (BFv3 使用 Dynamic Proxy)

-2021-03-09-8.40.52

前言

在 使用 OpenTracing - Jaeger (AP 整合) 之中,我們定義 OpenTracing 的區塊,然後將需要的資訊寫進去,如下,
var tracer = GlobalTracer.Instance;

const string operationName = "RootDialog-MessageReceivedAsync";

var spanBuilder = tracer.BuildSpan(operationName);

if (tracer.ActiveSpan != null)

    spanBuilder.AsChildOf(tracer.ActiveSpan);

using (var scope = spanBuilder.StartActive(true))

{

    // 其他的程式碼 ...

}
這些程式碼跟 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 物件,如下,
public class OpenTracingInterceptor : IInterceptor
{
    IAsyncInterceptor _asyncInterceptor;
    public OpenTracingInterceptor(IAsyncInterceptor asyncInterceptor)
    {
        _asyncInterceptor = asyncInterceptor;
    }
    public void Intercept(IInvocation invocation)
    {
        _asyncInterceptor.ToInterceptor().Intercept(invocation);
    }
}

public class OpenTracingInterceptorAsync : IAsyncInterceptor
{
    public void InterceptAsynchronous(IInvocation invocation)
    {
        invocation.ReturnValue = InternalInterceptAsynchronous(invocation);
    }
    private async Task InternalInterceptAsynchronous(IInvocation invocation)
    {
        var tracer = GlobalTracer.Instance;
        string operationName = invocation.Method.Name;
        var spanBuilder = tracer.BuildSpan(operationName);
        if (tracer.ActiveSpan != null)
        {
            spanBuilder.AsChildOf(tracer.ActiveSpan);
        }
        using (var scope = spanBuilder.StartActive(true))
        {
            // Step 1. Do something prior to invocation.
            invocation.Proceed();
            var task = (Task)invocation.ReturnValue;
            await task;
            // Step 2. Do something after invocation.
        }
    }
    public void InterceptAsynchronous<TResult>(IInvocation invocation)
    {
        invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation);
    }
    private async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation)
    {
        var tracer = GlobalTracer.Instance;
        string operationName = invocation.Method.Name;
        var spanBuilder = tracer.BuildSpan(operationName);
        if (tracer.ActiveSpan != null)
        {
            spanBuilder.AsChildOf(tracer.ActiveSpan);
        }
        TResult result;
        using (var scope = spanBuilder.StartActive(true))
        {
            // Step 1. Do something prior to invocation.
            invocation.Proceed();
            var task = (Task<TResult>)invocation.ReturnValue;
            result = await task;
            // Step 2. Do something after invocation.
        }
        return result;
    }
    public void InterceptSynchronous(IInvocation invocation)
    {
        var tracer = GlobalTracer.Instance;
        string operationName = invocation.Method.Name;
        var spanBuilder = tracer.BuildSpan(operationName);
        if (tracer.ActiveSpan != null)
        {
            spanBuilder.AsChildOf(tracer.ActiveSpan);
        }
        using (var scope = spanBuilder.StartActive(true))
        {
            // Step 1. Do something prior to invocation.
            invocation.Proceed();
            // Step 2. Do something after invocation.
        }
    }
}

向 AUTOFAC 註冊 及 要使用的 INTERCEPTOR

再來就是跟 Autofac 註冊及說明類別要用 EnableClassInterceptors() 或是 EnableInterfaceInterceptors(),如果使用 EnableClassInterceptors 的話,該類別的 Method 必需為 virtual methods 哦!
//autofac codes...

builder.RegisterType<OpenTracingInterceptor>();

builder.RegisterType<OpenTracingInterceptorAsync>().As<IAsyncInterceptor>();

builder.RegisterType<RootDialog>()

    .AsSelf()

    .EnableClassInterceptors()

    .InterceptedBy(typeof(OpenTracingInterceptor));



builder

    .RegisterType<ShowMenuActionStrategy>()

    .Named<IActionStrategy>(ActionTypes.ShowMenuAction.ToString())

    .InstancePerLifetimeScope()

    .EnableClassInterceptors()

    .InterceptedBy(typeof(OpenTracingInterceptor));

//autofac codes...
  • 註: InterceptedBy 也可以改用 宣告式的。例如在 Class 加入 [Intercept(typeof(OpenTracingInterceptor))] 設定。

運行結果

有了 AsyncInterceptor 設定之後,透過 Jaeger UI 來查,就可以看到該 Method 真正的花費時間,如下,

結論

透過 Interceptor 的方式,可以讓大部份的 Class 都可以加入 OpenTracing 記錄之中,如果在過程中,發現某個 Span 需要多 Log 資料的話,就可以到該 Span 的 Method 去加入更詳細的資訊。

補充說明

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 ConventionsAutofac Type Interceptorsgithub Castle.Core.AsyncInterceptor
5個常見開發工程師使用Open Source會犯的錯誤
使用 OpenTracing - Jaeger (AP整合)

相關文章