Zap: Golangでロギングの可能性を最大限に引き出す

[![Group301.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/2408cd7d-9e69-fcc3-2cbb-2a530e991f48.png)](https://leapcell.io/?lc_t=q_gzap)

### Abstract
ZapはUberによって開発された非常に高速で、構造化され、ログレベル付きのGoロギングライブラリです。Uber - go Zapのドキュメントによると、同様の構造化ロギングパッケージよりもパフォーマンスが優れており、標準ライブラリよりも高速でもあります。具体的なパフォーマンステストはGitHubで確認できます。

GitHubアドレス: [https://github.com/uber - go/zap](https://github.com/uber-go/zap)

[![logo11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/3fa3693a-4b5d-9ec2-6e20-bbc891f1adf8.png)](https://leapcell.io/?lc_t=q_gzap)


### インスタンスの作成
zap.NewProduction()/zap.NewDevelopment() または zap.Example() を呼び出してLoggerを作成します。これら3つのメソッドの違いは、記録する情報にあり、パラメータは文字列型のみになります。

```go
// コード
var log *zap.Logger
log = zap.NewExample()
log, _ := zap.NewDevelopment()
log, _ := zap.NewProduction()
log.Debug("This is a DEBUG message")
log.Info("This is an INFO message")
```

```plaintext
// 出力例
{"level":"debug","msg":"This is a DEBUG message"}
{"level":"info","msg":"This is an INFO message"}
```

```plaintext
// 開発環境の出力
2025-01-28T00:00:00.000+0800    DEBUG    development/main.go:7    This is a DEBUG message
2025-01-28T00:00:00.000+0800    INFO    development/main.go:8    This is an INFO message
```

```plaintext
// 本番環境の出力
{"level":"info","ts":1737907200.0000000,"caller":"production/main.go:8","msg":"This is an INFO message"}
{"level":"info","ts":1737907200.0000000,"caller":"production/main.go:9","msg":"This is an INFO message with fields","region":["us-west"],"id":2}
```

#### 3つの作成方法の比較:
- ExampleとProductionの両方がJSON形式で出力を行い、Developmentは1行ずつの出力形式を使用します。
- **開発環境**
  - 警告レベル以上のログをスタックトレースに出力します。
  - 常にパッケージ/ファイル/行番号(メソッド)を出力します。
  - 追加のフィールドを行の末尾にJSON文字列として追加します。
  - レベル名を大文字で出力します。
  - タイムスタンプをISO8601形式でミリ秒単位で出力します。
- **本番環境**
  - デバッグレベルのメッセージは記録されません。
  - エラーおよびDpanicレベルの記録では、スタックトレースにファイルが追跡されますが、警告レベルでは追跡されません。
  - 常に呼び出し元のファイルを追加します。
  - 日付をタイムスタンプ形式で出力します。
  - レベル名を小文字で出力します。

### 書式付き出力
Zapには*zap.Loggerと*zap.SugaredLoggerの2種類があります。両者の唯一の違いは、メインのLoggerの.Sugar()メソッドを呼び出してSugaredLoggerを取得し、その後SugaredLoggerを使用してprintf形式のステートメントを記録できるという点です。例:

```go
var sugarLogger *zap.SugaredLogger

func InitLogger() {
  logger, _ := zap.NewProduction()
    sugarLogger = logger.Sugar()
}

func main() {
    InitLogger()
    defer sugarLogger.Sync()
    sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
}
```

### ファイルへの書き込み
デフォルトでは、ログはアプリケーションのコンソールインターフェイスに出力されます。ただし、検索を容易にするために、ログをファイルに書き込むこともできます。ただし、これまでに説明した3つのインスタンス作成方法はもう使用できません。代わりにzap.New()を使用します。

```go
package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

var log *zap.Logger
func main() {
	writeSyncer, _ := os.Create("./info.log")							// ログファイルの保存ディレクトリ
	encoderConfig := zap.NewProductionEncoderConfig()					 // 日付形式を指定
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoder := zapcore.NewConsoleEncoder(encoderConfig)					// エンコーダを取得、NewJSONEncoder()はJSON形式で出力、NewConsoleEncoder()はプレーンテキスト形式で出力
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)	 // 3番目以降のパラメータはファイルに書き込むログレベル。ErrorLevelモードでは、エラーレベルのログのみが記録されます。
	log = zap.New(core,zap.AddCaller())								   // AddCaller()はファイル名と行番号を表示するために使用されます。
	log.Info("hello world")
	log.Error("hello world")
}
```

```plaintext
// ログファイルの出力結果:
2025-01-28T00:00:00.000+0800	INFO	geth/main.go:18	hello world
2025-01-28T00:00:00.000+0800	ERROR	geth/main.go:19	hello world
```

### コンソールとファイルへの出力
コンソールとファイルの両方に出力する必要がある場合は、zapcore.NewCoreを変更するだけです。例:

```go
package main

import (
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

var log *zap.Logger

func main() {
	// エンコーダを取得、NewJSONEncoder()はJSON形式で出力、NewConsoleEncoder()はプレーンテキスト形式で出力
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 日付形式を指定
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// ファイル用writeSyncer
	fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./info.log", // ログファイルの保存ディレクトリ
		MaxSize:    1,            // ファイルサイズ制限、単位MB
		MaxBackups: 5,            // 保持する最大ログファイル数
		MaxAge:     30,           // ログファイルを保持する日数
		Compress:   false,        // 圧縮するかどうか
	})
	fileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(fileWriteSyncer,zapcore.AddSync(os.Stdout)), zapcore.DebugLevel) // 3番目以降のパラメータはファイルに書き込むログレベル。ErrorLevelモードでは、エラーレベルのログのみが記録されます。

	log = zap.New(fileCore, zap.AddCaller()) // AddCaller()はファイル名と行番号を表示するために使用されます。


	log.Info("hello world")
	log.Error("hello world")
}
```

### ファイル分割
ログファイルは時間の経過とともに大きくなります。ハードディスクの空き容量を占めないように、ログファイルは一定の条件に応じて分割する必要があります。Zapパッケージ自体はファイル分割機能を提供していませんが、Zapが推奨するlumberjackパッケージを使用して対応できます。

```go
// ファイル用writeSyncer
fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
	Filename:   "./info.log", // ログファイルの保存ディレクトリ。フォルダが存在しない場合は自動的に作成されます。
	MaxSize:    1,            // ファイルサイズ制限、単位MB
	MaxBackups: 5,            // 保持する最大ログファイル数
	MaxAge:     30,           // ログファイルを保持する日数
	Compress:   false,        // 圧縮するかどうか
})
```

### レベル別のファイル書き込み
管理者が検索しやすいように、一般的にエラーレベル未満のログはinfo.logに、エラーレベル以上のログはerror.logファイルに入れる必要があります。zapcore.NewCoreメソッドの3番目のパラメータを変更し、その後ファイルのWriteSyncerをinfoとerrorに分割するだけです。例:

```go
package main

import (
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

var log *zap.Logger

func main() {
	var coreArr []zapcore.Core

	// エンコーダを取得
	encoderConfig := zap.NewProductionEncoderConfig()				// NewJSONEncoder()はJSON形式で出力、NewConsoleEncoder()はプレーンテキスト形式で出力
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 			// 日付形式を指定
	encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder	// レベルに応じて異なる色を表示。不要な場合はzapcore.CapitalLevelEncoderを使用します。
	//encoderConfig.EncodeCaller = zapcore.FullCallerEncoder      	// 完全なファイルパスを表示
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// ログレベル
	highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool{	// エラーレベル
		return lev >= zap.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {	// Infoとデバッグレベル、デバッグレベルが最も低い
		return lev < zap.ErrorLevel && lev >= zap.DebugLevel
	})

	// Infoファイル用writeSyncer
	infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/info.log", 	// ログファイルの保存ディレクトリ。フォルダが存在しない場合は自動的に作成されます。
		MaxSize:    1,            		// ファイルサイズ制限、単位MB
		MaxBackups: 5,            		// 保持する最大ログファイル数
		MaxAge:     30,           		// ログファイルを保持する日数
		Compress:   false,        		// 圧縮するかどうか
	})
	infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer,zapcore.AddSync(os.Stdout)), lowPriority) // 3番目以降のパラメータはファイルに書き込むログレベル。ErrorLevelモードでは、エラーレベルのログのみが記録されます。
	// エラーファイル用writeSyncer
	errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/error.log", 		// ログファイルの保存ディレクトリ
		MaxSize:    1,            			// ファイルサイズ制限、単位MB
		MaxBackups: 5,            			// 保持する最大ログファイル数
		MaxAge:     30,           			// ログファイルを保持する日数
		Compress:   false,        			// 圧縮するかどうか
	})
	errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer,zapcore.AddSync(os.Stdout)), highPriority) // 3番目以降のパラメータはファイルに書き込むログレベル。ErrorLevelモードでは、エラーレベルのログのみが記録されます。

	coreArr = append(coreArr, infoFileCore)
	coreArr = append(coreArr, errorFileCore)
	log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) // zap.AddCaller()はファイル名と行番号を表示するために使用され、省略可能です。

	log.Info("hello info")
	log.Debug("hello debug")
	log.Error("hello error")
}
```
このように変更すると、infoとデバッグレベルのログはinfo.logに、エラーレベルのログはerror.logファイルに別々に保存されます。


### ファイルパスと行番号の表示
前述の通り、ファイルパスと行番号を表示するには、`zap.New` メソッドに `zap.AddCaller()` パラメータを追加するだけです。完全なパスを表示したい場合は、エンコーダの設定で指定する必要があります。

```go
// エンコーダを取得
encoderConfig := zap.NewProductionEncoderConfig()				// NewJSONEncoder()はJSON形式で出力、NewConsoleEncoder()はプレーンテキスト形式で出力
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 			// 日付形式を指定
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder	// レベルに応じて異なる色を表示。不要な場合はzapcore.CapitalLevelEncoderを使用
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder      	// 完全なファイルパスを表示
encoder := zapcore.NewConsoleEncoder(encoderConfig)
```

### 完全なコード
```go
package main

import (
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

var log *zap.Logger

func main() {
	var coreArr []zapcore.Core

	// エンコーダを取得
	encoderConfig := zap.NewProductionEncoderConfig()				// NewJSONEncoder()はJSON形式で出力、NewConsoleEncoder()はプレーンテキスト形式で出力
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 			// 日付形式を指定
	encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder	// レベルに応じて異なる色を表示。不要な場合はzapcore.CapitalLevelEncoderを使用
	//encoderConfig.EncodeCaller = zapcore.FullCallerEncoder      	// 完全なファイルパスを表示
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// ログレベル
	highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool{	// エラーレベル
		return lev >= zap.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {	// Infoとデバッグレベル、デバッグレベルが最も低い
		return lev < zap.ErrorLevel && lev >= zap.DebugLevel
	})

	// Infoファイル用writeSyncer
	infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/info.log", 	// ログファイルの保存ディレクトリ。フォルダが存在しない場合は自動的に作成されます。
		MaxSize:    2,            		// ファイルサイズ制限、単位MB
		MaxBackups: 100,            	// 保持する最大ログファイル数
		MaxAge:     30,           		// ログファイルを保持する日数
		Compress:   false,        		// 圧縮するかどうか
	})
	infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer,zapcore.AddSync(os.Stdout)), lowPriority) // 3番目以降のパラメータはファイルに書き込むログレベル。ErrorLevelモードでは、エラーレベルのログのみが記録されます。
	// エラーファイル用writeSyncer
	errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/error.log", 		// ログファイルの保存ディレクトリ
		MaxSize:    1,            			// ファイルサイズ制限、単位MB
		MaxBackups: 5,            			// 保持する最大ログファイル数
		MaxAge:     30,           			// ログファイルを保持する日数
		Compress:   false,        			// 圧縮するかどうか
	})
	errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer,zapcore.AddSync(os.Stdout)), highPriority) // 3番目以降のパラメータはファイルに書き込むログレベル。ErrorLevelモードでは、エラーレベルのログのみが記録されます。

	coreArr = append(coreArr, infoFileCore)
	coreArr = append(coreArr, errorFileCore)
	log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) // zap.AddCaller()はファイル名と行番号を表示するために使用され、省略可能です。


	log.Info("hello info")
	log.Debug("hello debug")
	log.Error("hello error")
}
```

# [Leapcell: The Best Serverless Platform for Golang app Hosting, Async Tasks, and Redis](https://leapcell.io/)

[![barndpic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/61ca067b-772a-12c1-d62b-7bed5a701596.png)](https://leapcell.io/?lc_t=q_gzap)


最後に、Golangサービスをデプロイするための最高のプラットフォーム：[Leapcell](https://leapcell.io/) をおすすめします。

### 1. 多言語対応
 - JavaScript、Python、Go、またはRustで開発可能。  

### 2. 無制限のプロジェクトを無料でデプロイ
 - 使用量に応じて課金 — リクエストがなければ料金は発生しません。

### 3. 画期的なコスト効率
 - 使い捨て型の課金方式で、アイドル時の課金はありません。  
 - 例: 平均応答時間60msで25ドルで694万回のリクエストをサポート。  

### 4. 合理化された開発者体験
 - 直感的なUIによる簡単なセットアップ。  
 - 完全自動化されたCI/CDパイプラインとGitOps統合。  
 - アクション可能なインサイトを得るためのリアルタイムメトリクスとログ。  

### 5. 簡単なスケーラビリティと高性能
 - 高い同時実行数を簡単に処理するための自動スケーリング。  
 - オペレーションのオーバーヘッドはゼロ — 構築に集中できます。  

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/d7399df7-3cbf-9909-0034-21f26f8b0c61.png)](https://leapcell.io/?lc_t=q_gzap)


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

LeapcellのTwitter: [https://x.com/LeapcellHQ](https://x.com/LeapcellHQ)