モノリスアプリケーションにおけるSpring Cloud Sleuth

1概要

この記事では、https://cloud.spring.io/spring-cloud-sleuth/[ Spring Cloud Sleuth ] - あらゆるアプリケーション、特に複数のサービスで構成されるシステムでログを強化するための強力なツールを紹介します。 。

今回の記事では、マイクロサービス ではなく、モノリスアプリケーションでのSleuth の使用に焦点を当てます。

スケジュールされたタスク、マルチスレッドの操作、または複雑なWeb要求を使って問題を診断しようとすると、私たち全員に不幸な経験がありました。多くの場合、ロギングが行われている場合でも、単一の要求を作成するためにどのアクションを相互に関連付ける必要があるのか​​を判断するのは困難です。

これは、** 複雑な動作を診断することを非常に困難にするか、または不可能にさえします。多くの場合、ログを識別するためにリクエスト内の各メソッドに一意のIDを渡すなどの解決策になります。

に来るのは Sleuth 。このライブラリは、特定のジョブ、スレッド、または要求に関連するログを識別することを可能にします。 Sleuthは、 Logback SLF4J などのログ記録フレームワークと簡単に統合でき、ログを使用して問題を追跡および診断するのに役立つ一意の識別子を追加します。

その仕組みを見てみましょう。

2セットアップ

まず、お気に入りのIDEで Spring Boot Webプロジェクトを作成し、この依存関係を pom.xml ファイルに追加します。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

私たちのアプリケーションは Spring Boot で動作し、親pomは各エントリのバージョンを提供します。この依存関係の最新版は、ここにあります。

POM全体を見るにはhttps://github.com/eugenp/tutorials/tree/master/spring-sleuth[Github]でプロジェクトをチェックしてください。

さらに、このアプリケーションのログを識別するように Sleuth に指示するためのアプリケーション名を追加しましょう。

application.properties ファイルに次の行を追加します。

spring.application.name=Baeldung Sleuth Tutorial

3スルース設定

Sleuth は多くの状況でログを強化することができます。バージョン2.0.0以降、Spring Cloud Sleuthはhttps://github.com/openzipkin/brave[Brave]を使用して、アプリケーションに入る各Web要求に一意のIDを追加します。

さらに、SpringチームはこれらのIDをスレッドの境界を越えて共有するためのサポートを追加しました。

トレースは、アプリケーション内でトリガーされる単一の要求またはジョブのように考えることができます。アプリケーションとスレッドの境界を越えても、その要求のさまざまなステップはすべて同じtraceIdを持ちます。

一方、スパンは、仕事または要求の一部として考えることができます。単一のトレースは、要求の特定のステップまたはセクションにそれぞれ相関している複数のスパンで構成できます。トレースとスパンIDを使用して、アプリケーションが要求を処理している時点と場所を正確に特定できます。私達の丸太を読むことを大いにもっと簡単にする。

この例では、これらの機能を単一のアプリケーションで探求します。

3.1. 簡単なWebリクエスト

まず、作業するためのエントリポイントとなるコントローラクラスを作成しましょう。

@RestController
public class SleuthController {

    @GetMapping("/")
    public String helloSleuth() {
        logger.info("Hello Sleuth");
        return "success";
    }
}

アプリケーションを実行して「http://localhost:8080」に移動しましょう。ログを見て出力を確認します。

2017-01-10 22:36:38.254  INFO
 [Baeldung Sleuth Tutorial,4e30f7340b3fb631,4e30f7340b3fb631,false]12516
  ---[nio-8080-exec-1]c.b.spring.session.SleuthController : Hello Sleuth

これは通常のログのように見えますが、括弧で囲まれた部分の最初の部分が違います。これが Spring Sleuth が追加したコア情報です。このデータは以下のフォーマットに従います。

  • [アプリケーション名、traceId、spanId、export]**

  • アプリケーション名 - これは、プロパティファイルに設定した名前です。

また、同じアプリケーションの複数のインスタンスからログを集約するためにも使用できます。

  • TraceId - これは単一の要求、ジョブに割り当てられるIDです。

または行動。それぞれのユニークユーザーがWebリクエストを開始したようなものはそれ自身の traceId を持ちます。

  • SpanId - 作業単位を追跡します。で構成されている要求を考える

複数のステップ各ステップは独自の spanId を持ち、個別に追跡できます。デフォルトでは、どのアプリケーションフローも同じTraceIdとSpanIdで始まります。

  • Export - このプロパティは、そうでないかどうかを示すブール値です。

