ログの基礎:ログレベル・ログ機能の流れ・スタックトレース
ログとは何か、アプリがどのようにログを出力するかの流れ、ログレベルの意味、例外時にスタックトレースを出力する理由をゼロから解説
TL;DR
- ログとは「アプリが動作中に記録するメモ」。何が起きたかを後から追跡するために残す
- ログレベル(DEBUG / INFO / WARN / ERROR)で「重要度」を区別し、必要な情報だけを絞り込める
- 例外が起きたとき「スタックトレース」を出力すると、どのコードでエラーが発生したか行単位で特定できる
- ログの流れは「①アプリがイベントを検知 → ②ログライブラリが整形 → ③出力先へ書き出す」の3ステップ
- Java では SLF4J(ログAPIの統一窓口)+ Logback(実際に書き出す実装)の組み合わせが標準
目的
アプリが動いているとき、その中で何が起きているかは外からは見えない。
ログはその「見えない内側」を記録しておく仕組みで、次のような場面で役立つ。
- 本番環境でバグが起きたとき、原因を特定する
- いつ・誰が・何をしたかの操作履歴を残す(監査ログ)
- パフォーマンスが遅いとき、どこに時間がかかっているかを調べる
- 障害発生時に「どの順番で何が起きたか」を時系列で確認する
ログとは何か
ログとは、アプリケーションが動作中に自分で書き出す「記録」のこと。
たとえば、ユーザーがログインしたとき、データベースに接続したとき、エラーが発生したときなど、重要な出来事が起きるたびにアプリが自動でテキストを書き出す。
2026-06-16 12:34:56 [INFO] User login succeeded: userId=42
2026-06-16 12:35:01 [INFO] Database connected: host=db.example.com
2026-06-16 12:35:10 [ERROR] Payment failed: orderId=9876, reason=timeout1行1行が1つのログエントリで、通常は次の情報を含む。
| 項目 | 説明 | 例 |
|---|---|---|
| タイムスタンプ | いつ発生したか | 2026-06-16 12:34:56 |
| ログレベル | どの程度重要か | INFO / ERROR |
| メッセージ | 何が起きたか | User login succeeded |
| 付加情報 | 具体的なデータ | userId=42 |
ログ機能の流れ
アプリがログを出力するまでの流れを順番に追う。
①アプリがイベントを検知する
コードの中で「記録すべき出来事」が起きたとき、開発者がログ出力の命令を書いておく。
// ユーザー登録が成功したタイミングでログを出力するコードの例
async createUser(dto: CreateUserDto) {
const user = await this.prisma.user.create({ data: dto });
this.logger.info(`User created: userId=${user.id}`); // ← ここでログ命令
return user;
}②ログライブラリが情報を整形する
ログ命令を受け取ったログライブラリ(Winston、Log4j、Python の logging モジュールなど)が、タイムスタンプやログレベルを付加して1行のテキストに整形する。
2026-06-16 12:34:56 [INFO] [UsersService] User created: userId=42整形後の形式は設定によって変えられる。テキスト形式のほか、機械が読みやすい JSON 形式にすることも多い。
{"timestamp":"2026-06-16T12:34:56Z","level":"info","context":"UsersService","message":"User created: userId=42"}③出力先へ書き出す
整形されたログを、設定した出力先(Transport)へ書き出す。
| 出力先 | 説明 |
|---|---|
| コンソール(標準出力) | ターミナルやデプロイ環境のログとして表示される |
| ファイル | /var/log/app/app.log などのファイルに追記していく |
| クラウドサービス | AWS CloudWatch、Datadog、GCP Cloud Logging など |
| ログ収集システム | Fluentd、Logstash などへ転送する(次の記事で説明) |
複数の出力先を同時に指定することも一般的。「コンソールにも書き、ファイルにも書く」という設定が可能。
代表的なログライブラリ
Java:SLF4J + Logback
Java のログは「API(窓口)」と「実装(実際に動くエンジン)」が分離している。この2つを理解することが重要。
SLF4J(Simple Logging Facade for Java)
SLF4J はログAPIの統一窓口。実際にログを書き出す機能は持たず、「ログを書きたい」という命令を受け付けるインターフェースだけを提供する。
// アプリコードではSLF4JのAPIだけを使う
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserService {
private static final Logger log = LoggerFactory.getLogger(UserService.class);
public void createUser(String name) {
log.info("Creating user: name={}", name); // SLF4JのAPIを呼ぶ
log.error("Failed to create user", e); // エラーとスタックトレース
}
}アプリコードは SLF4J のインターフェースだけを使う。「実際にどのツールがログを書くか」はアプリの関知しないところで差し替えられる。
Logback
Logback は SLF4J の実装。SLF4J から渡されたログ命令を受け取り、実際にファイルやコンソールに書き出す。
Spring Boot はデフォルトで Logback を使うため、依存関係を追加するだけで動作する。
設定は logback-spring.xml(または logback.xml)で行う。
Logback の主要な3つの概念:
| 概念 | 役割 |
|---|---|
| Logger | アプリのどのクラスのログを、どのレベルから出力するかを管理する |
| Appender | ログの出力先(コンソール / ファイル / ネットワーク)を定義する |
| Encoder | ログのフォーマット(テキスト形式 / JSON形式)を定義する |
設定例(logback-spring.xml):
<configuration>
<!-- コンソールへの出力 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- フォーマット: 日時 [レベル] クラス名 - メッセージ -->
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%level] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- ファイルへの出力(日次ローテーション付き) -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/app/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/var/log/app/app-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>14</maxHistory> <!-- 14日分保持 -->
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%level] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 本番環境:INFO以上をコンソールとファイルの両方へ -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
<!-- このパッケージだけDEBUGレベルを出す設定も可能 -->
<logger name="com.example.users" level="DEBUG" />
</configuration>出力されるログ:
2026-06-16 12:34:56 [INFO ] c.e.users.UserService - Creating user: name=Alice
2026-06-16 12:35:10 [ERROR] c.e.users.UserService - Failed to create user
java.lang.NullPointerException: Cannot read field "id"
at com.example.users.UserService.createUser(UserService.java:24)
at com.example.users.UserController.createUser(UserController.java:15)Node.js:Winston
Node.js の代表的なログライブラリ。SLF4J のような「API と実装の分離」はなく、Winston が直接アプリから使われる。
詳しくは NestJS Winstonでロギングを整える を参照。
言語別ログライブラリの比較
| 言語 | API層 | 実装層 |
|---|---|---|
| Java | SLF4J | Logback(デフォルト)/ Log4j2 |
| Node.js | — | Winston / Pino |
| Python | logging(標準ライブラリ) | structlog など |
ログレベル
ログレベルとは、「このログはどの程度重要か」を示す区分のこと。
すべての出来事を同列に記録すると量が膨大になり、重要な情報を探しにくくなるため、重要度で分類する。
代表的なログレベル
| レベル | 意味 | いつ使うか |
|---|---|---|
| DEBUG | 開発中のデバッグ情報 | 変数の値、処理の詳細な追跡など |
| INFO | 正常な動作の記録 | ユーザー操作、API リクエスト受信、処理完了など |
| WARN | 問題ではないが注意が必要な状態 | 設定が非推奨、リトライが発生した、など |
| ERROR | エラーが発生したが、アプリは継続動作できる | API 呼び出し失敗、DB 書き込みエラーなど |
| FATAL | 致命的なエラー。アプリが継続できない | 起動時の設定ファイル読み込み失敗など |
重要度は DEBUG < INFO < WARN < ERROR < FATAL の順に高くなる。
ログレベルの「しきい値」設定
ログライブラリには「このレベル以上だけ出力する」というしきい値を設定できる。
# 本番環境:INFO以上のみ出力(DEBUGは出さない)
LOG_LEVEL=info
# 開発環境:DEBUGから全部出力(詳しく追いたい)
LOG_LEVEL=debug本番環境で DEBUG レベルまで出力すると、ログ量が膨大になりコストやパフォーマンスに影響するため、環境ごとに切り替えるのが一般的。
例外時のスタックトレースとは
スタックトレースとは
プログラムが例外(エラー)を投げたとき、「どのファイルの何行目で、どの関数を経由してエラーに至ったか」の経路を示したテキストのこと。
Error: Cannot read properties of undefined (reading 'id')
at UsersService.getUser (users.service.ts:24:30)
at UsersController.getUser (users.controller.ts:15:38)
at RouterExplorer.processRoute (router-explorer.js:302:15)
at Layer.handle (router.js:95:5)上から下に向かって読む。
users.service.ts:24:30→ ここでエラーが起きた(24行目30列目)users.controller.ts:15:38→ コントローラーからサービスを呼び出していた- その下はフレームワーク内部の処理
なぜスタックトレースをログに出力するか
エラーメッセージだけでは「何が起きたか」はわかっても「どこで起きたか」がわからない。
スタックトレースがあることで、コードのどのファイルの何行目が原因かを即座に特定できる。
// スタックトレースを含めてエラーログを出力する例
try {
const user = await this.prisma.user.findUnique({ where: { id } });
return user.id; // user が null のとき例外が発生する
} catch (error) {
this.logger.error('Failed to get user', error.stack); // ← error.stack でスタックトレースを渡す
throw error;
}ログに出力された例:
2026-06-16 12:35:10 [ERROR] Failed to get user
Error: Cannot read properties of undefined (reading 'id')
at UsersService.getUser (users.service.ts:24:30)
at UsersController.getUser (users.controller.ts:15:38)スタックトレースを出力するのはERROR以上が原則
DEBUG/INFO ログにスタックトレースを付けることは通常しない。
スタックトレースが必要なのは「予期しないエラーが起きたとき」だけ。
WARN でも原因追跡が必要なら付けることはあるが、基本は ERROR / FATAL レベルで使う。
よくある落とし穴
ログを出さなさすぎる
「動いているときは必要ない」と思ってログをほとんど書かないと、本番で問題が起きたときに原因が全くわからなくなる。少なくとも「処理の入口と出口」「エラー発生時」はログを残す習慣をつける。
ログに機密情報を含める
パスワード、トークン、クレジットカード番号などをログに出力すると、ログファイルが流出したときに大きなリスクになる。ログ出力前に機密フィールドをマスクする。
// 悪い例
this.logger.info(`Login attempt: email=${dto.email}, password=${dto.password}`);
// 良い例
this.logger.info(`Login attempt: email=${dto.email}`); // passwordはログに含めないエラーログに error.stack を渡し忘れる
error.message だけ渡すとスタックトレースが出ず、どこで起きたかがわからなくなる。
error.stack を明示的に渡すか、ログライブラリに Error オブジェクトごと渡す。