Skip to content

ファイル出力とローテーション

DD は柔軟なファイル出力機能を提供し、自動ローテーション、バッファ書き込み、マルチ出力先ディスパッチをサポートし、本番環境での使用に適しています。

クイックスタート

基本的なファイル出力

go
package main

import (
    "log"

    "github.com/cybergodev/dd"
)

func main() {
    logger, err := dd.New(dd.Config{
        Targets: []dd.OutputTarget{
            dd.FileOutput("logs/app.log"),
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    defer logger.Close()

    logger.Info("ログがファイルに書き込まれます") // 出力:logs/app.log に書き込まれる
}

コンソール + ファイル デュアル出力

go
logger, err := dd.New(dd.Config{
    Targets: []dd.OutputTarget{
        dd.ConsoleOutput(),
        dd.FileOutput("logs/app.log"),
    },
})
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

FileWriter ローテーション設定

FileWriter はサイズによる自動ローテーションと、時間による古いファイルのクリーンアップをサポートします:

デフォルト設定

go
cfg := dd.DefaultFileWriterConfig()
// MaxSizeMB:   100   — 単一ファイルの最大 100MB
// MaxAge:      30 * 24 * time.Hour  — 30 日間保持
// MaxBackups:  10    — 最大 10 バックアップ保持
// Compress:    false — 圧縮なし

カスタムローテーションポリシー

go
// 高トラフィックサービス:小さいファイル、高速ローテーション
fwCfg := dd.DefaultFileWriterConfig()
fwCfg.MaxSizeMB = 50                // 50MB でローテーション
fwCfg.MaxBackups = 20               // 20 バックアップ保持
fwCfg.MaxAge = 7 * 24 * time.Hour   // 7 日でクリーンアップ
fwCfg.Compress = true      // 古いファイルを圧縮

fw, err := dd.NewFileWriter("logs/app.log", fwCfg)
if err != nil {
    log.Fatal(err)
}
logger, err := dd.New(dd.Config{
    Targets: []dd.OutputTarget{dd.CustomOutput(fw)},
})
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

JSON フォーマットのログファイル

go
logger, err := dd.New(dd.Config{
    Format: dd.FormatJSON,
    Targets: []dd.OutputTarget{
        dd.FileOutput("logs/app.json"),
    },
})
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

ローテーション後のファイル命名規則:

text
logs/app.log           ← 現在のログ
logs/app_log_1.log     ← 1 回目のローテーション(最新のバックアップ)
logs/app_log_2.log     ← さらに古いバックアップ
logs/app_log_1.log.gz  ← Compress 有効時に古いバックアップが .gz に圧縮される

圧縮とバックアップは共存しない

Compress を有効にすると、圧縮はローテーション後に別の goroutine で非同期に行われます;圧縮完了時に元の .log バックアップは .log.gzリネームされ、両者は共存しません。

BufferedWriter バッファ書き込み

高スループット環境では、BufferedWriter を使用して I/O 回数を削減:

go
// ファイル Writer を作成
fw, err := dd.NewFileWriter("logs/app.log", dd.DefaultFileWriterConfig())
if err != nil {
    log.Fatal(err)
}

// バッファ Writer でラップ
bwCfg := dd.DefaultBufferedWriterConfig()
// BufferSize: 1024  — 1KB バッファ
// FlushTime:  100ms — 100ms 自動フラッシュ

bw, err := dd.NewBufferedWriter(fw, bwCfg)
if err != nil {
    log.Fatal(err)
}

logger, err := dd.New(dd.Config{
    Targets: []dd.OutputTarget{dd.CustomOutput(bw)},
})
if err != nil {
    log.Fatal(err)
}
defer logger.Close() // Close 時に自動 Flush

チューニングのヒント

シナリオBufferSizeFlushTime説明
低遅延要件51250ms高速フラッシュ、遅延を削減
汎用シナリオ1024100msデフォルト値、遅延とスループットのバランス
高スループット4096500ms大きなバッファ、スループットを最大化
バッチ処理タスク81921000ms最大バッファ、オフライン処理に適している

データ安全性

BufferedWriter はバッファが半分満たされた(BufferSize/2 に到達)またはタイマーが発動したときにフラッシュします。プログラムの異常終了時、バッファ内のデータが失われる可能性があります。データの完全性を確保するために Close() または Flush() を確実に呼び出してください。

MultiWriter マルチ出力先ディスパッチ

go
// ファイルとリモートサービスに同時書き込み
fw, err := dd.NewFileWriter("logs/app.log", dd.DefaultFileWriterConfig())
if err != nil {
    log.Fatal(err)
}
remote := &RemoteLogWriter{endpoint: "http://log-service/ingest"}

mw := dd.NewMultiWriter(fw, remote)

logger, err := dd.New(dd.Config{
    Targets: []dd.OutputTarget{dd.CustomOutput(mw)},
})
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

MultiWriter は全ての Writer にログをディスパッチし、ある Writer の失敗が他の Writer に影響することはありません。

動的 Writer 管理

Logger は実行時の Writer の追加と削除をサポートします:

go
// 実行時に Writer を追加
fw, err := dd.NewFileWriter("logs/debug.log", dd.DefaultFileWriterConfig())
if err != nil {
    log.Fatal(err)
}
err = logger.AddWriter(fw)

// 実行時に Writer を削除
err = logger.RemoveWriter(fw)

// 現在の Writer 数を確認
count := logger.WriterCount()
_ = count

使用シナリオ

動的 Writer は、実行時にログ出力先を切り替える必要があるシナリオに適しています。例:デバッグモード時に詳細ログファイルを追加、ディスク容量不足時にリモートログサービスに切り替えなど。

カスタム Writer

io.Writer インターフェースを実装するだけで、カスタム出力先を作成できます:

go
// ネットワークログ送信器
type LogstashWriter struct {
    endpoint string
    client   *http.Client
}

func (w *LogstashWriter) Write(p []byte) (n int, err error) {
    resp, err := w.client.Post(w.endpoint, "application/json", bytes.NewReader(p))
    if err != nil {
        return 0, err
    }
    defer resp.Body.Close()
    return len(p), nil
}

// カスタム Writer を使用
logger, err := dd.New(dd.Config{
    Format: dd.FormatJSON,
    Targets: []dd.OutputTarget{
        dd.FileOutput("logs/app.json"),
        dd.CustomOutput(&LogstashWriter{
            endpoint: "http://logstash:5044",
            client:   &http.Client{Timeout: 5 * time.Second},
        }),
    },
})
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

本番環境推奨設定

go
func NewProductionLogger() (*dd.Logger, error) {
    // ファイル Writer:中規模ローテーション + 圧縮
    fwCfg := dd.DefaultFileWriterConfig()
    fwCfg.MaxSizeMB = 100
    fwCfg.MaxAge = 30 * 24 * time.Hour
    fwCfg.MaxBackups = 15
    fwCfg.Compress = true

    fw, err := dd.NewFileWriter("logs/app.json", fwCfg)
    if err != nil {
        return nil, err
    }

    // バッファラッパー
    bw, err := dd.NewBufferedWriter(fw, dd.DefaultBufferedWriterConfig())
    if err != nil {
        return nil, err
    }

    return dd.New(dd.Config{
        Level:  dd.LevelInfo,
        Format: dd.FormatJSON,
        Targets: []dd.OutputTarget{
            dd.ConsoleOutput(),
            dd.CustomOutput(bw),
        },
    })
}

次のステップ