1. Home
  2. Golang
  3. GuideToBecomingGoDeveloper
  4. Logging
  5. log/slog - Golang learning step 6-1

log/slog - Golang learning step 6-1

  • 公開日
  • カテゴリ:Logging
  • タグ:Golang,roadmap.sh,学習メモ
log/slog - Golang learning step 6-1

roadmap.sh > Go > Logging > log/slog の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. 参考 URL
  3. log パッケージ
    1. 主な特徴
    2. 主な関数
    3. 基本的なログ出力
    4. プレフィックスの設定
    5. 出力先の変更
    6. フラグによる出力形式の制御
    7. カスタムロガーの作成
    8. Fatal と Panic の使用
    9. 実践的な使用例
  4. log/slog パッケージ
    1. 主な特徴
    2. 主な構成要素
    3. 基本的なログ出力
    4. ロガーの設定とハンドラーの使用
    5. カスタムハンドラーのオプション設定
    6. 構造化された属性グループの使用
    7. エラーハンドリングとの統合
    8. ロガーのコンテキスト付き使用
    9. レベル別ロギングの実践的な例
    10. ファイル出力との組み合わせ
  5. log と log/slog の違い

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

参考 URL

log パッケージ

log パッケージは、Go の標準ライブラリに含まれるシンプルなログ出力機能を提供するパッケージです。プログラムの動作確認やエラーの記録に便利です。

主な特徴

  1. シンプルなログ出力
    • 標準出力や標準エラー出力にログメッセージを出力する
    • デフォルトで標準エラー出力に出力
  2. 使い方がシンプル
    • 初心者でもすぐに利用できる
    • 日時が自動的に付与される
  3. 高度な機能はなし
    • 基本的なログ出力のみで、構造化ログや高度なカスタマイズはできない
    • ログレベル(INFO、ERRORなど)の概念がない

主な関数

  • log.Println: シンプルにログを出力します。
  • log.Printf: 書式付きのログを出力します。
  • log.Fatal: メッセージを出力した後にプログラムを終了します。
  • log.Panic: メッセージを出力した後にパニックを発生させます。

基本的なログ出力

package main

import "log"

func main() {
    // 基本的なログ出力
    log.Println("アプリケーションを開始します")  // 2024/12/09 10:30:45 アプリケーションを開始します
    
    // フォーマット付きのログ出力
    count := 3
    log.Printf("現在の数値: %d", count)    // 2024/12/09 10:30:45 現在の数値: 3
}

出力:

2024/12/09 22:42:23 アプリケーションを開始します
2024/12/09 22:42:23 現在の数値: 3

プレフィックスの設定

ログメッセージの前に固定の文字列を追加できます

package main

import "log"

func main() {
    // プレフィックスの設定
    log.SetPrefix("【INFO】")
    log.Println("システムチェックを開始")  // 【INFO】2024/12/09 10:30:45 システムチェックを開始
    
    // プレフィックスの変更
    log.SetPrefix("【ERROR】")
    log.Println("エラーが発生しました")    // 【ERROR】2024/12/09 10:30:45 エラーが発生しました
}

出力:

【INFO】2024/12/09 22:44:06 システムチェックを開始
【ERROR】2024/12/09 22:44:06 エラーが発生しました

出力先の変更

ログの出力先をファイルに変更できます

package main

import (
    "log"
    "os"
)

func main() {
    // ログファイルを作成
    file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatal("ログファイルを開けません:", err)
    }
    defer file.Close()

    // 出力先をファイルに設定
    log.SetOutput(file)
    
    log.Println("このメッセージはファイルに書き込まれます")
}

出力: app.log

2024/12/09 22:46:28 このメッセージはファイルに書き込まれます

フラグによる出力形式の制御

ログの出力形式をカスタマイズできます

package main

import (
    "log"
)

