awesome-hacks
Docs

ログの基礎:ログレベル・ログ機能の流れ・スタックトレース

ログとは何か、アプリがどのようにログを出力するかの流れ、ログレベルの意味、例外時にスタックトレースを出力する理由をゼロから解説

最終更新:2026/06/16

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=timeout

1行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層実装層
JavaSLF4JLogback(デフォルト)/ Log4j2
Node.jsWinston / Pino
Pythonlogging(標準ライブラリ)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)

上から下に向かって読む。

  1. users.service.ts:24:30 → ここでエラーが起きた(24行目30列目)
  2. users.controller.ts:15:38 → コントローラーからサービスを呼び出していた
  3. その下はフレームワーク内部の処理

なぜスタックトレースをログに出力するか

エラーメッセージだけでは「何が起きたか」はわかっても「どこで起きたか」がわからない。
スタックトレースがあることで、コードのどのファイルの何行目が原因かを即座に特定できる。

// スタックトレースを含めてエラーログを出力する例
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 オブジェクトごと渡す。


関連