Stackdriver Logging からログを取得する

# 概要
[![English](https://img.shields.io/badge/English-here-lightgrey.svg?style=flat)](https://www.jkawamoto.info/blogs/get-logs-from-stackdriver-logging/)
[Google Cloud Platform](https://cloud.google.com/) からログデータを取得する．
[logadmin](https://godoc.org/cloud.google.com/go/logging/logadmin) パッケージが提供する，[Stackdriver](https://cloud.google.com/stackdriver/) クライアントは使いやすいのだが，
手に入る各ログエントリーは [logging.Entry](https://godoc.org/cloud.google.com/go/logging#Entry)型で，ペイロードの取得が手間である．
本稿では，ログエントリー及びそのペイロード取得方法をまとめる．

# logadmin パッケージ
[logadmin](https://godoc.org/cloud.google.com/go/logging/logadmin) は，比較的新しいパッケージで， ログの取得に関わる操作を提供している． 
変数`filter`に入っているクエリにマッチするログデータのみを取得するならば，
下記の通りでよい．

```go
import (
    "cloud.google.com/go/logging/logadmin"
    "golang.org/x/net/context"
    "google.golang.org/api/iterator"
)

func GetLogEntries(filter string) error{
    ctx := context.Background()
    client, err := logadmin.NewClient(ctx, "your-project-id")
    if err != nil {
        return err
    }
    defer client.Close()

    iter := client.Entries(ctx, logadmin.Filter(filter))
    for {
        e, err := iter.Next()
        if err == iterator.Done {
            return nil
        } else if err != nil {
            return err
        }
        // logging.Entry型のログデータ e に対する操作
    }
}
```

# logging.Entry
上記の操作で手に入るログデータは，[logging.Entry](https://godoc.org/cloud.google.com/go/logging#Entry)構造体になっている．
この構造体では，ペイロードは `interface{}`型と定義されており，利用するためにはキャストが必要になる．
なお，Google Cloud Platform ではこのペイロードはほとんどの場合 JSON オブジェクトである．
また，実際に利用する場合は，フィルタの値からスキーマがある程度分かっていることが多いと思う．

さて，手元で調べたところ，このペイロードは [`structpb.Struct`型](https://godoc.org/github.com/golang/protobuf/ptypes/struct#Struct)へのポインタになっている．
この`structpb.Struct`型から目的のログデータを表す構造体へ変換するのだが，
簡単な方法が分からなかったので，自作の関数 `structpbconv.Convert` で変換することにした．
（そのコードは[ここ](https://github.com/jkawamoto/structpbconv)で公開している）

# 型変換
例えば次のコードは，この[structpbconv](https://github.com/jkawamoto/structpbconv)を使って，structpb.Structから[Compute Engine](https://cloud.google.com/compute/) VM インスタンスの
activity_log を取得している．

```go
import (
    "github.com/golang/protobuf/ptypes/struct"
    "github.com/jkawamoto/structpbconv"
)


type ActivityPayload struct {
    EventTimestampUs string `structpb:"event_timestamp_us"`
    EventType        string `structpb:"event_type"`
    TraceID          string `structpb:"trace_id"`
    Actor            struct {
        User string
    }
    Resource struct {
        Zone string
        Type string
        ID   string
        Name string
    }
    Version      string
    EventSubtype string `structpb:"event_subtype"`
    Operation    struct {
        Zone string
        Type string
        ID   string
        Name string
    }
}

func NewActivityPayload(payload *structpb.Struct) *ActivityPayload {
    var res ActivityPayload
    structpbconv.Convert(payload, &res)
    return &res
}
```

`ActivityPayload`構造体は， activity_log のペイロードに合わせて作成する．
実際は JSON オブジェクトなので，小文字+アンダーバーなど命名規則が異なっている部分の対応付けには，
`structpb` タグを用いる．

先ほどのログエントリー取得と合わせると，

```go
// イベントタイプが GCE_OPERATION_DONE のログだけを取得する
iter := client.Entries(ctx, logadmin.Filter("jsonPayload.event_type = \"GCE_OPERATION_DONE\""))
for {
    e, err := iter.Next()
    if err == iterator.Done {
        return nil
    } else if err != nil {
        return err
    }

    // logging.Entry型のログデータ e に対する操作
    if s, ok := e.Payload.(*structpb.Struct); ok {
        payload := NewActivityPayload(s)

        fmt.Println(payload.TraceID) // trace_id を出力
    }

}
```

などのようにログデータを使用できる．