Skip to content

出力先

DD は 3 種類の出力ライターを提供し、ファイルローテーション、バッファ書き込み、マルチ出力に対応します。

FileWriter

自動ローテーション付きファイルライター。

作成

go
func NewFileWriter(path string, cfg FileWriterConfig) (*FileWriter, error)

パスは内部のパスセキュリティ検証(パストラバーサル、null バイト、overlong UTF-8)を経た後、cfg.Validate() が数量上限を検証し、最後にゼロ/負の値をデフォルト設定にフォールバックします。エラーを返すケース:

  • パス系:ErrEmptyFilePath / ErrNullByte / ErrPathTooLong(>4096 バイト)/ ErrPathTraversal / ErrInvalidPath / ErrOverlongEncoding
  • 設定系:ErrMaxSizeExceededMaxSizeMB > 10240)/ ErrMaxBackupsExceededMaxBackups > 1000
  • I/O 系:ディレクトリ作成失敗(failed to create directory: … としてラップ)またはファイルオープン失敗(failed to open file …: %w としてラップ、ErrSymlinkNotAllowed / ErrHardlinkNotAllowed を含む)
go
// デフォルト設定を使用
fw, _ := dd.NewFileWriter("logs/app.log", dd.DefaultFileWriterConfig())

// カスタム設定
cfg := dd.DefaultFileWriterConfig()
cfg.MaxSizeMB = 50
fw, _ = dd.NewFileWriter("logs/app.log", cfg)

FileWriterConfig

ファイルライター設定。

go
type FileWriterConfig struct {
    MaxSizeMB  int            // ファイルサイズ上限 MB(デフォルト 100)
    MaxAge     time.Duration  // 古いファイルの保持期間(デフォルト 30 日)
    MaxBackups int            // バックアップ保持数(デフォルト 10)
    Compress   bool           // gzip 圧縮するか(デフォルト false)
}

デフォルト設定

go
func DefaultFileWriterConfig() FileWriterConfig

デフォルト値:100MB サイズ制限、30 日保持、10 バックアップファイル。

Validate

go
func (c FileWriterConfig) Validate() error

ファイルライター設定の妥当性を検証します。エラーを返すケース:

  • MaxSizeMB が 10240 を超える(ErrMaxSizeExceeded を返す)
  • MaxBackups が 1000 を超える(ErrMaxBackupsExceeded を返す)

負の値は Validate エラーをトリガーしません;ゼロまたは負の値の MaxSizeMB はデフォルト値適用時に 100MB にフォールバックし、MaxBackups / MaxAge の負の値はそのまま維持されます(詳細は下記「デフォルト値適用ルール」を参照)。

メソッド

メソッドシグネチャ説明
Write(p []byte) (int, error)データを書き込み(io.Writer を実装);書き込み前にサイズをチェックしてローテーションをトリガー、クローズ済みなら os.ErrClosed を返す
SetOnRotateCallback(fn func(path string))ファイルローテーション成功後のコールバックを設定
Close() errorクリーンアップ goroutine を停止し基底ファイルをクローズ;複数回呼び出しも安全(CAS ガード)

ローテーションコールバック

go
func (fw *FileWriter) SetOnRotateCallback(fn func(path string))

ファイルローテーション成功後に呼び出されるコールバック関数を設定します。コールバック引数 path は現在のログファイルのベースパス(NewFileWriter 構築時にパス正規化を経て格納されたパス——絶対パス入力の場合は通常入力と等しく、相対パスの場合は絶対パスに解決されます)です。この時点で古いログはバックアップファイルとしてアーカイブされ、同じパスに新しいファイルが再オープンされています。設定時に進行中のローテーションとの競合を避けるため内部ミューテックスロックを取得します。

内部利用

このメソッドは主に Logger が内部で使用します——FileWriter が Logger の出力先である場合、Logger はこれを通じて HookOnRotate フックイベントをトリガーします(詳細はフックシステムを参照)。通常のユーザーが手動で呼び出す必要はありませんが、ローテーション後のカスタム動作が必要な場合は直接設定できます。

go
fw, _ := dd.NewFileWriter("logs/app.log", dd.DefaultFileWriterConfig())

// ローテーションコールバックを設定:ローテーション後に現在のファイルパスを出力
fw.SetOnRotateCallback(func(path string) {
    fmt.Println("ログがローテーションされました、現在のファイル:", path)
})

