Go でのログ記録の方法

完了

プログラムでは、ログが重要な役割を果たします。何らかの問題が発生した場合に、確認できる情報元となるからです。 通常、エラーが発生すると、エンド ユーザーには、プログラムに問題があることを示すメッセージが表示されるのみです。 開発者の観点では、単純なエラー メッセージではなく、多くの情報が必要です。 これは主に、問題を再現して、適切な修正プログラムを作成する必要があるという理由からです。 このモジュールでは、Go でのログ記録のしくみについて説明します。 また、常に実装が必要な、いくつかの事項についても説明します。

log パッケージ

まず、Go には、ログを操作するための単純な標準パッケージが用意されています。 fmt パッケージを使用する場合と同様の方法で使用することができます。 標準パッケージでは、ログ レベルは指定されません。また、パッケージごとに個別のロガーを構成することはできません。 より複雑なログ構成を作成する必要がある場合は、ログ記録フレームワークを使用して作成できます。 ログ記録フレームワークについては、後で説明します。

最も簡単にログを使用する方法を次に示します。

import (
    "log"
)

func main() {
    log.Print("Hey, I'm a log!")
}

上記のコードを実行すると、次の出力が表示されます。

2020/12/19 13:39:17 Hey, I'm a log!

既定では、log.Print() 関数に、ログ メッセージのプレフィックスとして日付と時刻が含まれます。 fmt.Print() を使用すると、同じ動作を取得できますが、log パッケージでは、ファイルへのログの送信など、他の操作を行うことができます。 log パッケージの機能の詳細については、後で説明します。

log.Fatal() 関数を使用すると、os.Exit(1) を使用した場合と同様に、エラーをログに記録して、プログラムを終了することができます。 試してみるには、次のコード スニペットを使用しましょう。

package main

import (
    "fmt"
    "log"
)

func main() {
    log.Fatal("Hey, I'm an error log!")
    fmt.Print("Can you see me?")
}

上記のコードを実行すると、次の出力が表示されます。

2020/12/19 13:53:19  Hey, I'm an error log!
exit status 1

最後の行 fmt.Print("Can you see me?") が実行されていないことに注意してください。 これは、log.Fatal() 関数の呼び出しによってプログラムが停止するためです。 log.Panic() 関数を使用すると、次のように、同様の動作を取得することができます。これにより、panic() 関数も呼び出されます。

package main

import (
    "fmt"
    "log"
)

func main() {
    log.Panic("Hey, I'm an error log!")
    fmt.Print("Can you see me?")
}

上記のコードを実行すると、次の出力が表示されます。

2020/12/19 13:53:19  Hey, I'm an error log!
panic: Hey, I'm an error log!

goroutine 1 [running]:
log.Panic(0xc000060f58, 0x1, 0x1)
        /usr/local/Cellar/go/1.15.5/libexec/src/log/log.go:351 +0xae
main.main()
        /Users/christian/go/src/helloworld/logs.go:9 +0x65
exit status 2

ログ メッセージを引き続き取得していますが、エラー スタック トレースも取得するようになりました。

もう 1 つの重要な関数に、log.SetPrefix() があります。 これを使用すると、プログラムのログ メッセージにプレフィックスを追加できます。 たとえば、次のコード スニペットを使用できます。

package main

import (
    "log"
)

func main() {
    log.SetPrefix("main(): ")
    log.Print("Hey, I'm a log!")
    log.Fatal("Hey, I'm an error log!")
}

上記のコードを実行すると、次の出力が表示されます。

main(): 2021/01/05 13:59:58 Hey, I'm a log!
main(): 2021/01/05 13:59:58 Hey, I'm an error log!
exit status 1

プレフィックスを 1 回設定すると、ログが記録された関数の名前などの情報が、そのログに含まれるようになります。

Go の Web サイトに掲載されているその他の関数を参照してください。

ファイルへのログ記録

ログをコンソールに出力するだけではなく、ログをファイルに送信し、後で、またはリアルタイムで処理できるようにすることもできます。

なぜログをファイルに送信する必要があるのでしょうか。 まず、エンド ユーザーに対して特定の情報を非表示にすることができます。 関心が寄せられていない場合や、機密情報を公開している場合があります。 ファイルにログがある場合は、すべてのログを 1 か所で集中管理し、他のイベントと関連付けることができます。 このパターンは一般的なものであり、コンテナーなどの、一時的な分散アプリケーションを作成する場合に使用します。

次のコードを使用して、ファイルへのログの送信をテストしてみましょう。

package main

import (
    "log"
    "os"
)

func main() {
    file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        log.Fatal(err)
    }

    defer file.Close()

    log.SetOutput(file)
    log.Print("Hey, I'm a log!")
}

上記のコードを実行すると、コンソールには何も表示されません。 ディレクトリには、log.Print() 関数を使用して送信したログを含む、info.log という名前の新しいファイルが表示されます。 まず、ファイルを作成するか開いてから、すべての出力をファイルに送信するように log パッケージを構成する必要があることに注意してください。 その後は、通常どおり、log.Print() 関数を使用し続けることができます。

ログ記録フレームワーク

最終的に、log パッケージの関数が不十分な場合があります。 独自のライブラリを作成するのではなく、ログ記録フレームワークを使用するのが便利かもしれません。 Go 用のいくつかのログ記録フレームワークには、LogruszerologzapApex があります。

zerolog でできることを、詳しく見てみましょう。

まず、パッケージをインストールする必要があります。 このシリーズに取り組んでいる場合は、おそらく既に Go モジュールを使用しているので、何もする必要はありません。 念のため、ワークステーションで次のコマンドを実行して、zerolog ライブラリをインストールします。

go get -u github.com/rs/zerolog/log

次に、このコード スニペットを使用して、試してみます。

package main

import (
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
    log.Print("Hey! I'm a log message!")
}

上記のコードを実行すると、次の出力が表示されます。

{"level":"debug","time":1609855453,"message":"Hey! I'm a log message!"}

正しいインポート名を含める必要があることに注意してください。その後は、通常どおり、log.Print() 関数を使用し続けることができます。 また、出力が JSON 形式に変更されることに注目してください。 JSON は、集中管理された場所で検索を実行する場合に便利なログの形式です。

その他にも、次のように、コンテキスト データをすばやく含めることができる便利な機能があります。

package main

import (
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

func main() {
    zerolog.TimeFieldFormat = zerolog.TimeFormatUnix

    log.Debug().
        Int("EmployeeID", 1001).
        Msg("Getting employee information")

    log.Debug().
        Str("Name", "John").
        Send()
}

上記のコードを実行すると、次の出力が表示されます。

{"level":"debug","EmployeeID":1001,"time":1609855731,"message":"Getting employee information"}
{"level":"debug","Name":"John","time":1609855731}

従業員 ID をコンテキストとして追加したことに注目してください。 これは、別のプロパティとして、ログ行の一部となります。 また、含めるフィールドが厳密に型指定されていることを強調することが重要です。

平準化したログ記録の使用、書式設定されたスタック トレースの使用、さまざまな出力の管理を目的とした複数のロガー インスタンスの使用など、zerolog でその他の機能を実装することができます。 詳細については、GitHub サイトを参照してください。