[Tips] Amazon RDSのログファイルから全てのメッセージを取得する方法

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

藤本です。

今年もあと3週間です。忘年会の時期になりましたね。今年はプライベートの忘年会の予定がないことに気づきました(アハハ

概要

Amazon RDSはOS上にあるログファイルをAPIを通して閲覧することができ、ログファイルをシステムに取り込みやすい環境が整えられています。先日、RDS for MySQLのスロークエリーログをAWS LambdaでElasticsearchに取り込むという記事をエントリしました。こちらでMySQLのスロークエリログをOSにログインすることなく(RDSはそもそもできませんが)、DB接続することなく、APIによりログメッセージを取得しました。非常に便利な仕組みです。

ただ少し特徴があり、そちらを理解しないとハマることになります。

  • DBパラメータグループによる設定
    デフォルトDBパラメータグループでは各種ログはテーブルへ出力される設定となっています。APIで取得したい場合、ファイルへ出力する必要があり、DBパラメータグループのlog_outputにFILEを指定してください。もちろん出力が必要なログファイルの有効化も忘れずに。

  • 1時間毎にログファイルがローテーション
    各種ログファイルへの出力はSuffixなしのログファイルにリアルタイムで出力されます。general/mysql-general.logやslowquery/mysql-slowquery.logなど。それが1時間毎にローテーションされ、UTC時間をSuffixにしたログファイルに置き換えられます。例えば、大体、UTC時間の9時〜10時までに出力したログメッセージが10時過ぎにgeneral/mysql-general.log.10やslowquery/mysql-slowquery.log.10といった名前のログファイルに置き換えられます。

  • 最大24時間の保持期限
    各種ログファイルは最大24時間のログメッセージを保持することができます。毎時ログローテーションするタイミングで同一名前になるログファイルに上書きします。そのため24時間前のログメッセージを活用したい方は定期的に外部に出力し、保管する必要があります。

  • APIにより最大10,000行までのメッセージまでしか見れない
    本エントリのテーマです。APIによりログメッセージを取得する場合、DownloadDBLogFilePortion APIを利用します。こちらのAPIは最大10,000行までしかログメッセージを取得できません。

マネジメントコンソールでは10,000行を超えるログメッセージが、、、

RDS_·_AWS_Console

DownloadDBLogFilePortion APIでは10,000行しか取得できていない。。。

>>> import boto3
>>> client = boto3.client("rds")
>>> len(client.download_db_log_file_portion(DBInstanceIdentifier="db", LogFileName="general/mysql-general.log")["LogFileData"].strip().split("\n"))
10000

今回はこの10,000行を超えるログファイルから全てのメッセージを取得する方法をご紹介します。 方法として以下の二つがあります。

  • DownloadDBLogFilePortion APIのMarkerオプションにより分割して取得する
  • DownloadCompleteDBLogFile APIにより一括取得する

DownloadDBLogFilePortion APIのMarkerオプションにより分割して取得する

まずはDownloadDBLogFilePotion APIを利用し、10,000行単位で繰り返し取得することで全てのメッセージを取得する方法です。
DownloadDBLogFilePotion APIはMarkerというオプションを持っています。MarkerとはログファイルのUTC時間、文字数を位置として取得するためのAPIオプションです。フォーマットは"<UTC時間>:<文字数>"です。

  • UTC時間
    Suffixがない(=現在書き込まれている)ログファイルを対象とした場合のみ一つ前のローテーション済みのメッセージを取得可能です。その場合に指定するUTC時間です。それ以外はどんな数値にしても影響はありません。またログファイルの先頭からメッセージを取得したい場合、この指定は省力可能です。(Markerに"0"とだけ指定)

  • 文字数
    ログファイルの何文字目から取得するかという指定になります。シンプルですね。

ただMarkerの指定はUTC時間だったり、文字数をカウントしながら繰り返すのは面倒です。そこでDownloadDBLogFilePotion APIの結果にMarkerが埋め込まれており、今回取得したメッセージの終了位置(続きを取得する時に指定するMarker)を知ることができます。これを利用することで簡単に繰り返しの取得が可能となります。

やってみた

まずMarker指定しない場合、ログファイルの頭からではなく、直近で最大10,000行を取得します。そのため10,000行を超えるログファイルから全てのメッセージを取得したい場合、Markerの位置を"0"で指定します。

それではやってみましょう。

>>> import boto3
>>> client = boto3.client("rds")
>>> result = client.download_db_log_file_portion(DBInstanceIdentifier="db", LogFileName="general/mysql-general.log", Marker="0")
>>> len(result["LogFileData"].strip().split("\n"))
10000
>>> result["Marker"]
'5:427775'

10,000行を取得しました。結果のMarkerを利用して、続きを取得します。

>>> result1 = client.download_db_log_file_portion(DBInstanceIdentifier="db", LogFileName="general/mysql-general.log", Marker="5:427775")
>>> result["LogFileData"].strip().split("\n")[-10:]
['\t\t   13 Query\tinsert into users values ("user4270", "password")',
 '\t\t   13 Query\tcommit',
 '\t\t   13 Query\tselect count(*) from users',
 '\t\t   13 Query\tinsert into users values ("user4271", "password")',
 '\t\t   13 Query\tcommit',
 '\t\t   13 Query\tselect count(*) from users',
 '\t\t   13 Query\tinsert into users values ("user4272", "password")',
 '\t\t   13 Query\tcommit',
 '\t\t   13 Query\tselect count(*) from users']

>>> result1["LogFileData"].strip().split("\n")[:10]
['\t\t   13 Query\tinsert into users values ("user4273", "password")',
 '\t\t   13 Query\tcommit',
 '\t\t   13 Query\tselect count(*) from users',
 '\t\t   13 Query\tinsert into users values ("user4274", "password")',
 '\t\t   13 Query\tcommit',
 '\t\t   13 Query\tselect count(*) from users',
 '\t\t   13 Query\tinsert into users values ("user4275", "password")',
 '\t\t   13 Query\tcommit',
 '\t\t   13 Query\tselect count(*) from users',
 '\t\t   13 Query\tinsert into users values ("user4276", "password")']

今回は分かりやすいようにスクリプトでインクリメントしながらINSERT文を流した。続きを取得していることが分かると思います。
それでは最後まで取得しましょう。

>>> len(result1["LogFileData"].strip().split("\n"))
3842
>>> result1["Marker"]
'5:596984'
>>> result2 = client.download_db_log_file_portion(DBInstanceIdentifier="db", LogFileName="general/mysql-general.log", Marker="5:596984")
>>> result2["Marker"]
'5:596984'
>>> result2["LogFileData"]
''

最終的にはメッセージが空になり、最後までログファイルのメッセージを取得しました。 このログファイルには13,842行のメッセージがありました。

このようにMarkerオプションを利用すれば、最後までログメッセージを取得することが可能です。

DownloadCompleteDBLogFile APIにより一括取得する

DownloadCompleteDBLogFile APIは前回のエントリへのフィードバックで初めて知りました。自分のアウトプットへのフィードバックを受けることで知らなかったことを更に知ることができたり、自分の勘違いを正せたりできることはアウトプットすることのよいことですね!みなさんもどんどんアウトプットして、自分を高めていきましょう。

話を戻しまして、、、
今回のような課題のためにAWSはDownloadCompleteDBLogFileというAPIを用意してくれています。じゃあ、最初からそれ利用すればいいじゃん、と思われると思いますが、理由があります。それはbotocoreに実装されていないということです。そのため、現行バージョンのawscliでも使えませんし、boto3でも使えません。他のSDKはどうだろう。

やってみた

botocoreの署名リクエスト処理を活用しながら取得します。

>>> from botocore.awsrequest import AWSRequest
>>> from botocore.auth import SigV4Auth
>>> from botocore.endpoint import PreserveAuthSession
>>> from botocore.credentials import Credentials
>>> aws_access_key_id = "*******************"
>>> aws_secret_access_key = "************************"

>>> credentials = Credentials(aws_access_key_id, aws_secret_access_key)
>>> request = AWSRequest(method="GET", url="https://rds.ap-northeast-1.amazonaws.com/v13/downloadCompleteLogFile/db/general/mysql-general.log")
>>> SigV4Auth(credentials, "rds", "ap-northeast-1").add_auth(request)
>>> response = PreserveAuthSession().send(request.prepare())
>>> len(response.content.split("\n"))
13859

少し時間差があったので増えていますが、13,859行のメッセージ全てを取得できました。 実装に細かい制御がいらないのは嬉しいですね。

まとめ

いかがでしたでしょうか。
準リアルタイムでRDSのログファイルをシステムに取り込む場合、これを理解しないとハマります。
AWSを理解し、AWSをフルに活用し、楽できることは楽しましょう。