こんにちは、河野です。
これはTECHSCORE Advent Calendar 2017 の 25日目の記事です。
最近、Go言語用のAWS SDKであるaws-sdk-goを使用して、CloudWatch Logsのログイベントをダウンロードするツールを作りました。aws-sdk-goを本格的に使ってみるのは初めてだったのですが、いくつか気になったことがあったので、まとめてみました。
awsパッケージに目を通しておく
いろいろと実装を始める前に、まずはgithub.com/aws/aws-sdk-go/aws
パッケージに目を通しておいたほうが良いです。このパッケージでは、便利な関数や型が提供されています。
http://docs.aws.amazon.com/sdk-for-go/api/aws/
必須なのが値とポインタの変換用の関数群です。
例えば、
func String(v string) *string
func StringValue(v *string) string
といった基本データ型、Slice、Mapに対応した関数が用意されています。
以下のサンプルコードを見ると、雰囲気がつかめると思います。
1 2 3 4 5 6 7 8 9 10 11 |
var strPtr *string // Without the SDK's conversion functions str := "my string" strPtr = &str // With the SDK's conversion functions strPtr = aws.String("my string") // Convert *string to string value str = aws.StringValue(strPtr) |
また、ミリ秒を扱うための関数もあります。
func MillisecondsTimeValue(v *int64) time.Time
func TimeUnixMilli(t time.Time) int64
時刻を扱う構造体のtime.Time
とUnix時間(ミリ秒)を変換するための関数になります。
構造体のフィールドでポインタ型が多用されている
aws-sdk-goでは、利用したいAPIのメソッドごとにRequest,Response用の構造体が用意されています。全てのメソッドではないと思いますが、そのようなルールがあるようです。
例えばcloudwatchlogs.FilterLogEvents
は以下のように定義されています。
func (c *CloudWatchLogs) FilterLogEvents(input *FilterLogEventsInput) (*FilterLogEventsOutput, error)
Requestの情報として*FilterLogEventsInput
が必要で、Responseとして*FilterLogEventsOutput
を受け取れるようになっています。
それぞれの構造体は以下のように定義されています(フィールドのタグは除外しています)。
1 2 3 4 5 6 7 8 9 10 |
type FilterLogEventsInput struct { EndTime *int64 FilterPattern *string Interleaved *bool Limit *int64 LogGroupName *string LogStreamNames []*string NextToken *string StartTime *int64 } |
1 2 3 4 5 |
type FilterLogEventsOutput struct { Events []*FilteredLogEvent NextToken *string SearchedLogStreams []*SearchedLogStream } |
ここで気になるのはフィールドの型です。*int64
や*string
や[]*string
というような基本データ型のポインタとして扱うフィールドばかりです。
ポインタから実体を参照する場合には、panicが起きる可能性があるため、直接デリファレンスすることは避けたほうが良いです。そのため、フィールドのデータを取り扱う際には、 aws.String
、aws.StringValue
などの変換関数が必須となります。
例えばログに出力したい場合でも、変換関数を使用したほうが良いです。
1 2 3 4 |
// eventsはFilterLogEventsOutput log.Printf("NextToken: %s", aws.StringValue(events.NextToken)) // 単純なデリファレンスでは NextTokenがnilだった場合にpanicになってしまうため、以下は安全ではない // log.Printf("NextToken: %s", *events.NextToken)) |
Setterを使用する
構造体に値を設定する場合、Setter
を使用することもできます。
Setter
は、
- 基本データ型を受け取ってポインタ型にして設定する
- 構造体自身を返す
という特徴があります。
以下のように、メソッドチェインで記述することが可能です。
1 2 3 4 |
input := new(cloudwatchlogs.FilterLogEventsInput). SetLogGroupName(name). SetFilterPattern("Error"). SetLimit(10) |
ただ、Goでは個別のフィールドのためのSetterは見かけないので違和感があります。構造体リテラルを使用して書くとすると、以下のようになってしまいますし、微妙なところですね。
1 2 3 4 5 |
input := &cloudwatchlogs.FilterLogEventsInput{ LogGroupName: &name, FilterPattern: aws.String("Error"), Limit: aws.Int64(10), } |
時刻の扱いはミリ秒単位
cloudwatchlogsのパッケージ内では、時刻の扱いはミリ秒単位で行われています。変換用の関数も提供されていますし、おそらく全体的にそうなんだと思います。
一方、標準のtimeパッケージでは、Unix時間は秒とナノ秒で扱うようになっています。time.Time と int64の変換関数もありますがあくまで秒が単位なので、そのままaws-sdk-goの上で扱おうとすると意図しない時刻になってしまいます。
必ず、
func MillisecondsTimeValue(v *int64) time.Time
func TimeUnixMilli(t time.Time) int64
を経由するようにしましょう。
先程から何度も例に出していますが、cloudwatchlogs.FilterLogEvents
で時刻を指定してログイベントを取得したい場合には、FilterLogEventsInput
のStartTime
、EndTime
を指定します。ここでも変換関数を利用します。
1 2 3 4 5 6 |
start := time.Now().Add(-24 * time.Hour) // 1日前 input := new(cloudwatchlogs.FilterLogEventsInput). SetLogGroupName(name). SetFilterPattern("Error"). SetLimit(10). SetStartTime(aws.TimeUnixMilli(start)) // start.Unix() だと意図しない時刻になってしまう |
取得したログイベントを扱う構造体のFilteredLogEvent
はこのようになっています。Timestamp
はミリ秒なので、こちらも変換が必要になってきます。
1 2 3 4 5 6 7 |
type FilteredLogEvent struct { EventId *string IngestionTime *int64 LogStreamName *string Message *string Timestamp *int64 } |
例えば時刻とメッセージを表示するには以下のようになります。
1 2 3 4 5 6 7 |
for _, e := range output.Events { // 以下のようにすると意図しない時刻になってしまう // t := time.Unix(aws.Int64Value(e.Timestamp)) t := aws.MillisecondsTimeValue(e.Timestamp) log.Printf("%s %s\n", t.Format(time.RFC3339), aws.StringValue(e.Message)) } |
まとめ
aws-sdk-goを使う際の注意点をまとめます。
- awsパッケージに用意されている変換関数を把握しておく
- ポインタからのデリファレンスを安全に行うため、aws.XXXXValueを使用する
- 構造体に用意されているSetterを使用すると、値からポインタの変換を気にしなくて良い
- 時刻の単位はミリ秒なので、専用の関数を使用する
私はこの辺りをわかっていなかったので、1日ほど時間をムダにした気がします。
aws-sdk-goはAWSのサービスが大きい分、ライブラリも大きいです。まだ使っていないサービスもあるので、他にもハマるポイントが有るのかもしれませんね。
気になることがあったらパッケージのリファレンスを参照するように気をつけようと思います。