1. test 1
  2. test 2
  3. test 3
  4. test 4
  5. test 5
  6. test 6
  7. test 7
コース一覧
Spring Boot入門
ログとリクエストの追跡

Spring Boot入門

Java で Web API サーバーを作るための Spring Boot の入門コースです。DI コンテナ、REST コントローラ、リクエスト検証、レイヤ分割、Spring Data JPA、例外ハンドリング、テストまでを扱います。毎レッスン、Spring が裏側でやっている判断そのものを pure Java の関数として自分で書き、フレームワークが何を肩代わりしてくれているのかを手触りで理解できる構成にしました。約 12 時間 (1 日 30 分 × 24 日) で 48 レッスンを修了できます。

1
Spring Bootとは何か
01. Spring Bootで何が作れるのか10分
02. サーブレットからフレームワークへ15分
03. ルーティング表という発想15分
04. プロジェクトの構成10分
05. 起動のしくみと自動設定15分
06. パスのマッチング15分
2
DIコンテナ
01. newで書くと何が困るのか15分
02. コンストラクタインジェクション15分
03. インターフェースへの依存15分
04. @Componentと@Bean15分
05. 自作のDIコンテナ20分
06. Beanのスコープとライフサイクル15分
3
RESTコントローラ
01. RESTという設計方針15分
02. @RestControllerの役割15分
03. @GetMappingとパス変数15分
04. クエリパラメータの受け取り15分
05. @PostMappingとリクエストボディ15分
06. ステータスコードの選び方15分
07. ResponseEntityで組み立てる15分
4
リクエストとレスポンスの変換
01. JSONとオブジェクトの往復15分
02. DTOとエンティティを分ける15分
03. recordでDTOを書く15分
04. 入力検証の基本15分
05. 検証エラーをまとめて返す15分
06. アノテーションによる検証15分
5
レイヤ構成とビジネスロジック
01. 3層アーキテクチャ10分
02. @Serviceに置くもの15分
03. 状態遷移をルールにする15分
04. 料金計算のような業務ロジック15分
05. @Transactionalとは何か15分
06. トランザクションの巻き戻し15分
6
データアクセス
01. @Entityとテーブルの対応15分
02. リポジトリという抽象15分
03. メソッド名からクエリを作る20分
04. 検索条件の組み立て15分
05. ページングと並び替え15分
06. N+1問題15分
7
例外処理と横断的関心事
01. 例外をHTTPに変換する15分
02. @ExceptionHandlerと@ControllerAdvice15分
03. エラーレスポンスの形を決める15分
04. フィルタとインターセプタ15分
05. ログとリクエストの追跡15分
06. 設定値の外部化15分
8
テストと仕上げ
01. テストの種類と使い分け15分
02. Serviceの単体テスト15分
03. モックで依存を差し替える15分
04. MockMvcでエンドポイントを試す15分
05. 本番へ出すための次の一歩10分

ログとリクエストの追跡

このレッスンでは、障害が起きたときに原因へたどり着くためのログの書き方と、1 本のリクエストを最初から最後まで追いかけるためのトレース識別子を扱います。

System.out.println ではなく Logger を使います

学習中は System.out.println で足りますが、本番のアプリでは使いません。出力先を切り替えられず、重要度による絞り込みもできず、いつどのスレッドで起きたのかも残らないからです。Spring Boot は既定で SLF4J というログの窓口と Logback という実装を持っているので、次のように書きます。

Java

@Service public class UserService { private static final Logger log = LoggerFactory.getLogger(UserService.class); public User find(long id) { log.debug("finding user. id={}", id); return repository.findById(id) .orElseThrow(() -> new UserNotFoundException(id)); } }

中括弧のプレースホルダを使う点が要点です。文字列を足し算でつないでしまうと、そのレベルが無効で出力されない場合でも連結の処理だけは走ります。プレースホルダなら、出力すると決まってから初めて組み立てられます。

レベルは絞り込みのための道具です

ログレベルは、重要度の目盛りであると同時に、出す量を切り替えるつまみです。よく使う 5 段階の目安は次の通りです。

レベル使いどころ
ERROR処理が失敗し、人の対応が要る
WARN続行はできたが、放置すると問題になりそう
INFO業務上の節目。注文の確定、利用者の登録など
DEBUG開発時に流れを追うための詳細
TRACE変数の中身まで含む最も細かい記録

設定した水準以上のものだけが出力されます。本番は INFO、調査中だけ一時的に DEBUG、という運用が一般的です。ここで大事なのは、あとから DEBUG に上げれば見られる状態にしておくことです。そもそも書いていないログは、どんな設定にしても出てきません。

