ロギング
概要
ログ関連機能を提供します。
ログに関する機能はzlogパッケージにより提供されています。
機能
1. ログ出力機能
ログ出力機能は、ログを出力する機能です。 zlogパッケージでは以下のようにロガーのインターフェースが定められています。 これは特定のログパッケージに依存しないインターフェースとなっています。
type Logger interface {
DebugEnabled(ctx context.Context) bool
InfoEnabled(ctx context.Context) bool
WarnEnabled(ctx context.Context) bool
ErrorEnabled(ctx context.Context) bool
DebugContext(ctx context.Context, msg string, args ...any)
InfoContext(ctx context.Context, msg string, args ...any)
WarnContext(ctx context.Context, msg string, args ...any)
ErrorContext(ctx context.Context, msg string, args ...any)
}
zlog/zslogパッケージは、Go 標準パッケージのlog/slog.Loggerにたいしてロガーインターフェースを実装する機能を提供しています。
以下はデフォルトのslog.Logger
に対して上記の Logger インターフェースを実装する例です。
package main
import (
"context"
"log/slog"
"github.com/aileron-projects/go/zlog/zslog"
)
func main() {
// Create logger from default slogger handler.
lg := zslog.New(slog.Default().Handler())
ctx := context.Background()
if lg.DebugEnabled(ctx) {
lg.DebugContext(ctx, "debug enabled")
}
if lg.InfoEnabled(ctx) {
lg.InfoContext(ctx, "info enabled")
}
if lg.WarnEnabled(ctx) {
lg.WarnContext(ctx, "warn enabled")
}
if lg.ErrorEnabled(ctx) {
lg.ErrorContext(ctx, "error enabled")
}
}
2. コンテキスト属性操作機能
コンテキスト属性操作機能はcontext.Context
にログの属性を保存・取得する機能です。
これによりロガーはコンテキストに紐づく属性をログに含めることが可能です。
属性をコンテキストに保存・取得するためにContextWithAttrsとAttrsFromContextの2つの関数が提供されています。
ctx := context.Background()
// Save attributes in the context.
ctx = zlog.ContextWithAttrs(ctx, "key", "value")
// Extract attributes from the context.
attrs := zlog.AttrsFromContext(ctx)
なお、zlog/zslog.Newで作成したロガーを利用した場合、コンテキストに含まれる属性は自動的にログに出力されます。
以下のコードで動作確認ができます。
package main
import (
"context"
"log/slog"
"github.com/aileron-projects/go/zlog"
"github.com/aileron-projects/go/zlog/zslog"
)
func main() {
lg := zslog.New(slog.Default().Handler())
ctx := context.Background()
ctx = zlog.ContextWithAttrs(ctx, "key", "value")
if lg.InfoEnabled(ctx) {
lg.InfoContext(ctx, "output context attributes")
}
}
3. コンテキストログレベル設定機能
コンテキストログレベル設定機能はcontext.Context
を利用して、対象となるコンテキストに対してログ出力レベルを設定する機能です。
利用しているロガーのログレベルが Info である場合でも、コンテキストのログレベルを Debug に設定すると、当該コンテキストに紐づけられたログレコードは Debug レベルで出力されます。
この機能はデバッグや、コンテキストに応じたログレベルによるログ出力に利用できます。
以下のコードでは、ロガーのログレベルはInfo
になっています。
しかし、コンテキストはDebug
レベルを指定しているため、Debug ログが出力されます。
ログレベルをコンテキストに設定するためにContextWithLevelとLevelFromContextの2つの関数が提供されています。
ctx := context.Background()
// Save log level in the context.
ctx = zslog.ContextWithLevel(ctx, slog.LevelDebug)
// Extract log level from the context.
lv := zslog.LevelFromContext(ctx)
なお、zlog/zslog.Newで作成したロガーを利用した場合、コンテキストに設定されたログレベルは自動的に認識されるようになります。
以下のコードで動作確認ができます。
package main
import (
"context"
"log/slog"
"github.com/aileron-projects/go/zlog/zslog"
)
func main() {
lg := zslog.New(slog.Default().Handler()) // Info level logger.
ctx := context.Background()
// Try to output debug log.
lg.DebugContext(ctx, "log should not be output")
// Set this context to debug level.
ctx = zslog.ContextWithLevel(ctx, slog.LevelDebug)
// Once again, try to output debug log.
lg.DebugContext(ctx, "log should be output")
}
4. 論理ファイル機能
論理ファイル機能は、物理ファイルを仮想的にサイズ無限のファイルとして扱う機能です。
ログのファイル出力においてはログファイルのローテーションや世代管理、ログファイル圧縮などを考慮する必要があります。 論理ファイルにより、ロガー自身がこれら物理ファイルの存在や管理を意識する必要がなくなります。 これは Linux における論理ボリューム管理(LVM)に似ています。
論理ファイル機能は、アクティブな1つの物理ファイルと、複数の履歴ファイルを管理します。
履歴ファイルのファイル名は固定値(例えば app.log
)であり、履歴ファイルにはカウンター値や日時を含みます(例えば app-20060102-150405.log
)。
履歴ファイルのファイル名に指定できるフォーマット指定子は以下の表のとおりです。
フォーマット指定子が指定されていない場合、ファイル管理のために自動的に%i
が付与されます。
なお、MaxAge
によるファイル管理を行う場合、日時を含むフォーマット指定子をファイル名に含む必要があります。
Format | Value | Range |
---|---|---|
%Y | YYYY 4 digits year | 0 <= YYYY |
%M | MM 2 digits month | 1 <= MM <= 12 |
%D | DD 2 digits day of month | 1 <= DD <= 31 |
%h | hh 2 digits hour | 0 <= hh <= 23 |
%m | mm 2 digits minute | 0 <= mm <= 59 |
%s | ss 2 digits second | 0 <= ss <= 59 |
%u | unix second with free digits | 0 <= unix |
%i | index with free digits | 0 <= index |
%H | hostname | |
%U | user id. “-1” on windows | |
%G | user group id. “-1” on windows | |
%p | pid (process id) | |
%P | ppid (parent process id) |
論理ファイル機能は以下の項目で物理ファイルを管理します。 これらは組み合わせて利用することも可能です。
MaxAge
: 指定時間より古いファイルを削除 (%Y
,%M
,%D
の全て、または%u
が必須)MaxHistory
: 履歴ファイルが指定数より多い分を削除MaxTotal
: ファイルの合計サイズが指定値を超えた場合に削除
また、履歴ファイルはGzip圧縮により圧縮することも可能です。
セキュリティに関する特記事項
ログ関連機能ではログマスク機能を持ちません。 必要に応じてユーザ側で実装してください。
性能に関する特記事項
ログ出力は必ずしもif
で囲む必要はありません。
可読性と性能を考慮して使い分けることを推奨します。
Debug
レベルのログはほとんどの場合、本番環境では出力されないのにくわえて出力項目も多くなりがちのため if 文で囲むことが推奨されます。
一方で、Error
レベルのログはほとんどの場合、本番環境で出力されるため if 文の利用は冗長です。
lg.InfoContext(ctx, "log message")
if lg.InfoEnabled(ctx) {
lg.InfoContext(ctx, "log message")
}
実装例・使い方
MaxAgeによるファイル管理
以下の実装例はMaxAge
により履歴管理をおこないます。
1つのファイルのサイズが500バイトを超えないように物理ファイルがローテーションされ、
30秒前より古いファイルは削除されます。
履歴ファイルのファイル名に含まれる%u
はUnix秒を表しています。
なお、MaxAge
を利用する場合は、最低限%Y
/%M
/%D
のすべてまたは%u
を履歴ファイル名に含む必要があります。
時間指定子(%h
/%m
/%s
)が含まれない場合、それらは値がゼロ(00時/00分/00秒)として扱われます。
1package main
2
3import (
4 "fmt"
5 "time"
6
7 "github.com/aileron-projects/go/zlog"
8)
9
10func main() {
11 c := &zlog.LogicalFileConfig{
12 Manager: &zlog.FileManagerConfig{
13 MaxAge: 30 * time.Second,
14 Pattern: "app.%u.log",
15 },
16 RotateBytes: 500, // Max size of a single file.
17 FileName: "app.log", // Active file name.
18 }
19 f, err := zlog.NewLogicalFile(c)
20 if err != nil {
21 panic(err)
22 }
23
24 initial := time.Now()
25 for {
26 fmt.Println("Now:", time.Now().Unix(), "\t", "30s before:", time.Now().Unix()-30)
27 fmt.Fprintln(f, time.Now(), "Time past ", time.Since(initial))
28 time.Sleep(time.Second)
29 }
30}
MaxHistoryによるファイル管理
以下の実装例はMaxHistory
により履歴管理をおこないます。
1つのファイルのサイズが500バイトを超えないように物理ファイルがローテーションされます。
履歴ファイルの数が5つより多くならないように、古いファイルを削除します。
1package main
2
3import (
4 "fmt"
5 "time"
6
7 "github.com/aileron-projects/go/zlog"
8)
9
10func main() {
11 c := &zlog.LogicalFileConfig{
12 Manager: &zlog.FileManagerConfig{
13 MaxHistory: 5,
14 Pattern: "app.%i.log",
15 },
16 RotateBytes: 500, // Max size of a single file.
17 FileName: "app.log", // Active file name.
18 }
19 f, err := zlog.NewLogicalFile(c)
20 if err != nil {
21 panic(err)
22 }
23
24 initial := time.Now()
25 for {
26 fmt.Fprintln(f, time.Now(), "Time past ", time.Since(initial))
27 time.Sleep(time.Second)
28 }
29}
MaxTotalによるファイル管理
以下の実装例はMaxTotalBytes
により履歴管理をおこないます。
1つのファイルのサイズが500バイトを超えないように物理ファイルがローテーションされます。
全ての履歴ファイルのファイルサイズの合計が数が2000バイトより大きくならないように、古いファイルから削除します。
1package main
2
3import (
4 "fmt"
5 "time"
6
7 "github.com/aileron-projects/go/zlog"
8)
9
10func main() {
11 c := &zlog.LogicalFileConfig{
12 Manager: &zlog.FileManagerConfig{
13 MaxTotalBytes: 2000,
14 Pattern: "app.%i.log",
15 },
16 RotateBytes: 500, // Max size of a single file.
17 FileName: "app.log", // Active file name.
18 }
19 f, err := zlog.NewLogicalFile(c)
20 if err != nil {
21 panic(err)
22 }
23
24 initial := time.Now()
25 for {
26 fmt.Fprintln(f, time.Now(), "Time past ", time.Since(initial))
27 time.Sleep(time.Second)
28 }
29}
ログのファイル出力
論理ファイルはio.Writerのインターフェースを実装しているため、 ロガーの出力先として利用することが可能です。
Goの標準パッケージであるlog/slog.Handlerと組み合わせて利用する場合の利用例を以下に示します。 このような実装により、履歴管理機能付きのロギングを実現することが可能です。
package main
import (
"context"
"log/slog"
"time"
"github.com/aileron-projects/go/zlog"
"github.com/aileron-projects/go/zlog/zslog"
)
func main() {
c := &zlog.LogicalFileConfig{
Manager: &zlog.FileManagerConfig{
MaxHistory: 5,
Pattern: "app.%i.log",
},
RotateBytes: 500, // Max size of a single file.
FileName: "app.log", // Active file name.
}
f, err := zlog.NewLogicalFile(c)
if err != nil {
panic(err)
}
h := slog.NewTextHandler(f, nil) // Create slog handler with f.
lg := zslog.New(h)
for {
lg.InfoContext(context.Background(), "log message", "now", time.Now())
time.Sleep(time.Second)
}
}
参考資料
フィードバック
このページは役に立ちましたか?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.