Stackdriver Logging からログを取得する(その２)


# 概要
[Google Cloud Platform](https://cloud.google.com/) からログデータを取得する場合，
[Stackdriver Logging からログを取得する](http://qiita.com/jkawamoto/items/8e3e6c3e2a094a4ae214)に記した[logadmin](https://godoc.org/cloud.google.com/go/logging/logadmin)パッケージを利用する方法に加えて，
APIバージョン2の[logging](https://godoc.org/cloud.google.com/go/logging/apiv2)を使うこともできる．
（バージョン１の方はログを書き出せるが読み出せなかったはず）

ざっと見た限り，[logadmin](https://godoc.org/cloud.google.com/go/logging/logadmin)パッケージに比べて取得できるログの種類が多いように見えたので，
その使用方法についてまとめる．

# loggingパッケージ(Ver.2)
[logadmin](https://godoc.org/cloud.google.com/go/logging/logadmin) パッケージのドキュメントにはベータ版との記載されているが，
バージョン２のloggingパッケージのドキュメントには実験的(EXPERIMENTAL)と書かれている．
将来大幅な変更があるかもしれないので，その点は注意．

loggingパッケージのクライアントを使って，変数 `filter` にマッチするログデータを取得する場合，
次のようなコードになる．

```go
import (
	"context"

	"cloud.google.com/go/logging/apiv2"
	"google.golang.org/api/iterator"
	loggingpb "google.golang.org/genproto/googleapis/logging/v2"
)

func GetLogEntries(ctx context.Context, filter string) error{

	client, err := logging.NewClient(ctx)
	if err != nil {
		return
	}
	defer client.Close()

	iter := client.ListLogEntries(ctx, &loggingpb.ListLogEntriesRequest{
		ResourceNames: []string{
			fmt.Sprintf("projects/%v", "your-project-id"),
		},
		Filter: filter,
	})

	for {
		e, err := iter.Next()
		if err == iterator.Done {
			break
		} else if err != nil {
			return err
		}
		
		// ログデータ e に対する処理
		
	}

	return nil
}
```

# logadmin パッケージとの違い
まず一つ目の違いは，logadminパッケージではプロジェクトIDをクライアントの作成時に渡していたのに対し，
loggingパッケージではログデータのリクエスト時にリソース名として渡すことになっている点である．
このリソース名には，プロジェクト以外にも色々なリソースを指定できるようだ．

[loggingpb.ListLogEntriesRequest](https://godoc.org/google.golang.org/genproto/googleapis/logging/v2#ListLogEntriesRequest)型のドキュメントによれば，
このリソース名に指定できるフォーマットは，

- "projects/[PROJECT_ID]"
- "organizations/[ORGANIZATION_ID]"
- "billingAccounts/[BILLING_ACCOUNT_ID]"
- "folders/[FOLDER_ID]"

という４種類になっている．

二つ目の違いは，イテレータが返すログデータが [LogEntry](https://godoc.org/google.golang.org/genproto/googleapis/logging/v2#LogEntry)型に変更され，アクセッサが追加された点である．
logadminパッケージの場合だとログエントリのペイロードは `interface{}`型で
何をするにもキャストが必要であったが，LogEntry型の場合，ペイロードがテキストであれば
[GetTextPayload](https://godoc.org/google.golang.org/genproto/googleapis/logging/v2#LogEntry.GetTextPayload)関数を，JSONオブジェクトの場合は [GetJsonPayload](https://godoc.org/google.golang.org/genproto/googleapis/logging/v2#LogEntry.GetJsonPayload)関数を使用すれば良い．

ただし，GetJsonPayload関数は[structpb.Struct](https://godoc.org/github.com/golang/protobuf/ptypes/struct#Struct)型であり，
ログエントリ本来の構造を表す構造体に落とし込むためには変換が必要になる．
変換方法は[前エントリで紹介](http://qiita.com/jkawamoto/items/8e3e6c3e2a094a4ae214#%E5%9E%8B%E5%A4%89%E6%8F%9B)した [structpbconv](https://github.com/jkawamoto/structpbconv) が利用できる． 
例えば，イベントタイプが GCE_OPERATION_DONE のログだけを取得する場合，次のようになる．

```go
import (
	"context"

	"cloud.google.com/go/logging/apiv2"
	"google.golang.org/api/iterator"
	loggingpb "google.golang.org/genproto/googleapis/logging/v2"
   	"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) (res *ActivityPayload) {
    res = new(ActivityPayload)
    structpbconv.Convert(payload, res)
    return 
}

func GetLogEntries(ctx context.Context) error{

	client, err := logging.NewClient(ctx)
	if err != nil {
		return
	}
	defer client.Close()

	iter := client.ListLogEntries(ctx, &loggingpb.ListLogEntriesRequest{
		ResourceNames: []string{
			fmt.Sprintf("projects/%v", "your-project-id"),
		},
		Filter: `jsonPayload.event_type = "GCE_OPERATION_DONE"`,
	})

	for {

		e, err := iter.Next()
		if err == iterator.Done {
			return nil
		} else if err != nil {
			return err
		}

		payload := NewActivityPayload(e.GetJsonPayload())
		fmt.Println(payload.TraceID) // trace_id を出力

	}
	
}
```