CloudWatch Logs Insightsのクエリを、マネジメントコンソールからではなく、AWS CLI及びPython SDK(boto3)から投げてみる。

(この記事は2021年10月に他のブログで書いた記事です)

 

結論を先に言ってしまうと、CLIで取得可能な出力は限定的な様子。このためバリエーションとなるクエリを投げるにはSDKから行う必要がありそうだ。

 

Logs InsightsのクエリをCLIで実行する際は、最初にクエリIDを取得し、そのIDを指定して結果を取得する2段階となる。

参考
AWS CLI - logs
Amazon CloudWatch Logs InsightsをCLIから使ってみた #reinvent

 

  1. クエリID取得。ロググループとスキャン対象とする時間範囲をUNIXタイムスタンプで指定する。以下は単純に指定のロググループ・時刻範囲から10レコードを降順で取得するだけのクエリ。
$ aws logs start-query \
--log-group-name '/ec2/var/log/messages' \
--start-time 1629558000 \
--end-time 1629644399 \
--query-string 'fields @timestamp, @message
| sort @timestamp desc
| limit 10'

 

以下のようなクエリIDが返される。後続のコマンドでこのIDを指定する。

{
    "queryId": "8e1d5b49-b83b-4b84-a32f-4aadf8ce3d11"
}

 

  1. クエリ結果を取得する。
$ aws logs get-query-results \
--query-id '8e1d5b49-b83b-4b84-a32f-4aadf8ce3d11'

 

すると実行結果として、JSON形式でメッセージやタイムスタンプ、メタデータが出力される。ちなみにここで出力される項目「@ptr」はログイベントのメタデータとなるもの。get-log-record実行時はこの値を指定する。

 

ここまでは簡単にできた。が、冒頭に書いたように、クエリを若干アレンジすると期待値にならないのだ。以下はメッセージに「“count_number_is"を含む」というフィルタをかけた例。(面倒だからIDを変数に格納)

 

$ query=`aws logs start-query \
--log-group-name '/ec2/var/log/messages' \
--start-time 1629558000 \
--end-time 1629644399 \
--output text \
--query-string 'fileds @timestamp, @message
| filter @message like /count_number_is/
| sort @timestamp desc'

 

$ aws logs get-query-results --query-id $query

 

出力結果はこうなる。同じクエリをマネジメントコンソールから投げれば値が得られるのに、CLIでは結果が空になってしまう。ちなみに先のフィルタなしクエリでは"recordsMatched”: 221.0,だった。今度は0。recordsScanned,bytesScannedの値は同じ。

{
    "results": [],
    "statistics": {
        "recordsMatched": 0.0,
        "recordsScanned": 1404.0,
        "bytesScanned": 167578.0
    },
    "status": "Complete"
}

 

ドキュメントを見ても制約らしきことは書かれていないが、他で検索しても適当な情報が見つからないのでPython SDKで試してみた。rubyでカスタムクエリを実行している例があったからきっとできるだろう、と。

 

以下のクエリは最初のCLIでの成功例とまったく同じものである。“response"の出力結果は当然期待値となる。(インタラクティブモードで実行)

import boto3
from datetime import datetime, timedelta
import time

client = boto3.client('logs')

query = "fields @timestamp, @message | sort @timestamp desc | limit 10"
log_group = '/ec2/var/log/messages'

start_query_response = client.start_query(
    logGroupName=log_group,
    startTime=1629558000,
    endTime=1629644399,
    queryString=query,
)

query_id = start_query_response['queryId']

response = None

while response == None or response['status'] == 'Running':
    print('Waiting for query to complete ...')
    time.sleep(1)
    response = client.get_query_results(
        queryId=query_id
    )

print(response)

 

次にフィルタを追加してみる。queryを以下のように変更。

query = "fields @timestamp, @message | sort @timestamp desc | filter @message like /count_number_is/"

 

すると、クエリのresponseは期待値になった!以下はサマリの出力だが、92レコードマッチという値がとれている。マネジメントコンソールでも同じクエリを投げてみると、同じく92レコードだった。この値だけ抜き出したい場合は別処理から抽出すればいい。

'statistics': {'recordsMatched': 92.0, 'recordsScanned': 1404.0, 'bytesScanned': 167578.0}

 

というわけでSDKなら自由にクエリを構成できた。CLIでクエリのバリエーションが効かないのは謎のままであるが、こういうのを運用する場合はどうせLambdaでやるわけだから今今無理に追求しないでおく。

 

参考
API reference(get_query_results)

Pythonコードは以下参考
How to query cloudwatch logs using boto3 in python

Go SDKの例
Goを使ってCloudWatch Logs Insightsでクエリを実行する

Ruby SDKの例
CloudWatch Logs Insightsで始める簡単!!ログ集計

 


関連がありそうな記事