🐡

Goのlog/slogパッケージについてのメモ

に公開

log/slogパッケージの概要

slogパッケージはGo 1.21で導入されたレベル付きの構造化ログを提供する標準ライブラリ。
https://go.dev/doc/go1.21

構造化ログ

https://go.dev/doc/go1.21#slog

Structured logging emits key-value pairs to enable fast, accurate processing of large amounts of log data.

機械翻訳)
構造化ログは、キーバリューペアを発行することで、大量のログデータの高速かつ正確な処理を可能にします。

https://go.dev/blog/slog

Structured logs use key-value pairs so they can be parsed, filtered, searched, and analyzed quickly and reliably.

機械翻訳)
構造化ログはキーと値のペアを使用するため、迅速かつ信頼性高くパース、フィルタリング、検索、分析が可能です。

slogの構成要素

slogにはslog.Loggerslog.Recordslog.Handlerの要素があります。

開発者がInfo()等のslog.Loggerのメソッドを呼び出したときに、Recordが生成され、Handlerに渡されます。そして、Handlerは受け取ったRecordを処理し、ログを出力します。

slog.Logger

slog.LoggerはInfo()やWarn()、Error()といったレベルごとのメソッドや、Log()やWith()等のメソッドを提供します。

slog.Record

slog.Recordはログの生成時間やレベル、メッセージ等のログ情報を保持します。

slog.Handler

slog.Handlerはインタフェースです。slog.Recordを受け取って、出力形式や出力先等を決定します。
組み込みのハンドラーとして、TextHandlerとJSONHandlerが存在します。

log/slogパッケージの良いところ

以下の点をメリットと捉えています。

  • 標準ライブラリであるため、メンテナンスが担保されていると考えられる
  • 依存ライブラリが標準ライブラリになる(そのはず)

使い方

基本的な使い方

つぎのようにレベルに応じた関数を呼び出すことが可能です。
また、slog.InfoContext()のようにcontext.Contextを引数として受け取る関数も存在します。

slog.Info("Infoレベルのログです")
slog.Warn("Warnレベルのログです")
slog.Error("Errorレベルのログです")

// 2025/08/15 15:28:42 INFO Infoレベルのログです
// 2025/08/15 15:28:42 WARN Warnレベルのログです
// 2025/08/15 15:28:42 ERROR Errorレベルのログです

メッセージ以外のフィールドを追加したい場合、つぎのように使用します。

slog.Info("UserInfo",
	"user_id", 12345,
	"username", "test",
	"ip_address", "192.168.x.xxx",
)

// 2025/08/15 15:30:19 INFO UserInfo user_id=12345 username=test ip_address=192.168.x.xxx

これらのレベル付き関数はslog.Loggerのメソッドに対応しています。
例えばInfo()はつぎのように実装されており、*slog.Loggerを呼び出しています。

// Info calls [Logger.Info] on the default logger.
func Info(msg string, args ...any) {
	Default().log(context.Background(), LevelInfo, msg, args...)
}

// Default returns the default [Logger].
func Default() *Logger { return defaultLogger.Load() }

ログ出力の形式をデフォルトから変更してみる

組み込みのHandlerを使用してみる

slogのデフォルトのHandlerはdefaultHandlerになっています。
ここでは、組み込みのJSONHandlerを試してみることにします。

jsonHandler := slog.NewJSONHandler(os.Stdout, nil)
logger := slog.New(jsonHandler)

logger.Info("JSONフォーマットのログです")
// {"time":"2025-08-15T16:13:08.6877+09:00","level":"INFO","msg":"JSONフォーマットのログです"}

このようにJSONHandlerを生成し、そのハンドラーを用いてLoggerを生成しています。
そして、LoggerのInfo()を実行することで、JSON形式のログを出力します。

つぎのように複数フィールドを出力したり、 slog.Group() を使用してネストしたログを出力することも可能です。