func main() {
    // 日時のみを表示
    log.SetFlags(log.Ldate)
    log.Println("日付のみ")  // 2024/12/09 メッセージ

    // 時刻のみを表示
    log.SetFlags(log.Ltime)
    log.Println("時刻のみ")  // 10:30:45 メッセージ

    // 日時とファイル名、行番号を表示
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
    log.Println("詳細情報付き")  // 2024/12/09 10:30:45 main.go:15: メッセージ
}

出力:

2024/12/09 日付のみ
22:48:20 時刻のみ
2024/12/09 22:48:20 main.go:57: 詳細情報付き

カスタムロガーの作成

独自のロガーインスタンスを作成できます

package main

import (
    "log"
    "os"
)

func main() {
    // 情報ログ用のロガー
    infoLog := log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime)
    
    // エラーログ用のロガー
    errorLog := log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
    
    infoLog.Println("これは情報ログです")
    // INFO: 2024/12/09 10:30:45 これは情報ログです
    
    errorLog.Println("これはエラーログです")
    // ERROR: 2024/12/09 10:30:45 main.go:14: これはエラーログです
}

出力:

INFO: 2024/12/09 22:49:34 これは情報ログです
ERROR: 2024/12/09 22:49:34 main.go:70: これはエラーログです

Fatal と Panic の使用

プログラムを停止させる重大なエラーの場合に使用します

package main

import "log"

func main() {
    // Fatal - ログを出力して os.Exit(1) を呼び出す
    if somethingBadHappens() {
        log.Fatal("致命的なエラーが発生しました")
    }
    
    // Panic - ログを出力してパニックを発生させる
    if unexpectedCondition() {
        log.Panic("予期せぬエラーが発生しました")
    }
}

func somethingBadHappens() bool {
    return true
}

func unexpectedCondition() bool {
    return false
}

出力:

# Fatal
2024/12/09 22:51:01 致命的なエラーが発生しました
exit status 1

# Panic
2024/12/09 22:51:26 予期せぬエラーが発生しました
panic: 予期せぬエラーが発生しました

goroutine 1 [running]:
.
.
.

実践的な使用例

エラーハンドリングとログ出力を組み合わせた例

package main

import (
    "log"
    "os"
)

func main() {
    // ログの設定
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
    
    // ファイル操作の例
    file, err := os.Open("存在しないファイル.txt")
    if err != nil {
        log.Printf("ファイルオープンエラー: %v", err)
        return
    }
    defer file.Close()
    
    // 処理続行...
    log.Println("ファイルの処理を開始します")
}

出力:

2024/12/09 22:54:36 main.go:99: ファイルオープンエラー: open 存在しないファイル.txt: no such file or directory

log/slog パッケージ

slog パッケージは、Go 1.21 から標準ライブラリに追加された、構造化ログをサポートするパッケージです。従来の log よりも機能が豊富で、クラウドや分散システム向けに最適化されています。

主な特徴

  1. 構造化ログのサポート
    • メッセージだけでなく、キーと値のペア形式でログ情報を記録します。
  2. 柔軟なカスタマイズ
    • ログのフォーマット(JSON形式など)や出力先を自由に設定できます。
  3. パフォーマンス最適化
    • 効率的にログを記録し、高負荷のアプリケーションにも対応。
  4. ロガーの階層構造
    • コンテキストごとに異なるロガーを設定可能です。
  5. ログレベル
    • ログレベル(Debug、Info、Warn、Error)をサポート

主な構成要素

  • Logger: ログを記録する中心的なオブジェクト。
  • Handler: ログの出力形式や出力先を制御する部分。

基本的なログ出力

package main

import "log/slog"

func main() {
    // 基本的なログ出力
    slog.Info("アプリケーションを開始します")
    
    // 属性(キーと値のペア)を持つログ出力
    slog.Info("ユーザーがログイン",
        "username", "taro",
        "ip", "192.168.1.1",
        "loginTime", time.Now(),
    )
    
    // 異なるログレベルの使用
    slog.Debug("デバッグ情報です")
    slog.Info("通常の情報です")
    slog.Warn("警告です")
    slog.Error("エラーが発生しました")
}

出力:

