AWS RDSのログファイルをダウンロードするPythonとRubyのスクリプト

以前、AWS RDS PostgreSQLのクエリログをawscliで取得するワンライナーという記事で、RDSのインスタンスにあるログファイルを一括で取得する方法を書きました。しかしこのワンライナーでは、1MBを超えるログですと先頭の1MBしか取得できず、途切れてしまいました。

そこで、PythonRubyでdownload_db_log_file_portionというAPIを使ってログをダウンロードするスクリプトを書きました。

行数を指定することで、その行数分の取得を繰り返し全部ダウンロードすることができます。しかし、行数指定が難しいです。

行数を大きくしすぎて一度に取得する大きさが1MBを超えてしまうと、そこで途切れてしまって、後続のログが取得できません。

逆に行数を小さくしすぎると、ダウンロードの回数が増えてしまい、トータルのログサイズが大きい場合にとてつもなく時間がかかってしまう問題があります。

この方法は他にも問題がありました。ログに日本語の含まれる場合に ? に置き換えられてしまいます。文字化けの問題は以前の記事のワンライナーでも同じでした。

解決策は次の改良版の記事に書きましたが、ここまで試行した過程として、PythonRubyでのスクリプトを残しておきます。

改良版:AWS RDSのログファイルを高速にダウンロードするスクリプト

Python

import sys
import boto3

profile     = "default"
instance_id = "foo"

session = boto3.session.Session(profile_name = profile)
rds_client = session.client('rds')

files = rds_client.describe_db_log_files(DBInstanceIdentifier = instance_id)

for file in files["DescribeDBLogFiles"]:
    file_name = file["LogFileName"]
    if not file_name.startswith("error/"):
        continue

    sys.stderr.write("{}\n".format(file_name))

    number_of_lines = 1000
    # 大きすぎると一度に取得するサイズが1MBを超えてしまい、
    # ページネーションが途切れてしまう

    marker = "0"
    while(marker):
        sys.stderr.write("{} {}\n".format(file_name, marker))
        log = rds_client.download_db_log_file_portion(
            DBInstanceIdentifier = instance_id,
            LogFileName          = file_name,
            Marker               = marker,
            NumberOfLines        = number_of_lines
        )

        sys.stdout.write(log["LogFileData"])
        if log["AdditionalDataPending"]:
            marker = log["Marker"]
        else:
            marker = False
    break

バージョン情報

$ pip list | grep boto
boto3               1.14.63
botocore            1.17.63

ダウンロード結果を標準出力しますので、以下のように実行します。

$ python download-rds-log.py > log.txt

Ruby

RubyAWS SDKは初めて触りましたが、ページネーションがenumerableになっており、Rubyらしく使いやすいです。

Gemは以下をインストールしておきます。

$ gem install aws-sdk-core aws-sdk-rds

Rubyソースコードです。

require 'aws-sdk-core'
require 'aws-sdk-rds'
require 'yaml'
require 'pp'

profile     = "default"
instance_id = "foo"

Aws.config[:credentials] = Aws::SharedCredentials.new(profile_name: profile)
rds_client = Aws::RDS::Client.new()

rds_client.describe_db_log_files(db_instance_identifier: instance_id).each do |files|
  files.describe_db_log_files.each do |file|
    file_name = file.log_file_name
    next if file_name !~ /\Aerror/

    STDERR.puts("%s %d" % [file_name, file.size])

    number_of_lines = 1000
    # 大きすぎると一度に取得するサイズが1MBを超えてしまい、
    # ページネーションが途切れてしまう

    i = 0
    rds_client.download_db_log_file_portion(
      db_instance_identifier: instance_id,
      log_file_name:          file_name,
      marker:                 "0",
      number_of_lines:        number_of_lines).each_page do |data|
      STDERR.puts("%s %s" % [file_name, i])
      STDOUT.print(data.log_file_data)
      i += 1
    end
  end
  break
end

バージョン情報

$ gem list | grep aws
aws-eventstream (1.1.0)
aws-partitions (1.385.0)
aws-sdk-core (3.109.1)
aws-sdk-rds (1.104.0)
aws-sigv4 (1.2.2)

ダウンロード結果を標準出力しますので、以下のように実行します。

$ ruby download-rds-log.rb > log.txt