logger.Info("UserInfo",
	"user_id", 12345,
	"action", "purchase",
	"item_id", "ABC-789",
	"price", 2980,
	"currency", "JPY",
	"timestamp", time.Now().Unix(),
)

// {"time":"2025-08-15T16:13:08.687835+09:00","level":"INFO","msg":"UserInfo","user_id":12345,"action":"purchase","item_id":"ABC-789","price":2980,"currency":"JPY","timestamp":1755241988}

logger.Info("Users API",
	slog.Group("request",
		"method", "POST",
		"path", "/api/v1/users",
		"headers", map[string]string{
			"Content-Type":  "application/json",
			"Authorization": "Bearer xxx...",
		},
	),
	slog.Group("response",
		"status", 201,
		"duration_ms", 145,
		"size_bytes", 1024,
	),
)

// {"time":"2025-08-15T16:13:08.687865+09:00","level":"INFO","msg":"Users API","request":{"method":"POST","path":"/api/v1/users","headers":{"Authorization":"Bearer xxx...","Content-Type":"application/json"}},"response":{"status":201,"duration_ms":145,"size_bytes":1024}}

デフォルトのLoggerを変更する

前項でJSONHandlerをHandlerとするLoggerを生成して、JSON形式でログを出力する方法を試しました。
このJSONHandlerを使用して、デフォルトのログ出力の形式をJSONにする方法を検証します。

slog.SetDefault(logger)

slog.Info("LoggerをJSONHandlerに変更",
	"note", "このログはJSON形式で出力されます",
	"timestamp", time.Now().Unix(),
)

// {"time":"2025-08-15T16:30:48.048782+09:00","level":"INFO","msg":"LoggerをJSONHandlerに変更","note":"このログはJSON形式で出力されます","timestamp":1755243048}

slog.SetDefault()を使用することで、デフォルトのLoggerを変更することができました。
ただし、ドキュメントにも書かれているように、SetDefault()を実行すると、logパッケージのデフォルトのロガーも更新されます。

https://pkg.go.dev/log/slog

slog.SetDefault(logger)
will cause the top-level functions like Info to use it. SetDefault also updates the default logger used by the log package, so that existing applications that use log.Printf and related functions will send log records to the logger's handler without needing to be rewritten.

log.Println("logパッケージのログもJSON形式で出力されます")

// {"time":"2025-08-15T16:33:31.736144+09:00","level":"INFO","msg":"logパッケージのログもJSON形式で出力されます"}

ログのカスタマイズ

slog.JSONHandlerのカスタマイズ

シークレット情報のマスキング

ReplaceAttrを設定することで、出力する属性をカスタマイズすることができます。
ここではJSONHandlerを使用したときに、シークレット情報をマスキングする設定を試します。

opts := &slog.HandlerOptions{
	Level: slog.LevelInfo,
	ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
		if strings.Contains(strings.ToLower(a.Key), "secret") {
			return slog.String(a.Key, "***MASKED***")
		}

		return a
	},
}

logger := slog.New(slog.NewJSONHandler(os.Stdout, opts))

// 機密情報を含むログ(マスクされる)
logger.Info("Authentication",
	"username", "test",
	"secret", "secret123",
)

// {"time":"2025-08-15T17:51:57.620742+09:00","level":"INFO","msg":"Authentication","username":"test","secret":"***MASKED***"}

共通属性を追加する

複数のログ出力で共通した属性を出力したい場合は、Logger.With()を使用します。

logger2 := logger.With(
	"service", "user-api",
	"version", "2.1.0",
	"environment", "production",
)
	
logger2.Info("Start")
logger2.Info("Completed")

// {"time":"2025-08-15T18:06:28.476091+09:00","level":"INFO","msg":"Start","service":"user-api","version":"2.1.0","environment":"production"}
// {"time":"2025-08-15T18:06:28.476291+09:00","level":"INFO","msg":"Completed","service":"user-api","version":"2.1.0","environment":"production"}

Discussion