Help us understand the problem. What is going on with this article?

Cloud Functions上でStackdriver Loggingを使った構造化ロギング

はじめに

Cloud Functions上の処理で標準出力や標準エラー出力を行うと、自動的にStackdriver Loggingに記録されますが、この機能では以下のようなことを行えません。

  • payloadとしてjsonPayloadを使用し、ログを構造化する
  • ログレベルを設定する

Stackdriver Loggingの結果をそのままBigQueryに流し、あとで解析したい場合は、構造化できていた方が色々と都合が良いですが、標準出力を使うだけでは構造化ロギングが実現できません。
そこで、Cloud Functions上で構造化ロギングをする方法を紹介します。

ロギング方法

まず、ソースコードを提示し、次にその解説をします。

ソースコード

sample.go
package sample

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"

    "cloud.google.com/go/logging"
    mrpb "google.golang.org/genproto/googleapis/api/monitoredres"
)

var (
    logger       *logging.Logger
    projectID    string
    functionName string
    region       string
)

func init() {
    projectID = os.Getenv("GCP_PROJECT")
    functionName = os.Getenv("FUNCTION_NAME")
    region = os.Getenv("FUNCTION_REGION")

    client, err := logging.NewClient(context.Background(), projectID)
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
    }

    logName := "cloudfunctions.googleapis.com%2Fcloud-functions"

    logger = client.Logger(logName, logging.CommonResource(&mrpb.MonitoredResource{
        Type: "cloud_function",
        Labels: map[string]string{
            "function_name": functionName,
            "project_id":    projectID,
            "region":        region,
        },
    }))

}
func Hello(w http.ResponseWriter, r *http.Request) {
    defer logger.Flush()
    logger.Log(
        logging.Entry{
            Severity: logging.Info,
            Labels:   map[string]string{"execution_id": r.Header.Get("Function-Execution-Id")},
            Payload:  map[string]string{"msg": "hello"},
            Trace:    fmt.Sprintf("projects/%s/traces/%s", projectID, r.Header.Get("X-Goog-Cloud-Trace-Parent")),
        })
    fmt.Fprint(w, `{"msg": "hello"}`)
}

コードの解説

初期化処理

init()はインスタンスが起動したときに1度だけ起動する関数です。
環境変数を取得し1、それをもとにロガーを作成しています。ここで、MonitoredResourceを指定し、Cloud Functionsから送られていることをStackdriverに識別させる設定を行います。

メイン処理

Payloadには保存したいデータをmapで指定すれば、jsonPayloadでStackdriverに記録されます。また、Severityでログレベルが設定可能です。
Cloud Functionは呼び出される度にexecution_idを発行します。それがリクエストヘッダに入っているので、それをLabelsに登録しています。また、複数のログが1つのリクエストから生じる場合に、それらをグルーピングできるように、全てのログに同じtraceを記述するという方法がStackdriver Loggingではとられています2。このtraceもリクエストヘッダにあるので、それをTraceに登録しています。

グルーピングはexecution_idとtraceのどちらか一方だけで十分だと思いますが、
ececution_idはCloud Functionsが提供するグルーピング機能
traceはStackdriver Loggingが提供するグルーピング機能
という認識になります。

注意点

Cloud FunctionsがStackdriverに書き込めるように事前に権限を設定しておく必要があります3
構造化ログをBigQueryに流したいのであれば、Cloud Functions上では標準出力に改行なしのJSONを出力し、
Stackdriver Logging → Cloud Pub/Sub → Dataflow → BigQuery
の流れで、Dataflowに処理させても良いと思います。むしろこっちの方が良い?

追記

JSONをそのままBigQueryで保存し、BigQueryのJSON関数を使うこともできますが、JSON内容が巨大になる場合、構造化してBigQueryに保持しておいた方が、分析する際の利用料金を抑えられるメリットがあります。

brln
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした