コンテンツにスキップ

extract + TextMapAdapter を使用したカスタム traceId の実装


作者: 刘锐

はじめに

特定のシナリオでは、コードによってカスタム traceId を実装する必要があります。

実装の考え方:tracer.extract を使用して SpanContext を構築し、構築した SpanContext を上位ノード情報として、asChildOf(SpanContext) を使用して現在のスパンを構築します。

TraceId のパラメータ定義

tracer.extract で SpanContext を構築する際、内部では ContextInterpreter が解析を行い、対応する traceId と spanId を取得します。ContextInterpreter の実装コードの一部は後述します。

プロパゲーター

ddtrace はいくつかの伝搬プロトコルをサポートしており、プロトコルごとに traceId のパラメータ名が異なります。
Java の場合、ddtrace は 2 つの伝搬プロトコルをサポートしています。

  • Datadog:デフォルトの伝搬プロトコル
  • B3:B3 伝搬は、ヘッダー “b3” および “x-b3-” で始まるヘッダーの仕様です。これらのヘッダーはサービス間のトレースコンテキスト伝搬に使用されます。B3 には 2 つの方式があります。
    • B3SINGLE(B3_SINGLE_HEADER):ヘッダーのキーは b3
    • B3(B3MULTI):ヘッダーのキーは x-b3-

Datadog プロパゲーターを使用してカスタム traceId を実装

Datadog プロパゲーターを有効にする

-Ddd.propagation.style.extract=Datadog
-Ddd.propagation.style.inject=Datadog

メカニズムのソースコード説明