このログは Zipkin のようなアグリゲータにエクスポートされました。 Zipkin はこの記事の範囲外ですが、 Sleuth によって作成されたログを分析する上で重要な役割を果たします。

ここまでで、あなたはこのライブラリの力についていくらかの考えを持っているはずです。このライブラリがロギングにいかに不可欠であるかをさらに実証するために別の例を見てみましょう。

3.2. サービスアクセスを伴う簡単なWebリクエスト

単一の方法でサービスを作成することから始めましょう。

@Service
public class SleuthService {

    public void doSomeWorkSameSpan() {
        Thread.sleep(1000L);
        logger.info("Doing some work");
    }
}

それでは、私たちのサービスをコントローラに注入し、それにアクセスするリクエストマッピングメソッドを追加しましょう。

@Autowired
private SleuthService sleuthService;

    @GetMapping("/same-span")
    public String helloSleuthSameSpan() throws InterruptedException {
        logger.info("Same Span");
        sleuthService.doSomeWorkSameSpan();
        return "success";
}

最後に、アプリケーションを再起動して「http://localhost:8080/same-span」に移動します。次のようなログ出力に注意してください。

2017-01-10 22:51:47.664  INFO
 [Baeldung Sleuth Tutorial,b77a5ea79036d5b9,b77a5ea79036d5b9,false]12516
  ---[nio-8080-exec-3]c.b.spring.session.SleuthController      : Same Span
2017-01-10 22:51:48.664  INFO
 [Baeldung Sleuth Tutorial,b77a5ea79036d5b9,b77a5ea79036d5b9,false]12516
  ---[nio-8080-exec-3]c.baeldung.spring.session.SleuthService  : Doing some work

メッセージが2つの異なるクラスから発信されている場合でも、トレースとスパンIDは2つのログ間で同じであることに注意してください。これにより、リクエスト中の traceId を検索することで、リクエスト中に各ログを識別することは簡単になります。

これがデフォルトの振る舞いで、一つのリクエストは一つの traceId spanId を取得します。しかし、必要に応じて手動でスパンを追加することもできます。この機能を使った例を見てみましょう。

3.3. 手動でスパンを追加する

はじめに、新しいコントローラを追加しましょう。

@GetMapping("/new-span")
public String helloSleuthNewSpan() {
    logger.info("New Span");
    sleuthService.doSomeWorkNewSpan();
    return "success";
}

それでは、サービス内に新しいメソッドを追加しましょう。

@Autowired
private Tracer tracer;//...
public void doSomeWorkNewSpan() throws InterruptedException {
    logger.info("I'm in the original span");

    Span newSpan = tracer.newTrace().name("newSpan").start();
    try (SpanInScope ws = tracer.withSpanInScope(newSpan.start())) {
        Thread.sleep(1000L);
        logger.info("I'm in the new span doing some cool work that needs its own span");
    } finally {
        newSpan.finish();
    }

    logger.info("I'm in the original span");
}

新しいオブジェクト Tracer も追加したことに注意してください。 tracer インスタンスは起動時に Spring Sleuth によって作成され、依存性注入を通じてクラスで使用可能になります。

トレースは手動で開始および停止する必要があります。これを達成するために、手動で作成された span で実行されるコードは try-finally ブロック内に配置され、 span が操作の成功にかかわらず確実に閉じられるようにします。また、新しいスパンを範囲内に配置する必要があることにも注意してください。

アプリケーションを再起動し、「http://localhost:8080/new-span」に移動します。次のようなログ出力に注意してください。

2017-01-11 21:07:54.924
  INFO[Baeldung Sleuth Tutorial,9cdebbffe8bbbade,9cdebbffe8bbbade,false]12516
  ---[nio-8080-exec-6]c.b.spring.session.SleuthController      : New Span
2017-01-11 21:07:54.924
  INFO[Baeldung Sleuth Tutorial,9cdebbffe8bbbade,9cdebbffe8bbbade,false]12516
  ---[nio-8080-exec-6]c.baeldung.spring.session.SleuthService  :
  I'm in the original span
2017-01-11 21:07:55.924
  INFO[Baeldung Sleuth Tutorial,9cdebbffe8bbbade,1e706f252a0ee9c2,false]12516
  ---[nio-8080-exec-6]c.baeldung.spring.session.SleuthService  :
  I'm in the new span doing some cool work that needs its own span
