このセクションでは、Spring Cloud Sleuth の使用方法について詳しく説明します。Spring Cloud Sleuth API またはアノテーションを介したスパンライフサイクルの制御などのトピックについて説明します。また、いくつかの Spring Cloud Sleuth のベストプラクティスについても説明します。

Spring Cloud Sleuth を使い始める場合は、このセクションに入る前に入門ガイドを読む必要があります。

1. Spring Cloud Sleuth の API とスパンライフサイクル

api モジュールの Spring Cloud Sleuth コアには、トレーサーによって実装されるために必要なすべてのインターフェースが含まれています。プロジェクトには OpenZipkin Brave の実装が付属しています。org.springframework.cloud.sleuth.brave.bridge を見ると、トレーサーが Sleuth の API にどのようにブリッジされているかを確認できます。

最も一般的に使用されるインターフェースは次のとおりです。

  • org.springframework.cloud.sleuth.Tracer - トレーサーを使用して、リクエストのクリティカルパスをキャプチャーするルートスパンを作成できます。

  • org.springframework.cloud.sleuth.Span - スパンは、開始および停止する必要がある単一の作業単位です。タイミング情報とイベントおよびタグが含まれています。

トレーサー実装の API を直接使用することもできます。

次のスパンライフサイクルアクションを見てみましょう。

  • 開始 : スパンを開始すると、その名前が割り当てられ、開始タイムスタンプが記録されます。

  • 終了 : スパンが終了し (スパンの終了時間が記録され)、スパンがサンプリングされた場合、収集の対象となります (例: Zipkin へ)。

  • continue: スパンは別のスレッドなどで継続されます。

  • 明示的な親を使用して作成 : 新しいスパンを作成し、その親を明示的に設定できます。

Spring Cloud Sleuth は、Tracer のインスタンスを作成します。それを使用するために、それをオートワイヤーすることができます。

1.1. スパンの作成と終了

次の例に示すように、Tracer を使用して手動でスパンを作成できます。

// Start a span. If there was a span present in this thread it will become
// the `newSpan`'s parent.
Span newSpan = this.tracer.nextSpan().name("calculateTax");
try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) {
    // ...
    // You can tag a span
    newSpan.tag("taxValue", taxValue);
    // ...
    // You can log an event on a span
    newSpan.event("taxCalculated");
}
finally {
    // Once done remember to end the span. This will allow collecting
    // the span to send it to a distributed tracing system e.g. Zipkin
    newSpan.end();
}

前の例では、スパンの新しいインスタンスを作成する方法を確認できました。このスレッドにすでにスパンがある場合は、新しいスパンの親になります。

スパンを作成した後は、必ず清掃してください。
スパンに 50 文字を超える名前が含まれている場合、その名前は 50 文字に切り捨てられます。あなたの名前は明確で具体的でなければなりません。ビッグネームはレイテンシーの課題を引き起こし、時には例外さえ引き起こします。

1.2. 継続スパン

新しいスパンを作成したくないが、継続したい場合があります。このような状況の例は次のとおりです。

  • AOP : アスペクトに到達する前にすでにスパンが作成されている場合は、新しいスパンを作成したくない場合があります。

スパンを継続するには、次の例に示すように、スパンを 1 つのスレッドに格納し、別のスレッドに渡すことができます。

Span spanFromThreadX = this.tracer.nextSpan().name("calculateTax");
try (Tracer.SpanInScope ws = this.tracer.withSpan(spanFromThreadX.start())) {
    executorService.submit(() -> {
        // Pass the span from thread X
        Span continuedSpan = spanFromThreadX;
        // ...
        // You can tag a span
        continuedSpan.tag("taxValue", taxValue);
        // ...
        // You can log an event on a span
        continuedSpan.event("taxCalculated");
    }).get();
}
finally {
    spanFromThreadX.end();
}

1.3. 明示的な親を持つスパンの作成

新しいスパンを開始し、そのスパンの明示的な親を提供することをお勧めします。スパンの親が 1 つのスレッドにあり、別のスレッドで新しいスパンを開始するとします。Tracer.nextSpan() を呼び出すと、現在スコープ内にあるスパンを参照してスパンが作成されます。次の例に示すように、スパンをスコープに入れてから Tracer.nextSpan() を呼び出すことができます。

// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X. `initialSpan` will be the parent
// of the `newSpan`
Span newSpan = null;
try (Tracer.SpanInScope ws = this.tracer.withSpan(initialSpan)) {
    newSpan = this.tracer.nextSpan().name("calculateCommission");
    // ...
    // You can tag a span
    newSpan.tag("commissionValue", commissionValue);
    // ...
    // You can log an event on a span
    newSpan.event("commissionCalculated");
}
finally {
    // Once done remember to end the span. This will allow collecting
    // the span to send it to e.g. Zipkin. The tags and events set on the
    // newSpan will not be present on the parent
    if (newSpan != null) {
        newSpan.end();
    }
}
このようなスパンを作成したら、それを終了する必要があります。それ以外の場合は報告されません (例: Zipkin に)。

Tracer.nextSpan(Span parentSpan) バージョンを使用して、親スパンを明示的に提供することもできます。

2. スパンの命名

スパン名の選択は簡単な作業ではありません。スパン名は、操作名を表す必要があります。名前はカーディナリティが低い必要があるため、識別子を含めないでください。

多くのインストルメンテーションが行われているため、一部のスパン名は人工的なものです。

  • メソッド名が controllerMethodName のコントローラーが受信した場合の controller-method-name 

  • ラップされた Callable および Runnable インターフェースで実行される非同期操作用の async

  • @Scheduled アノテーションが付けられたメソッドは、クラスの単純な名前を返します。

幸い、非同期処理の場合、明示的な名前を付けることができます。

2.1. @SpanName アノテーション

次の例に示すように、@SpanName アノテーションを使用して、スパンに明示的に名前を付けることができます。

@SpanName("calculateTax")
class TaxCountingRunnable implements Runnable {

    @Override
    public void run() {
        // perform logic
    }

}

この場合、次の方法で処理されると、スパンの名前は calculateTax になります。

Runnable runnable = new TraceRunnable(this.tracer, spanNamer, new TaxCountingRunnable());
Future<?> future = executorService.submit(runnable);
// ... some additional logic ...
future.get();

2.2. toString() メソッド

Runnable または Callable 用に個別のクラスを作成することは非常にまれです。通常、これらのクラスの匿名インスタンスを作成します。そのようなクラスにアノテーションを付けることはできません。この制限を克服するために、@SpanName アノテーションが存在しない場合、クラスに toString() メソッドのカスタム実装があるかどうかを確認します。

このようなコードを実行すると、次の例に示すように、calculateTax という名前のスパンが作成されます。

Runnable runnable = new TraceRunnable(this.tracer, spanNamer, new Runnable() {
    @Override
    public void run() {
        // perform logic
    }

    @Override
    public String toString() {
        return "calculateTax";
    }
});
Future<?> future = executorService.submit(runnable);
// ... some additional logic ...
future.get();

3. アノテーションを使用したスパンの管理

アノテーションを使用してスパンを管理する理由はいくつかあります。

  • API にとらわれないということは、スパンと協力することを意味します。アノテーションを使用すると、ユーザーは、スパン API にライブラリを依存せずにスパンに追加できます。そうすることで、Sleuth はコア API を変更して、ユーザーコードへの影響を少なくすることができます。

  • 基本的なスパン操作の表面積が減少しました。この機能がない場合は、誤って使用される可能性のあるライフサイクルコマンドを含む spanapi を使用する必要があります。スコープ、タグ、ログの機能を公開するだけで、誤ってスパンのライフサイクルを壊すことなく共同作業を行うことができます。

  • ランタイムで生成されたコードとのコラボレーション。Spring Data や Feign などのライブラリを使用すると、インターフェースの実装は実行時に生成されます。その結果、オブジェクトのスパン折り返しは面倒でした。これで、インターフェースとそれらのインターフェースの引数を介してアノテーションを付けることができます。

3.1. 新しいスパンの作成

ローカルスパンを手動で作成したくない場合は、@NewSpan アノテーションを使用できます。また、自動化された方法でタグを追加するための @SpanTag アノテーションを提供します。

これで、いくつかの使用例を検討できます。

@NewSpan
void testMethod();

パラメーターを指定せずにメソッドにアノテーションを付けると、アノテーション付きのメソッド名と同じ名前の新しいスパンが作成されます。

