Skip to content

コア概念

DD のコア概念を理解することは、本ライブラリを効率的に使用するための基礎となります。本章では Logger 体系、フィールドシステム、処理パイプライン、インターフェース階層について説明します。

Logger 体系

DD のログ記録は 3 つのコアタイプを中心に展開します:

text
Logger(ロガー)

  ├── 直接使用 → logger.Info("message")

  └── WithFields() → LoggerEntry(プリセットフィールド付き Entry)

                        └── entry.Info("message")  // プリセットフィールドを自動付与

Logger

Logger はコアとなるロガーで、dd.New() で作成します:

go
logger, err := dd.New(dd.DefaultConfig())
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

logger.Info("サービス起動")
logger.InfoWith("リクエスト処理",
    dd.String("method", "GET"),
    dd.Int("status", 200),
)

各 Logger は独立した設定、出力先、セキュリティフィルター、ライフサイクルを持ち、異なるモジュール間で安全に共有できます。

LoggerEntry

LoggerEntryWithFields() で作成される、不変のプリセットフィールドコンテナです:

go
// プリセットフィールド付き Entry を作成
requestLog := logger.WithFields(
    dd.String("service", "user-api"),
    dd.String("version", "2.1.0"),
)

// 各呼び出しでプリセットフィールドが自動付与される
requestLog.Info("サービス起動")
// 出力:... サービス起動 service=user-api version=2.1.0

requestLog.InfoWith("ユーザーログイン",
    dd.String("user", "alice"),
)
// 出力:... ユーザーログイン service=user-api version=2.1.0 user=alice

不変設計

WithFields() を呼び出すたびに新しい LoggerEntry が作成され、元の Entry は影響を受けません。つまり、異なる goroutine で安全に同じ Entry を再利用できます。

グローバルロガー

DD はグローバルロガーを提供し、シンプルなシナリオや迅速なプロトタイピングに適しています:

go
// パッケージレベル関数を直接使用(グローバル Logger 経由)
dd.Info("グローバルログ")

// 以下と同等
dd.Default().Info("グローバルログ")

フィールドシステム

Field 型

Field は構造化ログの基本単位で、キーと値のペアで構成されます:

go
// フィールドコンストラクタは全ての一般的な型をカバー
dd.String("method", "GET")           // 文字列
dd.Int("status", 200)                // 整数
dd.Float64("latency", 0.123)         // 浮動小数点
dd.Bool("success", true)             // 真偽値
dd.Duration("elapsed", 150*time.Millisecond) // 所要時間
dd.Time("timestamp", time.Now())     // タイムスタンプ
dd.Err(err)                          // エラー(key は "error")
dd.ErrWithKey("db_error", err)       // エラー(カスタム key)
dd.Any("data", payload)              // 任意の型

フィールドのチェーン渡し

フィールドは Logger、Entry の間で階層的に渡すことができます:

go
// 第 1 層:サービスレベルフィールド
serviceLog := logger.WithFields(
    dd.String("service", "api-gateway"),
)

// 第 2 層:リクエストレベルフィールド(サービスレベルに追加)
requestLog := serviceLog.WithFields(
    dd.String("request_id", "req-001"),
    dd.String("path", "/api/users"),
)

// 第 3 層:実際のログ(さらにフィールドを追加)
requestLog.InfoWith("処理完了",
    dd.Int("status", 200),
    dd.Duration("elapsed", 50*time.Millisecond),
)
// 出力に含まれる:service=api-gateway request_id=req-001 path=/api/users status=200 elapsed=50ms

ログ処理パイプライン

各ログは以下の処理フローを経ます:

text
ユーザーが logger.InfoWith("msg", fields...) を呼び出し


  ① レベルチェック ─── レベルが無効 → 即座に返却(オーバーヘッドなし)


  ② セキュリティフィルタリング ─── メッセージとフィールドの機密データ → [REDACTED]


  ③ コンテキスト抽出 ── 登録済みエクストラクタで静的/グローバルフィールドを付与(context.Background() で呼び出され、リクエストスコープの TraceID は読み取れない)


  ④ BeforeLog フック


  ⑤ フォーマット ──── テキストフォーマット または JSON フォーマット


  ⑥ セキュリティサイズ制限 ─── Security.MaxMessageSize を超えると切り詰め(0 は制限なし)


  ⑦ 書き込み ────── 1 つ以上の Writer に出力


  ⑧ AfterLog フック


  ⑨ Fatal 処理 ── LevelFatal のみ:非同期で Logger を Close し(最長 5s 待機、OnClose フックを発火し writer を flush)、その後 os.Exit(1) またはカスタム FatalHandler を呼び出し

パフォーマンス設計

レベルチェック(ステップ ①)はアトミック操作を使用し、ロック不要でほぼゼロオーバーヘッドです。セキュリティフィルタリング(ステップ ②)はタイムアウト保護付きで、長時間のメインフロー阻断を防止します(大きい入力は goroutine + タイムアウトで最悪約 50ms 以内に返すことを保証)。Fatal 処理(ステップ ⑨)は非同期で Logger の Close(flush と OnClose フックを含む)を発火し、最長 5s 待機します;ユーザーの main の defer は依然実行されませんが、Logger 自体の Close は呼び出されます。

インターフェース階層

DD は 4 つのインターフェースを定義し、精密な依存性注入をサポートします:

text
CoreLogger                    ← 基本ログ:Debug/Info/Warn/Error/Fatal + WithFields

    ├── LevelLogger           ← レベル管理:GetLevel/SetLevel/IsLevelEnabled(CoreLogger を埋め込み)

    └── ConfigurableLogger    ← 設定管理:Writer/セキュリティ/コンテキスト/フック(CoreLogger を埋め込み)

LogProvider                   ← 完全機能:全メソッドを含む独立したフラットインターフェース
go
// 基本ログだけでいい?CoreLogger を注入
type Service struct {
    log dd.CoreLogger
}

// レベルを動的に調整したい?LevelLogger を注入
type Handler struct {
    log dd.LevelLogger
}

ベストプラクティス

コンストラクタで最小限必要なインターフェースを受け取るようにしてください。具体的な型ではなくインターフェースを受け取ることで、コードがテストしやすく、柔軟になります。

スレッドセーフモデル

DD のコア設計原則:複数 goroutine で安全に使用可能、追加の同期は不要

コンポーネント安全メカニズム
Loggerすべてのメソッドが安全に並行呼び出し可能
LoggerEntry不変、作成後は読み取り専用
ConfigClone() メソッドで安全なコピー
Writersアトミックポインタ、ロックフリー読み取り
SensitiveDataFilter読み書き分離、独立した goroutine
HookRegistryRWMutex で登録と読み取りを保護(Logger は atomic.Value でそのポインタを保持)
go
// 安全:複数の goroutine が同じ Logger を共有
var logger *dd.Logger  // 一度だけ初期化

func handleRequest(w http.ResponseWriter, r *http.Request) {
    // 安全:並行呼び出し
    logger.InfoWith("リクエスト到着",
        dd.String("path", r.URL.Path),
        dd.String("method", r.Method),
    )
}

出力先体系

DD は 3 種類の出力先をサポートし、自由に組み合わせ可能:

go
logger, err := dd.New(dd.Config{
    Targets: []dd.OutputTarget{
        dd.ConsoleOutput(),                    // コンソール
        dd.FileOutput("logs/app.log"),         // ファイル(自動ローテーション)
        dd.CustomOutput(customWriter),         // カスタム io.Writer
    },
})
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

組み込み Writer コンポーネント:

コンポーネント用途
FileWriterファイル書き込み + サイズ/時間ローテーション + 圧縮
BufferedWriterバッファ書き込み、I/O 回数を削減
MultiWriterマルチ出力先ディスパッチ、複数の Writer に書き込み

次のステップ