2017-01-11 21:07:55.924
  INFO[Baeldung Sleuth Tutorial,9cdebbffe8bbbade,9cdebbffe8bbbade,false]12516
  ---[nio-8080-exec-6]c.baeldung.spring.session.SleuthService  :
  I'm in the original span

3番目のログは他のユーザーと traceId を共有していますが、一意の spanId を持っています。これは、よりきめの細かいトレースを求めるために、1回の要求でさまざまなセクションを見つけるために使用できます。

それでは、スレッドに対する__Sleuthのサポートを見てみましょう。

3.4. スパニングランナブル

Sleuth のスレッド機能を実証するために、まずスレッドプールを設定するための設定クラスを追加しましょう。

@Configuration
public class ThreadConfig {

    @Autowired
    private BeanFactory beanFactory;

    @Bean
    public Executor executor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor
         = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(1);
        threadPoolTaskExecutor.setMaxPoolSize(1);
        threadPoolTaskExecutor.initialize();

        return new LazyTraceExecutor(beanFactory, threadPoolTaskExecutor);
    }
}

ここで LazyTraceExecutor の使用に注意することが重要です。このクラスは Sleuth ライブラリから派生したもので、私たちの __traceId を新しいスレッドに伝播し、その過程で新しい __spanIdを作成する特別な種類のexecutorです。

それでは、このエグゼキュータをコントローラに接続し、それを新しいリクエストマッピングメソッドで使用しましょう。

@Autowired
private Executor executor;

    @GetMapping("/new-thread")
    public String helloSleuthNewThread() {
        logger.info("New Thread");
        Runnable runnable = () -> {
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            logger.info("I'm inside the new thread - with a new span");
        };
        executor.execute(runnable);

        logger.info("I'm done - with the original span");
        return "success";
}

実行可能ファイルを配置したら、アプリケーションを再起動して「http://localhost:8080/new-thread」に移動します。次のようなログ出力に注意してください。

2017-01-11 21:18:15.949
  INFO[Baeldung Sleuth Tutorial,96076a78343c364d,96076a78343c364d,false]12516
  ---[nio-8080-exec-9]c.b.spring.session.SleuthController      : New Thread
2017-01-11 21:18:15.950
  INFO[Baeldung Sleuth Tutorial,96076a78343c364d,96076a78343c364d,false]12516
  ---[nio-8080-exec-9]c.b.spring.session.SleuthController      :
  I'm done - with the original span
2017-01-11 21:18:16.953
  INFO[Baeldung Sleuth Tutorial,96076a78343c364d,e3b6a68013ddfeea,false]12516
  ---[lTaskExecutor-1]c.b.spring.session.SleuthController      :
  I'm inside the new thread - with a new span

前の例と同じように、すべてのログが同じ traceId を共有していることがわかります。しかし、ランナブルから来るログには、そのスレッドで行われた作業を追跡するユニークなスパンがあります。これが LazyTraceExecutor のために起こることを覚えておいてください、もし私たちが通常のexecutorを使うことになっていたら、私たちは同じ spanId が新しいスレッドで使われ続けるのを見続けるでしょう。

それでは、 @ Async メソッドの Sleuth’s サポートを調べてみましょう。

3.5. @ Async のサポート

非同期サポートを追加するには、まずこの機能を有効にするために ThreadConfig クラスを変更しましょう。

@Configuration
@EnableAsync
public class ThreadConfig extends AsyncConfigurerSupport {

   //...
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(1);
        threadPoolTaskExecutor.setMaxPoolSize(1);
        threadPoolTaskExecutor.initialize();

        return new LazyTraceExecutor(beanFactory, threadPoolTaskExecutor);
    }
}

非同期実行プログラムを指定するために AsyncConfigurerSupport を拡張し、traceIdsとspanIdsが正しく伝搬されるように LazyTraceExecutor を使用することに注意してください。また、クラスの先頭に @ EnableAsync を追加しました。

それでは、サービスに非同期メソッドを追加しましょう。

@Async
public void asyncMethod() {
    logger.info("Start Async Method");
    Thread.sleep(1000L);
    logger.info("End Async Method");
}

それでは、コントローラからこのメソッドを呼び出しましょう。

@GetMapping("/async")
public String helloSleuthAsync() {
    logger.info("Before Async Method Call");
    sleuthService.asyncMethod();
    logger.info("After Async Method Call");

    return "success";
}

最後に、サービスを再起動して、「http://localhost:8080/async」に移動します。次のようなログ出力に注意してください。