@NewSpan("customNameOnTestMethod4")
void testMethod4();

アノテーションに値を指定する場合(直接または name パラメーターを設定することにより)、作成されたスパンには、指定された値が名前として含まれます。

// method declaration
@NewSpan(name = "customNameOnTestMethod5")
void testMethod5(@SpanTag("testTag") String param);

// and method execution
this.testBean.testMethod5("test");

名前とタグの両方を組み合わせることができます。後者に焦点を当てましょう。この場合、アノテーションが付けられたメソッドのパラメーターランタイム値の値がタグの値になります。このサンプルでは、タグキーは testTag であり、タグ値は test です。

@NewSpan(name = "customNameOnTestMethod3")
@Override
public void testMethod3() {
}

@NewSpan アノテーションは、クラスとインターフェースの両方に配置できます。インターフェースのメソッドをオーバーライドし、@NewSpan アノテーションに異なる値を指定すると、最も具体的なものが優先されます(この場合、customNameOnTestMethod3 が設定されます)。

3.2. 継続スパン

タグとアノテーションを既存のスパンに追加する場合は、次の例に示すように、@ContinueSpan アノテーションを使用できます。

// method declaration
@ContinueSpan(log = "testMethod11")
void testMethod11(@SpanTag("testTag11") String param);

// method execution
this.testBean.testMethod11("test");
this.testBean.testMethod13();

@NewSpan アノテーションとは対照的に、log パラメーターを使用してログを追加することもできることに注意してください)

そうすれば、スパンが継続され、次のようになります。

  • testMethod11.before および testMethod11.after という名前のログエントリが作成されます。

  • 例外がスローされると、testMethod11.afterFailure という名前のログエントリも作成されます。

  • キーが testTag11 で値が test のタグが作成されます。

3.3. 高度なタグ設定

スパンにタグを追加するには、3 つの異なる方法があります。それらはすべて SpanTag アノテーションによって制御されます。優先順位は次のとおりです。

  1. TagValueResolver 型の Bean と指定された名前で試してください。

  2. Bean 名が指定されていない場合は、式を評価してみてください。TagValueExpressionResolver Bean を検索します。デフォルトの実装では、SPEL 式の解決が使用されます。IMPORTANT SPEL 式からのみプロパティを参照できます。セキュリティ上の制約により、メソッドの実行は許可されていません。

  3. 評価する式が見つからない場合は、パラメーターの toString() 値を返します。

3.3.1. カスタムエクストラクター

次のメソッドのタグの値は、TagValueResolver インターフェースの実装によって計算されます。そのクラス名は、resolver 属性の値として渡す必要があります。

次のアノテーション付きメソッドを検討してください。

@NewSpan
public void getAnnotationForTagValueResolver(
        @SpanTag(key = "test", resolver = TagValueResolver.class) String test) {
}

次に、次の TagValueResolver Bean 実装についてさらに検討します。

@Bean(name = "myCustomTagValueResolver")
public TagValueResolver tagValueResolver() {
    return parameter -> "Value from myCustomTagValueResolver";
}

前述の 2 つの例では、タグ値を Value from myCustomTagValueResolver に設定します。

3.3.2. 値の式の解決

次のアノテーション付きメソッドを検討してください。

@NewSpan
public void getAnnotationForTagValueExpression(
        @SpanTag(key = "test", expression = "'hello' + ' characters'") String test) {
}

TagValueExpressionResolver のカスタム実装は、SPEL 式の評価につながりません。また、値が hello characters のタグがスパンに設定されます。他の式解決メカニズムを使用する場合は、Bean の独自の実装を作成できます。

3.3.3. toString() メソッドの使用

次のアノテーション付きメソッドを検討してください。

@NewSpan
public void getAnnotationForArgumentToString(@SpanTag("test") Long param) {
}

上記のメソッドを 15 の値で実行すると、文字列値が "15" のタグが設定されます。

4. 次のステップ

これで、Spring Cloud Sleuth の使用方法と、従う必要のあるいくつかのベストプラクティスを理解する必要があります。これで、特定の Spring Cloud Sleuth の機能について学習することも、スキップして Spring Cloud Sleuth で利用可能な統合について読むこともできます。