2024/12/09 22:57:57 INFO アプリケーションを開始します
2024/12/09 22:57:57 INFO ユーザーがログイン username=taro ip=192.168.1.1 loginTime=2024-12-09T22:57:57.388+09:00
2024/12/09 22:57:57 INFO 通常の情報です
2024/12/09 22:57:57 WARN 警告です
2024/12/09 22:57:57 ERROR エラーが発生しました

ロガーの設定とハンドラーの使用

異なる出力形式を設定できます

package main

import (
    "log/slog"
    "os"
)

func main() {
    // JSONハンドラーの設定
    jsonHandler := slog.NewJSONHandler(os.Stdout, nil)
    logger := slog.New(jsonHandler)
    slog.SetDefault(logger)

    // JSON形式でログが出力されます
    slog.Info("サーバー起動",
        "port", 8080,
        "env", "production",
    )
    
    // テキストハンドラーの設定
    textHandler := slog.NewTextHandler(os.Stdout, nil)
    logger = slog.New(textHandler)
    slog.SetDefault(logger)

    // テキスト形式でログが出力されます
    slog.Info("サーバー起動",
        "port", 8080,
        "env", "production",
    )
}

出力:

{"time":"2024-12-09T22:58:37.947658+09:00","level":"INFO","msg":"サーバー起動","port":8080,"env":"production"}
time=2024-12-09T22:58:37.947+09:00 level=INFO msg=サーバー起動 port=8080 env=production

カスタムハンドラーのオプション設定

package main

import (
    "log/slog"
    "os"
)

func main() {
    opts := &slog.HandlerOptions{
        Level: slog.LevelDebug,  // ログレベルの設定
        AddSource: true,         // ソースコードの位置情報を追加
    }
    
    handler := slog.NewJSONHandler(os.Stdout, opts)
    logger := slog.New(handler)
    slog.SetDefault(logger)
    
    slog.Debug("デバッグ情報が表示されます")
    slog.Info("ファイルの処理を開始", "filename", "test.txt")
}

出力:

{"time":"2024-12-09T22:59:54.517853+09:00","level":"DEBUG","source":{"function":"main.main0203","file":"/path/to/main.go","line":163},"msg":"デバッグ情報が表示されます"}
{"time":"2024-12-09T22:59:54.518227+09:00","level":"INFO","source":{"function":"main.main0203","file":"/path/to/main.go","line":164},"msg":"ファイルの処理を開始","filename":"test.txt"}

構造化された属性グループの使用

package main

import "log/slog"

func main() {
    // 属性グループを使用したログ出力
    slog.Info("データベース接続",
        slog.Group("database",
            slog.String("host", "localhost"),
            slog.Int("port", 5432),
            slog.String("user", "admin"),
        ),
        slog.Group("settings",
            slog.Bool("ssl", true),
            slog.Int("timeout", 30),
        ),
    )
}

出力:

2024/12/09 23:01:21 INFO データベース接続 database.host=localhost database.port=5432 database.user=admin settings.ssl=true settings.timeout=30

エラーハンドリングとの統合

package main

import (
    "errors"
    "log/slog"
)

func main() {
    err := doSomething()
    if err != nil {
        slog.Error("操作に失敗しました",
            "error", err,
            "operation", "doSomething",
            "retry", false,
        )
    }
}

func doSomething() error {
    return errors.New("何か問題が発生しました")
}

出力:

2024/12/09 23:02:31 ERROR 操作に失敗しました error=何か問題が発生しました operation=doSomething retry=false

ロガーのコンテキスト付き使用

package main

import (
    "log/slog"
    "context"
)

func main() {
    // コンテキストを持つロガーの作成
    ctx := context.Background()
    logger := slog.With(
        "service", "user-api",
        "version", "1.0.0",
    )
    
    // このロガーを使用する全てのログに上記の属性が付加されます
    logger.InfoContext(ctx, "リクエストを受信",
        "method", "GET",
        "path", "/users",
    )
}

出力:

2024/12/09 23:03:46 INFO リクエストを受信 service=user-api version=1.0.0 method=GET path=/users

レベル別ロギングの実践的な例

package main

import (
    "log/slog"
    "os"
)

func main() {
    // 開発環境用の設定
    opts := &slog.HandlerOptions{
        Level: slog.LevelDebug,
    }
    
    if os.Getenv("ENV") == "production" {
        // 本番環境ではInfoレベル以上のみ出力
        opts.Level = slog.LevelInfo
    }
    
    handler := slog.NewJSONHandler(os.Stdout, opts)
    logger := slog.New(handler)
    slog.SetDefault(logger)
    
    // 異なるレベルでのログ出力
    slog.Debug("詳細なデバッグ情報") // 開発環境でのみ表示
    slog.Info("通常の処理情報")    // 常に表示
    slog.Warn("警告メッセージ")    // 常に表示
    slog.Error("エラー情報")      // 常に表示
}

出力:

{"time":"2024-12-09T23:04:45.567226+09:00","level":"DEBUG","msg":"詳細なデバッグ情報"}
{"time":"2024-12-09T23:04:45.567471+09:00","level":"INFO","msg":"通常の処理情報"}
{"time":"2024-12-09T23:04:45.567476+09:00","level":"WARN","msg":"警告メッセージ"}
{"time":"2024-12-09T23:04:45.567479+09:00","level":"ERROR","msg":"エラー情報"}

ファイル出力との組み合わせ

package main

import (
    "log/slog"
    "os"
)

func main() {
    // ログファイルを開く
    file, err := os.OpenFile(
        "app.log",
        os.O_CREATE|os.O_WRONLY|os.O_APPEND,
        0666,
    )
    if err != nil {
        slog.Error("ログファイルを開けません", "error", err)
        return
    }
    defer file.Close()
    
    // ファイルへのJSON形式での出力を設定
    handler := slog.NewJSONHandler(file, nil)
    logger := slog.New(handler)
    slog.SetDefault(logger)
    
    // アプリケーションのログ出力
    slog.Info("アプリケーション開始",
        "env", os.Getenv("ENV"),
        "pid", os.Getpid(),
    )
}

log と log/slog の違い

特徴loglog/slog
提供時期初期(Go 1.0から)Go 1.21から
ログ形式テキスト形式のみ構造化ログ(JSON形式など対応)
カスタマイズ性限定的柔軟で高度なカスタマイズが可能
構造化ログ非対応対応
推奨用途小規模なアプリケーションやデバッグ大規模アプリケーションやクラウド環境
パフォーマンス基本的最適化済み
ログレベルなしDebug/Info/Warn/Error
エラーハンドリング基本的構造化されたエラー情報

slog パッケージは、モダンなアプリケーションやクラウド環境での利用を目的としており、複雑な要件に適しています。小規模なプロジェクトでは log で十分な場合もありますが、構造化ログが必要な場面では slog を検討する価値があります。

まとめ

  • logパッケージとlog/slogパッケージは、それぞれ異なる用途と特徴を持つGoの標準ログパッケージ
  • logパッケージは、シンプルで基本的なログ出力機能に特化
  • log/slogパッケージは、構造化ログと高度なカスタマイズ機能を提供
  • logパッケージの主な特徴
    • シンプルな使用方法
    • 基本的なログ出力
    • プレフィックスやフラグによる簡易的なカスタマイズ機能
    • 小規模アプリケーションに最適
  • log/slogパッケージの主な特徴
    • 構造化ログのサポート
    • JSON形式での出力対応
    • 複数のログレベル(Debug/Info/Warn/Error)
    • コンテキストとの統合
    • 大規模アプリケーションやクラウド環境に最適
  • 使い分けのポイント
    • シンプルな用途はlogパッケージ
    • 高度な要件や大規模システムはlog/slogパッケージ
    • 構造化ログが必要な場合はlog/slogパッケージ


[Next] Step 6-2: Zap

[Prev] Step 5-4: Gorilla

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級