2017-01-11 21:30:40.621
  INFO[Baeldung Sleuth Tutorial,c187f81915377fff,c187f81915377fff,false]10072
  ---[nio-8080-exec-2]c.b.spring.session.SleuthController      :
  Before Async Method Call
2017-01-11 21:30:40.622
  INFO[Baeldung Sleuth Tutorial,c187f81915377fff,c187f81915377fff,false]10072
  ---[nio-8080-exec-2]c.b.spring.session.SleuthController      :
  After Async Method Call
2017-01-11 21:30:40.622
  INFO[Baeldung Sleuth Tutorial,c187f81915377fff,8a9f3f097dca6a9e,false]10072
  ---[lTaskExecutor-1]c.baeldung.spring.session.SleuthService  :
  Start Async Method
2017-01-11 21:30:41.622
  INFO[Baeldung Sleuth Tutorial,c187f81915377fff,8a9f3f097dca6a9e,false]10072
  ---[lTaskExecutor-1]c.baeldung.spring.session.SleuthService  :
  End Async Method

実行可能な例のように、 Sleuth traceId をasyncメソッドに伝搬し、固有のspanIdを追加しています。

スケジュールされたタスクに対して春のサポートを使用した例を見てみましょう。

3.6. @予定 サポート

最後に、 Sleuth @ Scheduled メソッドとどのように連携するのかを見てみましょう。これを行うには、スケジューリングを有効にするように ThreadConfig クラスを更新しましょう。

@Configuration
@EnableAsync
@EnableScheduling
public class ThreadConfig extends AsyncConfigurerSupport
  implements SchedulingConfigurer {

   //...

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(schedulingExecutor());
    }

    @Bean(destroyMethod = "shutdown")
    public Executor schedulingExecutor() {
        return Executors.newScheduledThreadPool(1);
    }
}

SchedulingConfigurer インターフェースを実装し、そのconfigureTasksメソッドをオーバーライドしたことに注意してください。また、クラスの先頭に @ EnableScheduling を追加しました。

次に、スケジュールされたタスク用のサービスを追加しましょう。

@Service
public class SchedulingService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SleuthService sleuthService;

    @Scheduled(fixedDelay = 30000)
    public void scheduledWork() throws InterruptedException {
        logger.info("Start some work from the scheduled task");
        sleuthService.asyncMethod();
        logger.info("End work from scheduled task");
    }
}

このクラスでは、30秒の固定遅延で単一のスケジュールタスクを作成しました。

それでは、アプリケーションを再起動して、タスクが実行されるのを待ちましょう。

このような出力をコンソールで確認してください。

2017-01-11 21:30:58.866
  INFO[Baeldung Sleuth Tutorial,3605f5deaea28df2,3605f5deaea28df2,false]10072
  ---[pool-1-thread-1]c.b.spring.session.SchedulingService     :
  Start some work from the scheduled task
2017-01-11 21:30:58.866
  INFO[Baeldung Sleuth Tutorial,3605f5deaea28df2,3605f5deaea28df2,false]10072
  ---[pool-1-thread-1]c.b.spring.session.SchedulingService     :
  End work from scheduled task

ここで、 Sleuth が私たちのタスクのための新しいトレースとスパンIDを作成したことがわかります。タスクの各インスタンスは、デフォルトでそれ自身のトレースとスパンを取得します。

4結論

結論として、単一のWebアプリケーション内のさまざまな状況で Spring Sleuth をどのように使用できるかを説明しました。このテクノロジを使用すると、1つのリクエストが複数のスレッドにわたる場合でも、そのリクエストからのログを簡単に関連付けることができます。

これまでに、マルチスレッド環境をデバッグするときに Spring Cloud Sleuth がどのようにして私たちの正当性を保つのに役立つかを見ることができます。 traceId 内の各操作と spanId 内の各ステップを識別することで、ログ内の複雑なジョブの分析を中断することができます。

クラウドにアクセスしなくても、 Spring Sleuth はほとんどすべてのプロジェクトにおいて重要な依存関係になる可能性があります。統合するのはシームレスで、 は価値の大幅な追加 です。

ここから、 Sleuth の他の機能を調べたいと思うかもしれません。 RabbitMQ Redis で使用されているメッセージングプロトコル全体にわたって、そしてZuulのようなゲートウェイを通じて、 RestTemplate を使用して分散システムでトレースをサポートできます。

いつものように、ソースコードhttps://github.com/eugenp/tutorials/tree/master/spring-sleuth[over on Github]を見つけることができます。