1. はじめに
前回の記事「AWS FargateでJavaアプリを自動計装してX-Rayでトレースする方法」では、アプリケーションコードを変更することなく、AWS Distro for OpenTelemetry (ADOT)を使用したJavaアプリケーションの自動計装方法を解説しました。
AWS FargateでJavaアプリを自動計装してX-Rayでトレースする方法
これにより、X-Rayでのトレース収集は実現できましたが、より効果的なトラブルシューティングのためには「ログとトレースの紐付け」が重要です。
今回は、前回構築した環境をさらに発展させ、アプリケーションログにX-Rayトレース情報を埋め込む方法を解説します。これにより、CloudWatch Logsで見つけたエラーログからX-Rayトレースへの直接リンク、またはその逆の流れが可能になり、問題の根本原因分析が大幅に効率化されます。
2. ログとトレースの紐付けがもたらす価値
2.1 なぜログとトレースの紐付けが重要か?
分散システムでは、問題が発生したとき「何が」「どこで」「なぜ」起きたのかを特定するのが難しいことがあります。
- ログだけでは: 詳細な情報は得られるものの、リクエストの全体像が見えない
- トレースだけでは: リクエストの流れは把握できるが、詳細なアプリケーション状態が分からない
両方を紐付ければ:
- エラーログを見つけたら、そのリクエストの全体的な処理フロー(トレース)を確認できる
- トレースで異常を見つけたら、関連する詳細なアプリケーションログを参照できる
2.2 AWS環境での実現方法
AWS環境では、X-RayとCloudWatch Logsの連携により、この紐付けを実現できます。
- アプリケーションログにX-Ray形式のトレースIDを埋め込む
- CloudWatch LogsのログイベントからX-Rayトレースへリンクする機能を活用
3. 実装手順:ログとトレースの紐付け
前回の自動計装環境に、以下の3つのファイル修正/追加を行います。
AWS FargateでJavaアプリを自動計装してX-Rayでトレースする方法
- build.gradle.kts - OpenTelemetry Logbackの依存関係を追加
- logback.xml - ログにトレースIDを埋め込む設定を追加
- Dockerfile - 上記設定ファイルの配置と環境変数の設定
3.1 build.gradle.ktsの更新
まず、build.gradle.ktsファイルに、OpenTelemetryとLogbackの連携に必要な依存関係を追加します。
plugins {
java
application
id("org.springframework.boot") version "3.1.3"
id("io.spring.dependency-management") version "1.1.3"
}
sourceSets {
main {
java.setSrcDirs(setOf("."))
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
// OpenTelemetryとLogbackの連携のための依存関係を追加
implementation("io.opentelemetry.instrumentation:opentelemetry-logback-mdc-1.0:1.33.0-alpha")
}
変更ポイント:
-
opentelemetry-logback-mdc-1.0
ライブラリを追加(バージョンは必ず使用する自動計装エージェントと揃える) - このライブラリにより、Logbackでトレースコンテキストに含まれる情報にアクセスできるようになります
3.2 logback.xml の作成
次に、Logbackの設定ファイルを作成します。この設定により、ログメッセージにトレースIDを含めることができます。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} trace_id=%X{AWS-XRAY-TRACE-ID} span_id=%X{span_id} trace_flags=%X{trace_flags} - %msg%n</pattern>
</encoder>
</appender>
<!-- OpenTelemetryAppenderでラップして、トレースコンテキストの情報をMDCに注入 -->
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender">
<appender-ref ref="CONSOLE"/>
</appender>
<!-- OTELアペンダーを使用 -->
<root level="DEBUG">
<appender-ref ref="OTEL"/>
</root>
</configuration>
重要ポイント:
-
trace_id=%X{AWS-XRAY-TRACE-ID}
でX-Ray形式のトレースIDをログに埋め込む -
OpenTelemetryAppender
でLogbackアペンダーをラップし、トレースコンテキスト情報をMDC(Mapped Diagnostic Context)に自動注入 - MDCとは、スレッドローカルにキー/値ペアを保存するLogbackの機能で、トレース情報をログに埋め込むのに最適です
3.3 Dockerfileの更新
前回のDockerfileを以下のように更新します。
FROM public.ecr.aws/docker/library/gradle:jdk17 AS builder
WORKDIR /adot-java-sample
COPY *.java .
COPY build.gradle.kts .
COPY logback.xml .
RUN ["gradle", "assemble"]
FROM gcr.io/distroless/java17-debian11:latest
WORKDIR /adot-java-sample
COPY --from=builder /adot-java-sample/build/libs/ ./build/libs/
COPY logback.xml .
ENV SERVER_PORT=80
# ADOTエージェントのダウンロード
ADD https://github.com/aws-observability/aws-otel-java-instrumentation/releases/download/v1.33.0/aws-opentelemetry-agent.jar ./aws-opentelemetry-agent.jar
ENV JAVA_TOOL_OPTIONS "-javaagent:/adot-java-sample/aws-opentelemetry-agent.jar"
# OpenTelemetry関連の環境変数設定
ENV OTEL_RESOURCE_ATTRIBUTES "service.name=xraytestapp"
ENV OTEL_IMR_EXPORT_INTERVAL "60000"
ENV OTEL_EXPORTER_OTLP_ENDPOINT "http://127.0.0.1:4317"
ENV OTEL_METRICS_EXPORTER "none"
ENV OTEL_TRACES_EXPORTER="otlp"
EXPOSE 80
CMD ["/adot-java-sample/build/libs/adot-java-sample.jar"]
変更ポイント:
-
logback.xml
をコピー -
LOGGING_CONFIG
環境変数でlogback.xmlの場所を指定
4. 実装の詳細解説
4.1 トレースコンテキストがログに伝播される仕組み
この実装で重要なのは、トレースコンテキスト情報がアプリケーションログに自動的に含まれる仕組みです。
-
OpenTelemetry Java自動計装エージェント:
- サーバーリクエスト受信時に自動的にトレースを開始
- 実行スレッドにトレースコンテキスト(トレースID、スパンIDなど)を関連付け
-
OpenTelemetry Logback MDC統合:
-
opentelemetry-logback-mdc-1.0
ライブラリが現在のトレースコンテキスト情報をLogbackのMDCに注入 - 主要なコンテキスト情報:
AWS-XRAY-TRACE-ID
,span_id
,trace_flags
-
-
logback.xml:
- MDCに注入された値をログパターンで参照(
%X{キー名}
記法で)
- MDCに注入された値をログパターンで参照(
4.2 X-Ray形式のトレースID
AWS X-Rayでは、トレースIDは以下の形式で表現されます。
1-5759e988-bd862e3fe1be46a994272793
この形式は:
-
1-
で始まるプレフィックス(バージョン) - タイムスタンプ(8桁の16進数)
- ランダムな24桁の16進数
CloudWatch Logsでは、この形式のトレースIDを含むログエントリが自動的にX-Rayトレースにリンクされます。
5. 実際にやってみた
3章でも記載の通り前回の自動計装環境に、以下の3つのファイル修正/追加を行います。
環境の準備ができていない方は以下記事をご参照ください。
AWS FargateでJavaアプリを自動計装してX-Rayでトレースする方法
5.1 ファイル準備
1. すべてのファイルを用意
前回の作業ディレクトリに、更新したbuild.gradle.ktsと新しいlogback.xmlを追加します。
すべてのファイル作成コマンド(まとめて実行)
# DiceApplication.javaの作成
cat > DiceApplication.java << 'EOL'
package otel;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DiceApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(DiceApplication.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}
}
EOL
# RollController.javaの作成
cat > RollController.java << 'EOL'
package otel;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RollController {
private static final Logger logger = LoggerFactory.getLogger(RollController.class);
@GetMapping("/rolldice")
public String index(@RequestParam("player") Optional<String> player) {
int result = this.getRandomNumber(1, 6);
if (player.isPresent()) {
logger.info("{} is rolling the dice: {}", player.get(), result);
} else {
logger.info("Anonymous player is rolling the dice: {}", result);
}
return Integer.toString(result);
}
@GetMapping("/health")
public String health() {
return "OK";
}
private int getRandomNumber(int min, int max) {
return ThreadLocalRandom.current().nextInt(min, max + 1);
}
}
EOL
# 更新したbuild.gradle.ktsの作成
cat > build.gradle.kts << 'EOL'
plugins {
java
application
id("org.springframework.boot") version "3.1.3"
id("io.spring.dependency-management") version "1.1.3"
}
sourceSets {
main {
java.setSrcDirs(setOf("."))
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
// OpenTelemetryとLogbackの連携のための依存関係を追加
implementation("io.opentelemetry.instrumentation:opentelemetry-logback-mdc-1.0:1.33.0-alpha")
}
EOL
# logback.xmlの作成
cat > logback.xml << 'EOL'
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} trace_id=%X{AWS-XRAY-TRACE-ID} span_id=%X{span_id} trace_flags=%X{trace_flags} - %msg%n</pattern>
</encoder>
</appender>
<!-- OpenTelemetryAppenderでラップして、トレースコンテキストの情報をMDCに注入 -->
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.mdc.v1_0.OpenTelemetryAppender">
<appender-ref ref="CONSOLE"/>
</appender>
<!-- OTELアペンダーを使用 -->
<root level="DEBUG">
<appender-ref ref="OTEL"/>
</root>
</configuration>
EOL
# 更新したDockerfileの作成
cat > Dockerfile << 'EOL'
FROM public.ecr.aws/docker/library/gradle:jdk17 AS builder
WORKDIR /adot-java-sample
COPY *.java .
COPY build.gradle.kts .
COPY logback.xml .
RUN ["gradle", "assemble"]
FROM gcr.io/distroless/java17-debian11:latest
WORKDIR /adot-java-sample
COPY --from=builder /adot-java-sample/build/libs/ ./build/libs/
COPY logback.xml .
ENV SERVER_PORT=80
# ADOTエージェントのダウンロード
ADD https://github.com/aws-observability/aws-otel-java-instrumentation/releases/download/v1.33.0/aws-opentelemetry-agent.jar ./aws-opentelemetry-agent.jar
ENV JAVA_TOOL_OPTIONS "-javaagent:/adot-java-sample/aws-opentelemetry-agent.jar"
# OpenTelemetry関連の環境変数設定
ENV OTEL_RESOURCE_ATTRIBUTES "service.name=xraytestapp"
ENV OTEL_IMR_EXPORT_INTERVAL "60000"
ENV OTEL_EXPORTER_OTLP_ENDPOINT "http://127.0.0.1:4317"
ENV OTEL_METRICS_EXPORTER "none"
ENV OTEL_TRACES_EXPORTER="otlp"
# logback.xmlの場所を指定
ENV LOGGING_CONFIG=/adot-java-sample/logback.xml
EXPOSE 80
CMD ["/adot-java-sample/build/libs/adot-java-sample.jar"]
EOL
5.2 イメージのビルドとデプロイ
1. Dockerイメージのビルド
docker build -t adot-java-sample-with-logs .
2. ECRリポジトリへの認証
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin $(aws sts get-caller-identity --query 'Account' --output text).dkr.ecr.ap-northeast-1.amazonaws.com
3. イメージのタグ付け
docker tag adot-java-sample:latest $(aws sts get-caller-identity --query 'Account' --output text).dkr.ecr.ap-northeast-1.amazonaws.com/adot-java-sample:latest
ECR名は適宜更新してください。
4. イメージのプッシュ
docker push $(aws sts get-caller-identity --query 'Account' --output text).dkr.ecr.ap-northeast-1.amazonaws.com/adot-java-sample:latest
ECR名は適宜更新してください。
5. ECSサービスの更新
前回作成したECSサービスが新しいイメージを使用するように更新されます。
ECSサービスのデプロイが完了後、アプリケーションにアクセスしてみましょう。
5.3 結果の確認
1. アプリケーションへのアクセス
ALB経由で/rolldice
エンドポイントに複数回アクセスします。
https://your-alb-domain/rolldice
https://your-alb-domain/rolldice?player=Alice
2. CloudWatchログの確認
CloudWatch Logsコンソールで、アプリケーションのログを確認します。ログには以下のようにトレースID情報が含まれています。
05:46:03.853 [http-nio-80-exec-7] INFO otel.RollController trace_id=1-67ee209b-047d7d7e766d57968527e70d@706540b73155c174 span_id=706540b73155c174 trace_flags=01 - Anonymous player is rolling the dice: 6
このログエントリには:
-
trace_id=1-67ee209b-047d7d7e766d57968527e70d
- X-Ray形式のトレースID -
span_id=706540b73155c174
- 現在のスパンID -
trace_flags=01
- トレースフラグ(サンプリングされたかどうかなど)
が含まれています。
3. X-Rayトレースの確認
X-Rayコンソールでトレースを確認すると、トレース内の各セグメントに対応するログを見ることができます。
6. まとめ
今回の実装により、以下のメリットが得られます。
-
効率的なトラブルシューティング:
- トレースからログへのスムーズな移動
- エラーの発生したコンテキストをより明確に把握可能
-
観測可能性の向上:
- アプリケーション内部の状態(ログ)と分散システム全体の状態(トレース)の両方を一貫して監視可能
-
低侵襲な実装:
- アプリケーションコードへの変更は不要
- 設定ファイルの追加と依存関係の更新のみで実現
この方法は、特にマイクロサービスアーキテクチャを採用している環境や、多数のコンテナを運用している場合に、運用効率を大きく向上させます。自動計装とログ・トレース紐付けの組み合わせにより、AWSのマネージドサービスの利点を最大限に活かした可観測性基盤を構築できます。