📝

GCP ログ ライブラリ v 1.5 for Golang

2022/07/21に公開

GCPのKubernetesでログ出力するのに便利なGolangのライブラリです。

https://cloud.google.com/blog/products/devops-sre/more-support-for-structured-logs-in-new-version-of-go-logging-library

使い方

go get cloud.google.com/go/logging

Ginで利用するサンプルで説明します。

ミドルウェアでロガーを初期化し、contextに保持します。

main.go
import "cloud.google.com/go/logging"

func main() {
	r := gin.New()
	r.Use(gcpLogger)
gcpLogger
func gcpLogger(c *gin.Context) {
	client, err := logging.NewClient(c, "my-project")
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"ok": false})
	}
	// ロガーを生成
	logger := client.Logger("my-category-log",
	    logging.SourceLocationPopulation(
	        logging.PopulateSourceLocationForDebugEntries),
            logging.RedirectAsJSON(os.Stdout))

	c.Set("MyLogger", logger) // contextにロガーを保持

	c.Next()

	if err := client.Close(); err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"ok": false})
	}
}

ハンドラーからロガーを使用します。

MyHandler
func MyHandler(c *gin.Context) {
	v, _ = c.Get("MyLogger") // ロガーの取り出し
	logger, ok := v.(*logging.Logger)
	
	msg := fmt.Sprintf("foo=%s, bar=%s", foo, bar) // 出力メッセージ

        // ログ出力
	logger.Log(logging.Entry{
	    Severity: logging.Debug, 
	    Payload: msg, // メッセージ
	    HTTPRequest: &logging.HTTPRequest{
		Request: c.Request,
	}})

次のようにGCPコンソールに構造化ログが出力されます。

解説

trace (x-cloud-tracing-context)

このライブラリを使う最大のメリットは、traceを自動設定してくれることでしょう。

logging.Entry()HTTPRequestに、http.Requestを渡すとx-cloud-tracing-contextを取り出してセットしてくれます。spandIdも設定してくれますので、GCPの膨大なログで迷子にならなくてすみます。[1]

HTTPRequest: &logging.HTTPRequest{
		Request: c.Request
}		

sourceLocation (ソースコードの行番号を出力)

どこに仕込んだlogger.Log()から出力したログか分かるようにソースコードの行番号を表示します。ロガーを生成するところで以下のように指定します。これはseverityDebugの時だけ行番号を出力するオプションになります。

logging.SourceLocationPopulation(logging.PopulateSourceLocationForDebugEntries)

常に出力する場合は、logging.AlwaysPopulateSourceLocationを指定します。ただしパフォーマンスが悪化するのでご注意ください。

gRPC経由でのログ出力

デフォルトではGCPのCloud Logging APIをgRPCで呼び出してログ出力します。その場合は以下のようにlogging.RedirectAsJSON()を削除します。[2]
ログ出力を非同期に実行できるためログ出力によるパフォーマンスの低下を避けることができます。

-client.Logger('my-logger', logging.RedirectAsJSON(os.Stdout))
+client.Logger('my-logger')

message メッセージ

Payloadに構造体をそのまま渡して出力できます。

type Category struct {
	ID     string `json:"id"`
	Name   string `json:"name"`
}

cat := Category{...}
logging.Entry{Payload: cat, ...}
脚注
  1. Kubernetesではリクエストが複数のマイクロサービスを呼び出すためログ出力が分断されてしまい追跡がパズルのように難しくなります。GCPではリクエストごとに固有のID(trace)をHTTPヘッダ(x-cloud-tracing-context)にセットし、GCPコンソール画面のフィルターで絞り込み検索することができます。 ↩︎

  2. ローカル実行すると動かなくなります。 ↩︎

Discussion