構造化ログにすると機械が読めます

ユーザー 7 が見つかりませんでした という 1 行は、人には読めても機械には読めません。あとから「利用者 7 に関するログだけ集めたい」と思っても、正規表現で本文を切り出すはめになります。そこで、ログを JSON など決まった構造で出す構造化ログが使われます。

JSON

{"level":"INFO","traceId":"trace-000042","method":"GET","path":"/users/7","message":"user not found"}

こうしておくと、ログ基盤側で traceId や path を条件に検索できます。人が読む見た目より、あとで絞り込めることを優先する考え方です。

トレース識別子で 1 本のリクエストをつなぎます

本番のサーバーは同時に何十本ものリクエストを処理しているので、ログは複数のリクエストの行が入り混じって並びます。この中から「あの 1 回の呼び出し」に属する行だけを抜き出すために、リクエストごとに固有の識別子を振り、すべての行に載せます。これがトレース識別子です。

問題は、その識別子をどうやって Controller から Service、Repository まで持ち回るかです。すべてのメソッドの引数に足すのは現実的ではありません。ここで使われるのが MDC という仕組みです。MDC はスレッドごとに紐づく小さな入れ物で、入口のフィルタで値を入れておくと、同じスレッドで走るあらゆるログ出力からその値を参照できます。

Java

public class TraceIdFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { MDC.put("traceId", newTraceId()); try { chain.doFilter(req, res); } finally { MDC.remove("traceId"); // 後始末を忘れない } } }

最後の finally が要点です。サーバーのスレッドは使い回されるので、消し忘れると次のリクエストが前の識別子を引き継いでしまい、まったく無関係な行が同じ 1 本のリクエストに見えてしまいます。

演習で書くもの

Spring では前のレッスンで学んだフィルタが識別子を作り、MDC を通じて各行に埋め込みます。ここではその 1 行を組み立てる部分を書きます。テストを安定させるため、現在時刻や乱数は使わず、渡された連番だけから識別子を作ってください。

要件

  1. レベル、角括弧で囲んだ識別子、メソッド名、パスを半角空白でつないだ 1 行を返すこと
  2. 識別子は trace- に続けて連番を 6 桁のゼロ埋めで書き、10 の倍数のときだけレベルを WARN にすること
  3. メソッド名かパスが空、または連番が負のときは INVALID を返すこと

入出力例

buildLogLine("get", "/users", 1) → "INFO [trace-000001] GET /users" buildLogLine("POST", "users", 42) → "INFO [trace-000042] POST /users" buildLogLine("delete", " /users/7 ", 30) → "WARN [trace-000030] DELETE /users/7" buildLogLine("GET", "/health", 0) → "WARN [trace-000000] GET /health" buildLogLine("", "/users", 3) → "INVALID" buildLogLine("GET", "/users", -1) → "INVALID" buildLogLine("put", "/orders/12", 999999) → "INFO [trace-999999] PUT /orders/12"

ヒント

6 桁のゼロ埋めは String.format("%06d", sequence) で作れます

0 は 10 の倍数として扱うので、0 のときは WARN になります

パスの補正は startsWith("/") で判定し、必要なら先頭にスラッシュを足します

生田 陸人
監修生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
編集 ゆめさく編集部·公開 2026/08/09

関連レッスン

  • 設定値の外部化

    設定ファイルの key=value を解析し、既定値を適用します。

  • テストの種類と使い分け

    @SpringBootTest と @WebMvcTest の違いを確認します。

  • フィルタとインターセプタ

    連なった処理を順に通すチェーンを実装します。

  • エラーレスポンスの形を決める

    一貫したエラー本文を組み立てられるようになります。

このレッスンに出てくる用語

意味があいまいなまま進んだ語は、ここから読み直せます。

  • リクエストWeb 通信の基本単位、ブラウザの問い合わせとサーバーの返答
  • スレッド1 つの質問に紐づく返信の集まり
  • プレースホルダSQL の `?` 部分にあとから値を入れる仕組み
  • 処理計算や代入を表す長方形
  • 変数データに名前をつけて参照する仕組み
  • applicationJSON 本文を送るときの Content-Type
  • サーバークライアント(ブラウザなど)がリクエストを送り、サーバーがレスポンスを返す。
  • メソッドクラスに属する関数
main.java
学習モード
エディタを読み込んでいます

メモ

ログとリクエストの追跡

⌘S で保存