AWS CLI で CloudWatch Logs にログを送信する
2019-09-03CloudWatch Logs には AWS の各サービスからあらゆるログが送信されるようになっていますが、SDK や CLI を使えば外部のアプリケーションからもログを送信することができます。今回は AWS CLI を使ってログを送信してみました。
CloudWatch Logs とは
Amazon CloudWatch Logs を使用して、Amazon Elastic Compute Cloud (Amazon EC2) インスタンス、AWS CloudTrail、Route 53、およびその他のソースのログファイルの監視、保存、アクセスができます。
AWS のリソースに限らず、アプリケーションのログも溜めておくことができます。サーバレスアーキテクチャだったりスケーリングを意識したアーキテクチャだと、ログの置き場所に困りますが、そういう構成ではとりあえず CloudWatch Logs に投げるみたいな構成になっていると思います。
CloudWatch Logs の構成要素
CloudWatch Logs では ロググループ と ログストリーム という要素があるので、それぞれについてざっと確認します。
ロググループ
ロググループは、保持、監視、アクセス制御について同じ設定を共有するログストリームのグループです。ロググループを定義して、各グループに入れるストリームを指定することができます。1 つのロググループに属することができるログストリームの数に制限はありません。
つまり、アプリケーション単位や画面単位といったくくりでログをグループ分けしている要素です。例えば Lambda の実行ログに関しては、関数単位でロググループが作成されています。
ログストリーム
ログストリームは、同じソースを共有する一連のログイベントです。CloudWatch Logs へのログの各ソースによって、個別のログストリームが構成されます。
ロググループ内でさらにログを分割している要素がログストリームです。同じアプリケーションであっても、別のバージョンや別日付などでストリームを分けることになると思います。Lambda の実行ログを例にすると、関数のバージョン単位、一定時間単位でストリームが分割されています。
ロググループ内には数の制限なくログストリームを作成することができます。一つだけでもいいです。ただし、最低でも日付ごととかで分割してあると、あとでログを閲覧するときに見つけやすいです。
また、ストリームを分けたとしても、マネジメントコンソール上ではストリームを横断したログの検索が可能なので、心配ありません。
ログイベント
いわゆるログのことです。あとで書きますが、 AWS CLI では put-log-events
というサブコマンドを使ってログを送信します。
AWS CLI での操作
では、実際に AWS CLI で CloudWatch Logs を操作してみます。
CloudWatch Logs のコマンドは aws logs
です。これに続けてサブコマンドを足していく感じです。
コマンドの詳細については公式のドキュメントを参照してください。
ログの送信 (準備)
ログの送信には put-log-events
サブコマンドを使うと書きましたが、実行時には下記のオプションが必要になります。
--log-group-name
: 対象のロググループ名--log-stream-name
: 対象のログストリーム名--log-events
: 送信するログ
つまり、ログの送信するにはロググループとログストリームが既に存在している必要があります。なので、今回はそれらも AWS CLI で作成してみます。
ロググループ、ログストリームの作成
まずは ロググループです。
$ aws logs create-log-group --log-group-name michimani-test-group
出力は特にありません。本当に作成されたのか確認してみます。
$ aws logs describe-log-groups --log-group-name-prefix michimani
{
"logGroups": [
{
"logGroupName": "michimani-test-group",
"creationTime": 1567477587999,
"metricFilterCount": 0,
"arn": "arn:aws:logs:ap-northeast-1:XXXXXXXXXXXX:log-group:michimani-test-group:*",
"storedBytes": 0
}
]
}
もちろんですが、既にグループが存在している場合はエラーが出力されます。
$ aws logs create-log-group --log-group-name michimani-test-group
An error occurred (ResourceAlreadyExistsException) when calling the CreateLogGroup operation: The specified log group already exists
続いてログストリームを作成します。
$ aws logs create-log-stream \
--log-group-name michimani-test-group \
--log-stream-name test-stream-001
これも出力は特に何もないので、作成されているのか確認してみます。
$ aws logs describe-log-streams \
--log-group-name michimani-test-group \
--log-stream-name-prefix test-stream-001
{
"logStreams": [
{
"logStreamName": "test-stream-001",
"creationTime": 1567478248995,
"arn": "arn:aws:logs:ap-northeast-1:XXXXXXXXXXXX:log-group:michimani-test-group:log-stream:test-stream-001",
"storedBytes": 0
}
]
}
これでログを入れる箱ができたので、ログを送信していきます。
ログの送信
ログの送信 (準備) で書いた通り、ログの送信にはロググループとログストリーム、そしてログのイベントデータを指定する必要があります。
イベントデータは --log-events
オプションで指定しますが、方法が Shorthand Syntax と JSON Syntax の 2 通りあるのでそれぞれ見ていきます。
例として、Hello CloudWatch Logs というログを送信するとします。
Shorthand Syntax
ワンラインでタイムスタンプとメッセージを指定する方法です。
$ aws logs put-log-events \
--log-group-name michimani-test-group \
--log-stream-name test-stream-001 \
--log-events timestamp=1567478448995,message="Hello CloudWatch Logs"
JSON Syntax
JSON 形式で指定する方法です。この方法だと、複数のログイベントを一度に送信することができます。
[
{
"timestamp": 1567478448995,
"message" : "Hello CloudWatch Logs"
}
]
この方法の場合は、 JSON をファイルとして保存しておいて、そのファイルを --log-events
で指定する形になります。
$ aws logs put-log-events \
--log-group-name michimani-test-group \
--log-stream-name test-stream-001 \
--log-events file://events.json
どちらの方式でも timestamp
と message
が必要になります。また、 timestamp
は 13 桁 である必要があります。
では実際に JSON Syntax の方式でログを送信してみます。
送信するイベントデータとして、下記のような JSON を準備します。
[
{
"timestamp": 1567478448995,
"message" : "Good morning CloudWatch Logs"
},
{
"timestamp": 1567478548995,
"message" : "Good afternoon CloudWatch Logs"
},
{
"timestamp": 1567478648995,
"message" : "God night CloudWatch Logs"
}
]
aws logs put-log-events \
--log-group-name michimani-test-group \
--log-stream-name test-stream-001 \
--log-events file://events.json
{
"nextSequenceToken": "49596402802751870934231393163316499531521111741320675602"
}
結果として、 nextSequenceToken
という値が出力されました。この値は、 引き続き同じログストリームにログを送信する際に必要 になります。仮に同じコマンドをもう一度実行してみると
$ aws logs put-log-events \
--log-group-name michimani-test-group \
--log-stream-name test-stream-001 \
--log-events file://events.json
An error occurred (DataAlreadyAcceptedException) when calling the PutLogEvents operation: The given batch of log events has already been accepted. The next batch can be sent with sequenceToken: 49596402802751870934231393163316499531521111741320675602
エラーとともに、先ほどと同じ sequenceToken
の値が記されています。ということで、 sequenceToken
を指定して送信してみます。
aws logs put-log-events \
--log-group-name michimani-test-group \
--log-stream-name test-stream-001 \
--log-events file://events.json \
--sequence-token 49596402802751870934231393163316499531521111741320675602
{
"nextSequenceToken": "49596402802751870934231393650155762793610763307610816786"
}
別の sequenceToken
が返ってきました。つまり、同一のログストリームにログイベントを送信する場合は、 2 回目以降はその一つ前に送信した際に返ってきた sequenceToken
を --sequence-token
オプションで指定する必要があります。
ログの確認
では、上で送信したログを確認してみます。
$ aws logs get-log-events \
--log-group-name michimani-test-group \
--log-stream-name test-stream-001 \
{
"events": [
{
"timestamp": 1567478448995,
"message": "Good morning CloudWatch Logs",
"ingestionTime": 1567483678013
},
{
"timestamp": 1567478448995,
"message": "Good morning CloudWatch Logs",
"ingestionTime": 1567484033671
},
{
"timestamp": 1567478548995,
"message": "Good afternoon CloudWatch Logs",
"ingestionTime": 1567483678013
},
{
"timestamp": 1567478548995,
"message": "Good afternoon CloudWatch Logs",
"ingestionTime": 1567484033671
},
{
"timestamp": 1567478648995,
"message": "God night CloudWatch Logs",
"ingestionTime": 1567483678013
},
{
"timestamp": 1567478648995,
"message": "God night CloudWatch Logs",
"ingestionTime": 1567484033671
}
],
"nextForwardToken": "f/34955941955374514222924862267686763373673893786318209026",
"nextBackwardToken": "b/34955937495225474516800233530579142916825806445936443392"
}
同じ JSON を 2 回送信したので、それぞれ 2 つずつ同じログイベントが取得できました。
一緒に帰ってきている nextForwardToken
と nextBackwardToken
は何でしょうか。
AWS CLI で取得できるログイベントは、データサイズは 1 MB または イベント数が 10,000 件 が上限となっています。なので、それ以降 (Forward) もしくは それ以前 (Backward) のイベントを取得する際には --next-token
で値を指定します。
また、取得するログイベント数の上限は --limit
オプションでも指定できます。 --limit
と --next-token
の挙動を試してみます。
$ aws logs get-log-events \
--log-group-name michimani-test-group \
--log-stream-name test-stream-001 \
--limit 2
{
"events": [
{
"timestamp": 1567478648995,
"message": "God night CloudWatch Logs",
"ingestionTime": 1567483678013
},
{
"timestamp": 1567478648995,
"message": "God night CloudWatch Logs",
"ingestionTime": 1567484033671
}
],
"nextForwardToken": "f/34955941955374514222924862267686763373673893786318209026",
"nextBackwardToken": "b/34955941955374514222924861837722797446498107642019643394"
}
直近の 2 件が取得できました。それ以前の 2 件を取得するために --next-token
に nextBackwardToken
の値を指定してみます。
$ aws logs get-log-events \
--log-group-name michimani-test-group \
--log-stream-name test-stream-001 \
--limit 2 \
--next-token b/34955941955374514222924861837722797446498107642019643394
{
"events": [
{
"timestamp": 1567478548995,
"message": "Good afternoon CloudWatch Logs",
"ingestionTime": 1567483678013
},
{
"timestamp": 1567478548995,
"message": "Good afternoon CloudWatch Logs",
"ingestionTime": 1567484033671
}
],
"nextForwardToken": "f/34955939725299994369862548114114936108837743188276609025",
"nextBackwardToken": "b/34955939725299994369862547684150970181661957043978043393"
}
次の 2 件が取得できました。
こんな感じで、前後のイベントを取得できます。
ログの検索
filter-log-events
サブコマンドで、任意の文字列でログイベントを検索することができます。
filter-log-events
で必須のオプションは --log-group-name
のみで、 --log-stream-name
は任意です。つまり、冒頭にも書きましたがログストリームを横断してロググループ全体で検索することが可能です。これは AWS CLI だけでなく、マネジメントコンソール上でも可能です。
では実際に試してみます。
$ aws logs filter-log-events \
--log-group-name michimani-test-group \
--filter-pattern "Good morning"
{
"events": [
{
"logStreamName": "test-stream-001",
"timestamp": 1567478448995,
"message": "Good morning CloudWatch Logs",
"ingestionTime": 1567483678013,
"eventId": "34955937495225474516800233530579142916825806445936443392"
},
{
"logStreamName": "test-stream-001",
"timestamp": 1567478448995,
"message": "Good morning CloudWatch Logs",
"ingestionTime": 1567484033671,
"eventId": "34955937495225474516800233960543108844001592590235009024"
}
],
"searchedLogStreams": [
{
"logStreamName": "test-stream-001",
"searchedCompletely": true
}
]
}
ログイベントの形式
上の例で送ったログを、マネジメントコンソールで確認してみます。
まあ、普通です。
ログの内容がシンプルなのでこれでもいいかもしれませんが、実際のログにはいろんな情報が含まれています。なので、それらの情報をマネジメントコンソール上で見やすくなるような形でログを送信してみます。
方法としては、ログイベントの message
を JSON 文字列にする です。具体的には下記のような JSON を準備して、送信します。
[
{
"timestamp": 1567478748995,
"message": "{\"name\":\"Ken\",\"message\":\"Good morning\"}"
},
{
"timestamp": 1567478848995,
"message": "{\"name\":\"Kumi\",\"message\":\"Good afternoon\"}"
}
]
マネジメントコンソールで確認してみると、下のキャプチャのようにログイベントを展開したときに JSON が整形されて表示されます。
この JSON 形式にしておくと視認性が良くなるというメリットもありますが、ログの検索時に JSON のキーと値を指定して検索することもできます。例えば name
が Ken
のログであれば、 {$.name = "Ken"}
というフィルタで検索できます。
もちろんこれは AWS CLI で操作するときにも使えます。
$ aws logs filter-log-events \
--log-group-name michimani-test-group \
--filter-pattern '{$.name = "Ken"}'
{
"events": [
{
"logStreamName": "test-stream-001",
"timestamp": 1567478748995,
"message": "{\"name\":\"Ken\",\"message\":\"Good morning\"}",
"ingestionTime": 1567486685224,
"eventId": "34955944185449034075987179626789594095612148516425498624"
}
],
"searchedLogStreams": [
{
"logStreamName": "test-stream-001",
"searchedCompletely": true
}
]
}
ということなので、送信した後のログイベントのその後の扱い方を考えると、 JSON 形式で送信しておいた方が便利なような気がします。
まとめ
AWS CLI での CloudWatch Logs の操作してみた話でした。
実際にアプリケーションからログを送信する際には、適度な粒度でログストリームを作成したりする必要があります。今回は CLI で試しましたが、各言語の SDK で実装する場合にも、基本的にはこんな感じの操作になるかと思います。
やっぱり CLI で操作する楽しさって何か特別なものがありますよね。
comments powered by Disqus