ddtrace はデフォルトで Datadog を伝搬プロトコルとして使用し、インターセプターは DatadogContextInterpreter です。そのコードの一部を以下に示します。

    public boolean accept(String key, String value) {
        case 'x':
            if ("x-datadog-trace-id".equalsIgnoreCase(key)) {
                classification = 0;
            } else if ("x-datadog-parent-id".equalsIgnoreCase(key)) {
                classification = 1;
            } else if ("x-datadog-sampling-priority".equalsIgnoreCase(key)) {
                classification = 3;
            } else if ("x-datadog-origin".equalsIgnoreCase(key)) {
                classification = 2;


        ....

        switch(classification) {
            case 0:
                this.traceId = DDId.from(firstValue);
                break;
            case 1:
                this.spanId = DDId.from(firstValue);
                break;
            case 2:
                this.origin = firstValue;
                break;
            case 3:
                this.samplingPriority = Integer.parseInt(firstValue);
                break;
        ....
    }

コード実装

    /***
     * カスタム traceId 関連情報を設定し、カスタムトレースを実装します
     * @param traceId
     * @param parentId
     * @param treeLength
     * @return
     */
    @GetMapping("/customTrace")
    @ResponseBody
    public String customTrace(String traceId, String parentId, Integer treeLength) {
        Tracer tracer = GlobalTracer.get();
        traceId = StringUtils.isEmpty(traceId) ? IdGenerationStrategy.RANDOM.generate().toString() : traceId;
        parentId = StringUtils.isEmpty(parentId) ? DDId.ZERO.toString() : parentId;
        treeLength = treeLength == null ? 3 : treeLength;
        for (int i = 0; i < treeLength; i++) {
            Map<String, String> data = new HashMap<>();
            data.put("x-datadog-trace-id", traceId);
            data.put("x-datadog-parent-id", parentId);

            SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(data));
            Span serverSpan = tracer.buildSpan("opt" + i)
                    .withTag("service_name", "someService" + i)
                    .asChildOf(extractedContext)
                    .start();
            tracer.activateSpan(serverSpan).close();
            serverSpan.finish();
            parentId = serverSpan.context().toSpanId();
        }
        return "build success!";
    }

B3 プロパゲーターを使用してカスタム traceId を実装

<B3 プロパゲーターの紹介>

B3 には 2 つのエンコード方式があります。Single Header と Multiple Header です。

  • マルチヘッダーエンコード:X-B3- プレフィックス付きのヘッダーを使用してトレースコンテキストの各項目を伝送します。
  • シングルヘッダー:コンテキストを b3 という 1 つのヘッダーにまとめます。フィールド抽出時、シングルヘッダー形式がマルチヘッダー形式より優先されます。

以下は、マルチヘッダーエンコードを使用したフローの例です。HTTP リクエストにトレースが伝搬されていると仮定します。

画像

B3 プロパゲーターを有効にする

-Ddd.propagation.style.extract=B3SINGLE
-Ddd.propagation.style.inject=B3SINGLE

メカニズムのソースコード説明

    public boolean accept(String key, String value) {
        ...
        char first = Character.toLowerCase(key.charAt(0));
        switch (first) {
            case 'f':
                if (this.handledForwarding(key, value)) {
                    return true;
                }
                break;
            case 'u':
                if (this.handledUserAgent(key, value)) {
                    return true;
                }
                break;
            case 'x':
                if ((this.traceId == null || this.traceId == DDId.ZERO) && "X-B3-TraceId".equalsIgnoreCase(key)) {
                    classification = 0;
                } else if ((this.spanId == null || this.spanId == DDId.ZERO) && "X-B3-SpanId".equalsIgnoreCase(key)) {
                    classification = 1;
                } else if (this.samplingPriority == this.defaultSamplingPriority() && "X-B3-Sampled".equalsIgnoreCase(key)) {
                    classification = 3;
                } else if (this.handledXForwarding(key, value)) {
                    return true;
                }
        }

        ...

        String firstValue = HttpCodec.firstHeaderValue(value);
        if (null != firstValue) {
            switch (classification) {
                case 0:
                    if (this.setTraceId(firstValue)) {
                        return true;
                    }
                    break;
                case 1:
                    this.setSpanId(firstValue);
                    break;
                case 2:
                    String mappedKey = (String)this.taggedHeaders.get(lowerCaseKey);
                    if (null != mappedKey) {
                        if (this.tags.isEmpty()) {
                            this.tags = new TreeMap();
                        }

                        this.tags.put(mappedKey, HttpCodec.decode(firstValue));
                    }
                    break;
                case 3:
                    this.samplingPriority = this.convertSamplingPriority(firstValue);
                    break;
                case 4:
                    if (this.extractB3(firstValue)) {
                        return true;
                    }
            }
        }

        ...

以下のメソッドは Single Header 方式の処理です。

    private boolean extractB3(String firstValue) {
        if (firstValue.length() == 1) {
            this.samplingPriority = this.convertSamplingPriority(firstValue);
        } else {
            int firstIndex = firstValue.indexOf("-");
            int secondIndex = firstValue.indexOf("-", firstIndex + 1);
            String b3SpanId;
            if (firstIndex != -1) {
                b3SpanId = firstValue.substring(0, firstIndex);
                if (this.setTraceId(b3SpanId)) {
                    return true;
                }
            }

            if (secondIndex == -1) {
                b3SpanId = firstValue.substring(firstIndex + 1);
                this.setSpanId(b3SpanId);
            } else {
                b3SpanId = firstValue.substring(firstIndex + 1, secondIndex);
                this.setSpanId(b3SpanId);
                String b3SamplingId = firstValue.substring(secondIndex + 1);
                this.samplingPriority = this.convertSamplingPriority(b3SamplingId);
            }
        }

        return false;
    }

Multiple Header のコード実装

    private static void b3TraceByMultiple(){
        String traceId = DDId.from("6917954032704516265").toHexStringOrOriginal();
        Tracer tracer = GlobalTracer.get();
        String parentId = DDId.from("4025816492133344807").toHexStringOrOriginal();
        for (int i = 0; i < 3; i++) {
            Map<String, String> data = new HashMap<>();
            data.put("X-B3-TraceId", traceId);
            data.put("X-B3-SpanId", parentId);

            SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(data));
            Span serverSpan = tracer.buildSpan("opt"+i)
                    .withTag("service","someService"+i)
                    .asChildOf(extractedContext)
                    .start();
            serverSpan.setTag("code","200");
            tracer.activateSpan(serverSpan).close();
            serverSpan.finish();
            parentId = DDId.from(serverSpan.context().toSpanId()).toHexStringOrOriginal();
            System.out.println( traceId+"\t"+serverSpan.context().toTraceId()+"\t"+parentId);
        }

    }

注意: Multiple Header では 2 つのヘッダー X-B3-TraceIdX-B3-SpanId を渡す必要があります。インターセプターの解析から、大文字小文字は区別されません。

6001828a33d570a9    6917954032704516265 58c4b35f113ee353
6001828a33d570a9    6917954032704516265 330359b7aaea9d6b
6001828a33d570a9    6917954032704516265 1ac0dcd332f9262f

Single Header のコード実装

    private static void b3TraceBySingle(){
        String traceId = DDId.from("6917954032704516265").toHexStringOrOriginal();
        Tracer tracer = GlobalTracer.get();
        String parentId = DDId.from("4025816492133344807").toHexStringOrOriginal();
        for (int i = 0; i < 3; i++) {
            String b3 = traceId+ "-"+parentId+"-1";
            Map<String, String> data = new HashMap<>();
            data.put("b3",b3);
            SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(data));
            Span serverSpan = tracer.buildSpan("opt"+i)
                    .withTag("service","someService"+i)
                    .asChildOf(extractedContext)
                    .start();
            serverSpan.setTag("code","200");
            tracer.activateSpan(serverSpan).close();
            serverSpan.finish();
            parentId = DDId.from(serverSpan.context().toSpanId()).toHexStringOrOriginal();
            System.out.println( traceId+"\t"+serverSpan.context().toTraceId()+"\t"+parentId);
            System.out.println("b3="+b3);
        }

    }
6001828a33d570a9    6917954032704516265 308287d022272ed9
b3=6001828a33d570a9-37de92c518846627-1
6001828a33d570a9    6917954032704516265 5e6fbaad91daef5c
b3=6001828a33d570a9-308287d022272ed9-1
6001828a33d570a9    6917954032704516265 2cfbc225bddf5e6d
b3=6001828a33d570a9-5e6fbaad91daef5c-1

注意: Single Header ではヘッダーに b3 を渡すだけで、形式は traceId-parentId-Sampled です。

複数のプロパゲーターを有効にする

以下の 2 つの方法のいずれかを選択してください。

  • System Property:
-Ddd.propagation.style.inject=Datadog,B3SINGLE
-Ddd.propagation.style.extract=Datadog,B3SINGLE
  • 環境変数:
DD_PROPAGATION_STYLE_INJECT=Datadog,B3SINGLE
DD_PROPAGATION_STYLE_EXTRACT=Datadog,B3SINGLE

フィードバック

このページは役に立ちましたか?