// MaxSizeMB を超えてローテーションがトリガーされるとコールバックが呼び出されます
fw.Write([]byte("ログ内容\n"))

ファイルローテーションとクリーンアップ

FileWriter のローテーションとクリーンアップは 2 つの独立したパスで実行されます:

  • ローテーション(rotation)Write 呼び出し時に現在のファイルサイズをチェックし、MaxSizeMB(デフォルト 100MB)を超えるとトリガーされます——古いファイルはバックアップにリネーム、新しいファイルは O_EXCL で再オープン(シンボリックリンク TOCTOU を防止)され、その後 internal.RotateBackupsMaxBackups に従ってバックアップチェーンを切り詰め、Compress=true の場合は独立した goroutine がバックアップを gzip 圧縮します。
  • クリーンアップ(cleanup)MaxAge > 0 かつ MaxBackups > 0 の場合のみバックグラウンド goroutine を起動し、1 時間ごとにスキャンして internal.CleanupOldFiles を呼び出し、MaxAge(デフォルト 30 日)を超える古いバックアップを削除します。

デフォルト値適用ルール

ゼロ値または負の値の MaxSizeMB は一律 100MB にフォールバックします。MaxAge/MaxBackups の組み合わせルール:① 両方とも 0 → 完全デフォルトを有効化(30 日 + 10 個、クリーンアップ goroutine 起動);② MaxBackups のみ設定 → 数量によるバックアップチェーン切り詰めのみ、MaxAge=0 ではクリーンアップ goroutine 起動なし;③ MaxAge のみ設定 → MaxBackups はデフォルト 10 にフォールバック。

go
fw, _ := dd.NewFileWriter("logs/app.log", dd.DefaultFileWriterConfig())

// 書き込み時に MaxSizeMB を超えると自動ローテーションがトリガーされます
fw.Write([]byte("ログ内容\n"))

// ローテーション後に生成されるファイル:
// logs/app.log      (現在のファイル)
// logs/app_log_1.log (最新のバックアップ)
// logs/app_log_2.log (さらに古いバックアップ)
// Compress 有効時に古いバックアップは logs/app_log_1.log.gz に非同期圧縮

セキュリティ機能

FileWriter はパストラバーサル、null バイト、シンボリックリンク、ハードリンク、overlong UTF-8 防護を内蔵し、TOCTOU 攻撃を防ぐため新しいファイルは O_EXCL でオープンします。

BufferedWriter

バッファ付きライター。システムコール回数を削減します。

作成

go
func NewBufferedWriter(w io.Writer, cfg BufferedWriterConfig) (*BufferedWriter, error)

構築時にまず nil 基底 writer を拒否し(ErrNilWriter)、次に cfg.Validate() を呼び出し、最後に BufferSize がデフォルト(1KB)未満の値をデフォルト値にクランプし、FlushTime <= 0 を 100ms にクランプします。エラーを返すケース:

  • ErrNilWriterw == nil
  • ErrBufferSizeTooLargeBufferSize > 10MBValidate が返す)
  • 不正な設定:BufferSize < 0 または FlushTime < 0Validate が返す)
go
// デフォルト設定を使用
bw, _ := dd.NewBufferedWriter(os.Stdout, dd.DefaultBufferedWriterConfig())

// カスタム設定
cfg := dd.DefaultBufferedWriterConfig()
cfg.BufferSize = 4096
bw, _ = dd.NewBufferedWriter(os.Stdout, cfg)

BufferedWriterConfig

バッファライター設定。

go
type BufferedWriterConfig struct {
    BufferSize int            // バッファサイズ(バイト、デフォルト 1024 つまり 1KB、上限 10MB)
    FlushTime  time.Duration  // 定期フラッシュ間隔(デフォルト 100ms)
}

デフォルト設定

go
func DefaultBufferedWriterConfig() BufferedWriterConfig

デフォルト値:1KB バッファ、100ms フラッシュ間隔。

Validate

go
func (c BufferedWriterConfig) Validate() error

バッファライター設定の妥当性を検証します。エラーを返すケース:

  • BufferSize が負の値
  • BufferSize が 10MB を超える(ErrBufferSizeTooLarge を返す)
  • FlushTime が負の値

メソッド

メソッドシグネチャ説明
Write(p []byte) (int, error)バッファに書き込み;バッファ済みバイトが BufferSize/2 以上の場合自動 flush
Flush() errorバッファを基底 Writer に明示的にフラッシュ
Close() errorまずバッファを flush しバックグラウンド goroutine を停止、その後基底 Writer をクローズ(io.Closer を実装している場合)

