Goにおけるログファイルローテーションの実装：logrus・zap・slogからの考察

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967563/0c7c88d4-4566-4c74-a2ab-db5d5b4100c5.png)](https://leapcell.io/?lc_t=q_gologrotation)

## 前書き

既存のログライブラリの中には、Go 標準の log/slog ログライブラリを含めて、通常、ログファイルのローテーションおよび分割（切り替え）をサポートしています。ただし、これらの機能は直接内蔵されているわけではなく、ユーザーが明示的に設定して有効化する必要があります。

本記事では、logrus、zap、そして公式の slog など、人気のあるログライブラリについて探ります。これらのライブラリの主要な設計要素を分析し、ログのローテーションおよび分割機能の設定方法について考察します。

## logrus、zap、slog の設計に関する簡単な分析

logrus、zap、slog というログライブラリの設計を比較分析する際、明らかな共通点として、それら全てが `io.Writer` という重要な属性を持っていることが挙げられます。この属性はログフレームワークの設計において中核的な役割を果たしており、ログ出力のターゲット（出力先）を決定します。

### logrus ログライブラリ

logrus は、Go 言語で書かれた多機能なログライブラリで、構造化ログ記録、ログレベル制御などの機能を提供します。

logrus を使用する場合、`logrus.New()` 関数を呼び出して Logger インスタンスを作成します。このインスタンスを通じて、ログ出力先のカスタマイズやログの出力など、さまざまな操作を行うことができます。以下はその一例です：

```go
logger := logrus.New()
logger.Out = os.Stdout // 標準出力
// あるいはファイルへ出力
// out, err := os.OpenFile("file.log", os.O_CREATE|os.O_WRONLY, 0666)
// if err != nil {
//  panic(err)
// }
// logger.Out = out
```

Logger 構造体の定義は次の通りです：

```go
type Logger struct {
    Out io.Writer
    Hooks LevelHooks
    Formatter Formatter
    // その他のフィールド...
}
```

重要な属性 `Out` の型は `io.Writer` であり、この属性はログの出力先（標準出力、ファイル、またはその他のカスタム出力）を指定します。

### zap ログライブラリ

zap は非常に高性能なログライブラリです。構造化ログ記録、複数レベルのログ制御、柔軟な設定オプションを提供します。

logrus と同様に、zap も設定を通じてログの出力先を指定できますが、その実装方法は若干異なります。zap では、ログ出力は `zapcore.Core` の設定を通じて実現されます。`zapcore.Core` のインスタンスを作成する際、`zapcore.WriteSyncer` インターフェースの実装をパラメータとして指定する必要があります。このパラメータが、ログの出力ターゲットを直接決定します。`zapcore.WriteSyncer` のインスタンスを作成するには、通常 `zapcore.AddSync()` 関数を使い、この関数は `io.Writer` 型のパラメータを受け取ります。

以下は、zap を使ってログインスタンスを作成する基本的な例です：

```go
writer := zapcore.AddSync(os.Stdout) // 標準出力をログターゲットとして利用
core := zapcore.NewCore(
    zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
    writer,
    zap.InfoLevel,
)
logger := zap.New(core)
defer logger.Sync() // バッファされたログエントリをフラッシュ

// logger を使ってログ記録
```

重要なのは `zapcore.AddSync()` 関数で、この関数は `io.Writer` 型のパラメータを受け取ります。このパラメータによって、ログの出力ターゲット（標準出力、ファイル、またはその他のカスタム出力）が指定されます。

### slog ログライブラリ

slog は、Go 1.21.0 バージョンで公式に導入されたログライブラリで、構造化ログを提供します。slog ログライブラリについてより詳しく知りたい場合は、以前の[記事](https://leapcell.io/blog/hands-on-with-go-slog-package)も参照してください。

logrus や zap と同様に、slog もユーザーが `io.Writer` パラメータを指定することでログ出力のターゲットを設定できます。この設定は `slog.Handler` インターフェースの実装を作成する際に行います。

```go
textLogger := slog.New(slog.NewTextHandler(os.Stdout, nil))
jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
```

これら 2 つの関数で、`slog.NewTextHandler` および `slog.NewJSONHandler` の最初のパラメータはいずれも `io.Writer` 型です。

### 簡単なまとめ

logrus、zap、slog という 3 つの主流ログライブラリを分析すると、ログ出力を扱う際に共通して `io.Writer` インターフェースに依存しているという重要な共通点が分かります。これらのログライブラリは、`io.Writer` インターフェースを主要なパラメータ型とすることで、ログの出力ターゲットを柔軟に設定できるようになっています。

## ログローテーションと分割機能の実現メカニズムと実践

### 実現メカニズム

logrus、zap、slog の設計について簡単に分析した結果、それらの共通点が明らかになりました。では次に、ログローテーションと分割機能の実現メカニズムを詳しく見ていきましょう。

ログファイルのローテーションや分割を実現するためには、通常、**lumberjack**のようなサードパーティライブラリを利用します。もちろん他にも似たようなライブラリは存在しますが、ここでは詳細は割愛します。

lumberjack は、ログのローテーションや分割を専門に設計されたライブラリであり、その役割はまるでプラグインのようです。このコンポーネントを設定して、選んだログライブラリに統合することで、ログファイルのローテーションや分割機能を簡単に実現できます。

lumberjack コンポーネントを初期化するコード例は次の通りです：

```go
log := &lumberjack.Logger{
    Filename:   "/path/file.log", // ログファイルのパス
    MaxSize:    10, // ファイル最大サイズ（MB単位）
    MaxBackups: 3, // 保持する古いファイルの最大数
    MaxAge:     28, // 古いファイルの最大保持日数
    Compress:   true, // 古いファイルを圧縮・アーカイブするかどうか
    LocalTime:  true, // タイムスタンプの作成にローカルタイムを使用
}
```

この例では、lumberjack.Logger インスタンスを作成し、以下のパラメータを設定しています：

- Filename：ログファイルの保存パス
- MaxSize：ログファイルが何 MB に達したらローテーションを行うか
- MaxBackups：古いログファイルを最大でいくつ保持するか
- MaxAge：古いファイルの最大保持期間（日数）
- Compress：古いファイルを圧縮（例えば .gz へ変換）するか

特に注意すべきなのは、lumberjack の Logger 構造体は`io.Writer`インターフェースを実装している点です。つまり、ログファイルのローテーションや分割に関するコアロジックは全て Write メソッドの中にカプセル化されています。この実装により、Logger 構造体はどんな`io.Writer`を受け入れるログライブラリにも簡単に統合できます。

これらの知識を理解すれば、ログローテーションや分割機能の実現方法も自ずと明確になるでしょう。lumberjack の Logger 構造体が`io.Writer`インターフェースを実装しているので、それをサードパーティライブラリに渡すだけで統合設定が完成します。

### 実践

#### logrus ログライブラリでの実装

```go
log := &lumberjack.Logger{
    Filename:   "/path/file.log", // ログファイルのパス
    MaxSize:    10,               // ファイル最大サイズ（MB単位）
    MaxBackups: 3,                // 保持する古いファイルの最大数
    MaxAge:     28,               // 古いファイルの最大保持日数
    Compress:   true,             // 古いファイルを圧縮・アーカイブするか
    LocalTime:  true,             // タイムスタンプの作成にローカルタイムを使用
}
logger := logrus.New()
logger.Out = log
```

#### zap ログライブラリでの実装

```go
log := &lumberjack.Logger{
    Filename:   "/path/file.log", // ログファイルのパス
    MaxSize:    10,               // ファイル最大サイズ（MB単位）
    MaxBackups: 3,                // 保持する古いファイルの最大数
    MaxAge:     28,               // 古いファイルの最大保持日数
    Compress:   true,             // 古いファイルを圧縮・アーカイブするか
    LocalTime:  true,             // タイムスタンプの作成にローカルタイムを使用
}
writer := zapcore.AddSync(log)
core := zapcore.NewCore(
    zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
    writer,
    zap.InfoLevel,
)
logger := zap.New(core)
defer logger.Sync() // バッファされたログエントリをフラッシュ
```

#### slog ログライブラリでの実装

```go
log := &lumberjack.Logger{
    Filename:   "/path/file.log", // ログファイルのパス
    MaxSize:    10,               // ファイル最大サイズ（MB単位）
    MaxBackups: 3,                // 保持する古いファイルの最大数
    MaxAge:     28,               // 古いファイルの最大保持日数
    Compress:   true,             // 古いファイルを圧縮・アーカイブするか
    LocalTime:  true,             // タイムスタンプの作成にローカルタイムを使用
}
textLogger := slog.New(slog.NewTextHandler(log, nil))
jsonLogger := slog.New(slog.NewJSONHandler(log, nil))
```

## まとめ

本記事では、3 つの人気ログライブラリである logrus、zap、slog の設計要素を簡単に分析しました。その結果、ログインスタンスの作成方法に細かな違いはあっても、いずれもログ出力の処理には`io.Writer`インターフェースのパラメータに依存していることが分かりました。`io.Writer`パラメータの設定方法を理解し、lumberjack ライブラリを組み合わせて使うことで、ログファイルのローテーションや分割機能を簡単に実現できます。

今後、新しいログライブラリが登場しても、同様の方法で素早くログファイルのローテーションや分割機能を統合することができるでしょう。

---

### [私たちはLeapcell、Goプロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_gologrotation)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967563/e230cf3d-f064-4525-a8b6-024a5b45ff9e.png)](https://leapcell.io/?lc_t=q_gologrotation)

[Leapcell](https://leapcell.io/?lc_t=q_gologrotation)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967563/96852446-766d-46ae-9359-ab738a8409da.png)](https://leapcell.io/?lc_t=q_gologrotation)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/log-rotation-and-file-splitting-in-go)