分散システムに依存している組織は、チャネルやgoroutinesなどの並行処理の機能を利用するためにGo言語でしばしばアプリケーションを書いています(例: Goroutines, GOLANGなど.., Heroku、Basecamp、Cockroach Labs、および Datadog など)。 Go アプリケーションの構築またはサポートを担当している場合、よく考えられたログ記録戦略は、ユーザーの行動を理解し、エラーを特定し、アプリケーションのパフォーマンスを監視するのに役立ちます。
この投稿では、Golang ログを管理するためのいくつかのツールとテクニックを紹介します。 まず、さまざまな種類の要件に対してどのログ記録パッケージを使用するかという疑問から始めます。 次に、ログをより検索しやすく、信頼できるものにし、ロギング設定のリソースのフットプリントを減らし、ログメッセージを標準化するためのいくつかのテクニックについて説明します。
- ロギング パッケージを知る
- Use log for simplicity
- Use logrus for formated logs
- Use glog if you’re concerned about volume
- Best practices for writing and storing Golang logs
- Avoid declaring goroutines for logging
- Write your logs to a file
- Centralize Golang logs
- Track Golang logs across microservices
- Clean and comprehensive Golang logs
ロギング パッケージを知る
Go はロギング パッケージを選択する際に、豊富な選択肢を提供し、以下では、これらのうちのいくつかについて説明します。 logrus は、私たちがカバーするライブラリの中で最も人気があり、一貫したロギング フォーマットの実装を支援しますが、他のライブラリには言及する価値のある特別な使用事例があります。 このセクションでは log、logrus、および glog ライブラリについて調査します。
Use log for simplicity
Golang の組み込みロギングライブラリである log
は、標準エラーに書き込み、設定の必要なくタイムスタンプを追加できるデフォルトロガーを持っています。 リッチで構造化されたログを生成するよりも、 コードから素早くフィードバックを得ることが重要な場合、 ローカル開発ではこれらの荒削りなログを使うことができます。
例えば、ゼロで割ろうとしたときにプログラムを終了させずに呼び出し元に エラーを返す除算関数を定義できます。
package mainimport ( "log" "errors" "fmt" )func divide(a float32, b float32) (float32, error) { if b == 0 { return 0, errors.New("can't divide by zero") } return a / b, nil}func main() { var a float32 = 10 var b float32 ret, err := divide(a,b) if err != nil{ log.Print(err) } fmt.Println(ret)}
この例はゼロで割るので、次のログメッセージを出力します:
2019/01/31 11:48:00 can't divide by zero
Use logrus for formated logs
私たちは logrus、JSON でログを取るためによく適している構造化ロギング用に設計されたログパッケージを使って Golang ログを書くことを推奨します。 JSONフォーマットはマシンがあなたのGolangログを簡単にパースすることを可能にします。 そして、JSON はよく定義された標準なので、新しいフィールドを含めることによってコンテキストを追加するのが簡単です。
logrus を使用して、以下に示すように、WithFields
関数を使用して JSON ログに追加する標準フィールドを定義することができます。 そして、Info()
、Warn()
、Error()
などの異なるレベルでロガーへの呼び出しを行うことができます。 logrus ライブラリは、ログを自動的に JSON として書き出し、標準のフィールドと、その場で定義したフィールドを挿入します。
package mainimport ( log "github.com/sirupsen/logrus")func main() { log.SetFormatter(&log.JSONFormatter{}) standardFields := log.Fields{ "hostname": "staging-1", "appname": "foo-app", "session": "1ce3f6v", } log.WithFields(standardFields).WithFields(log.Fields{"string": "foo", "int": 1, "float": 1.1}).Info("My first ssl event from Golang")}
結果として得られるログは、メッセージ、ログ レベル、タイムスタンプ、および JSON オブジェクト内の標準フィールドを含みます:
{"appname":"foo-app","float":1.1,"hostname":"staging-1","int":1,"level":"info","msg":"My first ssl event from Golang","session":"1ce3f6v","string":"foo","time":"2019-03-06T13:37:12-05:00"}
Use glog if you’re concerned about volume
いくつかのログ ライブラリは特定のレベルでのログを有効または無効にすることができ、開発と生産の間を移行する際にログ量をチェックするために役に立ちます。 このようなライブラリのひとつに glog
があり、コマンドラインでフラグ (例: -v
は冗長性) を使用して、コード実行時にロギングレベルを設定することができます。
例えば、先ほどと同じ “Can’t divide by zero” エラーを書くために glog を使うことができますが、それは 2
のverbosityレベルでログをとっている場合だけです。 冗長度は任意の符号付き 32 ビット整数に設定することができ、また関数 Info()
、Warning()
、Error()
、Fatal()
を使用して冗長レベル 0
から 3
(それぞれ)を割り当てることができます。
if err != nil && glog.V(2){ glog.Warning(err) }
生産時に特定のレベルのみロギングすることにより、アプリケーションにあまりリソースがかからないようにすることが可能です。 同時に、ユーザーへの影響がない場合、アプリケーションとのできるだけ多くのインタラクションをログに記録することはしばしば良いアイデアであり、その後、Datadog のようなログ管理ソフトウェアを使用して、調査に必要なデータを見つけます
Best practices for writing and storing Golang logs
いったんログライブラリを選択したら、コード内のどこでロガーへの呼び出しを行い、ログを格納し、それらを理解する方法についても計画したいと思うでしょう。 このセクションでは、Golang ログを整理するための一連のベスト・プラクティスを推奨します:
- Goroutines 内ではなく、メイン アプリケーション プロセス内から logger を呼び出します。
- Send your logs to a central platform so you can analyze and aggregate them.
- Use HTTP headers and unique IDs to log user behavior across microservices.
Avoid declaring goroutines for logging
There are two reasons to avoid you own goroutines creating to handle writing logs.Specialization for logs. まず、ロガーの重複が同じ io.Writer
にアクセスしようとするため、並行処理の問題につながる可能性があります。 第二に、ログ記録ライブラリは通常、並行性の問題を内部で管理するゴルーチン自体を開始し、独自のゴルーチンを開始しても干渉するだけです。
Write your logs to a file
たとえログを中央プラットフォームに送信していても、最初にローカルマシンのファイルにそれらを書き込むことを推奨します。 ログがネットワークで失われることなく、常にローカルで利用可能であることを確認したいと思うでしょう。 また、ファイルに書き出すことで、ログを書き出す作業と、セントラル・プラットフォームへ送信する作業を切り離すことができます。 アプリケーション自身が接続を確立したり、ログをストリームする必要はなく、これらの作業はDatadog Agentのような専門ソフトウェアに任せることができます。 永続的ストレージをまだ含まないコンテナ化されたインフラストラクチャ内で Go アプリケーションを実行している場合 (AWS Fargate で実行しているコンテナなど)、コンテナの STDOUT および STDERR ストリームから直接ログを収集するようにログ管理ツールを構成することができます (これは Docker と Kubernetes では異なって処理されます)。 一貫性のない属性は、ユーザーを混乱させ、同じ画像の一部を形成するはずのログを相関させることを不可能にします。
標準化を強制する良い方法は、アプリケーション コードとログ記録ライブラリの間にインターフェイスを作成することです。 インターフェイスは特定のフォーマットを実装した定義済みのログメッセージを含み、 ログメッセージを検索、グループ化、フィルタリングできることを保証することにより、 問題の調査を容易にします。