寝ても覚めてもこんぴうた

プログラム書いたり、ネットワーク設計したり、サーバ構築したり、車いじったり、ゲームしたり。そんなひとにわたしはなりたい。 投げ銭は kyash_id : chidakiyo マデ

GCP でアプリケーションのログや特定の値をいい感じに BigQuery にエクスポートしたい

f:id:chidakiyo:20201111192602j:plain

アプリケーションを実装していて、ログをかっちり決めたカラムに挿入したい、や、
難しい API を利用せずに BQ に特定のデータを挿入したいという場合ありますよね。

普通にやると、BigQuery の API を経由し、ストリーミングインサートするなどちょっとひと手間かけてデータを BQ に送っていると思います。

今回書く方法は、ある程度意図した構造(カラム)の状態で BQ に気軽にデータを送る方法になります。

サンプルとして、 Cloud Run での例になりますが、Cloud Logging に出力できる GCP のサービスならほぼ同じように利用できると思います。

概要

Cloud Run から stdout に対して JSON Payload 形式のログを出力します。
出力したログは Log Router を経由し、対象のログを絞り込みます。
絞り込んだログは BQ の Dataset へデータを流し込みます。

Go のコードを用意する

最低限のコードは以下のようなイメージになります

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/export", func(writer http.ResponseWriter, request *http.Request) {

        // BQに出力したい構造にしておく
        type ExportData struct {
            Type    string `json:"type"`
            Name    string `json:"name"`
            Num     int64  `json:"num"`
            Message string `json:"msg"`
        }

        // 値を詰め込む
        payload := &ExportData{
            Type:    "Type_A",
            Name:    "Export Data A Type",
            Num:     3,
            Message: "Export message.",
        }

        // stdoutにJSONとして吐き出す
        json.NewEncoder(os.Stdout).Encode(payload)

        fmt.Fprintf(writer, "ok")
    })
    port := "8080"
    if s := os.Getenv("PORT"); s != "" {
        port = s
    }
    if err := http.ListenAndServe(":"+port, mux); err != nil {
        log.Fatalf("http.ListenAndServe: %v", err)
    }
}

Go の構造体を作成し、適当なラベル(BQにカラムとして出力されるので適切なもの)をつけ、
stdout に対して JSON を吐き出します。

これを動かすと Cloud Logging にログが出力されます。

Logs Router で条件にマッチしたデータを BQ に送り込む Sink を作成する

Sink details, Sink destination は適当に設定しましょう。笑
Sink destination はもちろん BigQuery dataset を選択し、この設定を行う前に事前にBQ側でデータセットを作成しておく必要があります。

Choose logs to include in sink の設定だけ注意が必要で、
今回の例では

resource.type="cloud_run_revision"
jsonPayload.type = "Type_A"

f:id:chidakiyo:20201111192050p:plain

のように設定しましたが、JSON Payload の type ごとに BQ のデータセットを分けたい等の場合にはこのような設定にするのも良いでしょうし、Previewして自分が必要なログが対象になっていることを確認して利用してください。

完了したら Sink を作成します。
Sink の作成が完了しても、体感的に Sink の条件が反映され動き出すまでに 10分程度のラグがあるような気がします。
設定したのにうごかない!!と思っても、ちょっとのんびり待って、再度確認してみると良さそうです。

BQ に対してクエリする

ここまで設定がうまく行っていれば、アプリケーションにアクセスすることで BQ にログが保存されます。
先程構造体(JSON)として出力していますが、 BQ には構造体以外の Cloud Logging のログ部分も出力されていますが、
ここで構造体部分だけクエリしてみます。

以下のようなクエリを実行します

select jsonPayload.* from `{dataset}.run_googleapis_com_stdout` 

dataset名やテーブル名が異なることがあると思いますので、環境に合わせて指定します。
このクエリを実行すると以下のような結果が表示されます。

f:id:chidakiyo:20201111192115p:plain

4度ほど実行したので、4行の結果が出力されています。
今回の例では同一の値を出力してしまったので良いクエリの例が示せませんが、アプリケーションの任意の値を出力している場合には、一般的な SQL の知識で自由に利用できます。

まとめ

ちょっと雑な記事になってしまったけど、アプリケーション内から BQ に気軽に出力できると便利だと思う。
stdoutに出力する形だとローカル実行のテストなども行いやすいし。
ユースケースとしては、アプリの分析用の指標をポロポロ落としておいて、あとからざっと集計するなどアイディア次第で BQ と組み合わせて色々やれそうです。

ではでは。