バックグラウンド goroutine は FlushTime を周期にチェックします:バッファが空でなく前回の flush から FlushTime 以上経過している場合のみ自動 flush をトリガーします。Close の複数回呼び出しは安全(CAS ガード);クローズ済みの場合 Writeos.ErrClosed を返します。

go
cfg := dd.DefaultBufferedWriterConfig()
cfg.BufferSize = 8192
bw, _ := dd.NewBufferedWriter(file, cfg)
bw.Write([]byte("ログ行\n"))
_ = bw.Flush()      // 基底へ明示的にフラッシュ
defer bw.Close()    // Close はまず Flush し基底 Writer をクローズ

MultiWriter

マルチライター管理。複数の出力先に同時書き込みします。

作成

go
func NewMultiWriter(writers ...io.Writer) *MultiWriter

nil writer は構築時に暗黙的に無視されます。戻り値は決して nil になりません(構築でエラーは発生しません)。

go
mw := dd.NewMultiWriter(os.Stdout, fileWriter)

メソッド

メソッドシグネチャ説明
Write(p []byte) (int, error)全出力先に書き込み(エラー戦略は下記参照)
AddWriter(w io.Writer) error出力先を動的追加(重複 writer は暗黙的に受け付け)
RemoveWriter(w io.Writer) error出力先を動的削除
Close() errorすべての基底 io.Closer をクローズ(標準ストリームを除く)

AddWriter がエラーを返すケース:ErrNilMultiWriter(レシーバが nil)/ ErrNilWriter(引数が nil)/ ErrMaxWritersExceeded(≥ 100 個登録済み)。 RemoveWriter がエラーを返すケース:ErrNilMultiWriter / ErrWriterNotFound

go
mw := dd.NewMultiWriter(console, file)

// 動的管理
_ = mw.AddWriter(anotherFile)
_ = mw.RemoveWriter(console)

// すべての基底ライターをクローズ(os.Stdout などの標準ストリームはクローズされない)
_ = mw.Close()

エラー型

Write 失敗時に返されるエラーは 2 つの公開型で保持されます(errors.go で定義):

go
// 単一 writer のエラー
type WriterError struct {
    Index  int       // MultiWriter 内におけるこの writer のインデックス
    Writer io.Writer // エラーが発生した writer
    Err    error     // 基底エラー
}

// 複数 writer のエラー集約(Write は *MultiWriterError を返す)
type MultiWriterError struct {
    Errors []WriterError
}

2 つの型のメソッド:

メソッド説明
*WriterErrorError() stringwriter[i]: <err> 形式;Err が nil の場合は unknown error と表示
*WriterErrorUnwrap() errorErr を返し、errors.Is のチェーンマッチングに使用
*MultiWriterErrorError() string単一エラーはそのまま返す;複数エラーは multiple writer errors: [...] に結合
*MultiWriterErrorUnwrap() []errorすべての WriterError.Err を返し、errors.As / errors.Is に使用
*MultiWriterErrorHasErrors() boolエラーを収集したか
*MultiWriterErrorErrorCount() intエラー数
*MultiWriterErrorFirstError() error最初のエラー(*WriterError)、無ければ nil

エラー戦略

Write は「ベストエフォート書き込み」を採用:単一 writer の失敗が他の writer に影響することはありません。基底 writer のエラーは MultiWriterError に集約され、errors.As/errors.Is で使用できるよう Unwrap() []error を実装します。全失敗時は (0, *MultiWriterError) を返し;一部失敗時は (pLen, *MultiWriterError) を返し;書き込みバイト数が不足する場合は short write エラーとして記録します。

単一 writer 最適化

基底 writer が 1 つのみの場合、Write は高速パスで直接転送し、エラーはそのまま返され、MultiWriterError でラップされません。

組み合わせて使用

go
// ファイル + バッファ + マルチ出力先
fw, _ := dd.NewFileWriter("logs/app.log", dd.DefaultFileWriterConfig())
bw, _ := dd.NewBufferedWriter(fw, dd.DefaultBufferedWriterConfig())
mw := dd.NewMultiWriter(os.Stdout, bw)

logger, _ := dd.New(dd.Config{
    Level: dd.LevelInfo,
    Targets: []dd.OutputTarget{dd.CustomOutput(mw)},
})
defer logger.Close()  // バッファを自動フラッシュしファイルを閉じる

次のステップ