はじめに
CloudWatchのログの内容が膨大なときはCloudWatch Logs Insightsを使って必要なログのみを取得し分析していたのですが、AWS SDK for Rubyを使ってCSV形式で出力する機会があったのでその方法についてまとめてみました。
CloudWatch Logs Insightsをあまり使ったことがない方、CloudWatchのログデータをCSV形式で取得する方法について興味がある方のお役に立つかと思います!
CloudWatch Logs Insightsとは?
独自のクエリ構文を使いCloudWatch Logsのログデータをインタラクティブに検索、分析できるAWSが提供するサービスの一つです。
CloudWatch Logsとは?
CloudWatchの機能の一つで、AWSのリソースやアプリケーションから生成されるログデータを収集、監視、保存する
CloudWatchのサイドバーから「ログのインサイト」に進むと操作画面に移ります。
ざっくりですが操作手順は以下の通りです。
- 検索、分析を行うロググループを選択
- クエリ構文を作成し実行
- 実行したクエリの内容に応じて取得したクエリが表示される
基本的な操作方法やクエリ構文については本記事では割愛するので、下記の記事を参考にしてみてください。
AWS SDK for Rubyとは
Ruby言語を使用してAWSのリソースやサービスをプログラム的に操作するためのツールキットです。
AWS SDK for Ruby のバージョン3からは サービス固有のgem (例: aws-sdk-s3、aws-sdk-dynamodb など) にモジュール化されたので、必要なSDKのみを個別のgemで選択できるようになりました。
事前準備
Ruby on Railsを使ってCloudWatch Logs Insightsの分析内容をCSV形式で取得するスクリプトを作りますが、その前に必要な準備します。
開発環境: Mac M2
認証情報の設定
AWS SDKを使ってAWSサービスに接続するためには認証情報をあらかじめ指定しておく必要があります。
- AWS IAMコンソールからIAMユーザを作成し、アクセスキーIDとシークレットアクセスキーを取得
- ローカル環境で
~/.aws/credentials
を作成し、取得した認証情報を設定したのちに保存
[default]
aws_access_key_id='取得したアクセスキーID'
aws_secret_access_key='シークレットアクセスキー'
aws_session_token='セッショントークン(一時的なアクセス権限をとする場合に必要)'
認証情報の設定について:
SDKのインストール
cloudwatch用のgemをGmefileに追加し、bundle installを実行
gem 'aws-sdk-cloudwatchlogs'
RailsコンソールでCloudWatchに接続しログを取得する
Railsコンソールを使って実際にログを取得してみることで、どうすればログを取得できるかを見てみます。
CloudWatchへの接続
はじめに先述で設定した認証情報を使ってCloudWatchに接続してみましょう。
client = Aws::CloudWatchLogs::Client.new(region: 'ap-northeast-1')
Aws::CloudWatchLogs::Client.new
はCloudWatch Logsの操作をサポートするクライアントです。呼び出される際に、~/.aws/credentials
から認証情報を自動で読み込みます。region
でAWSリージョンを指定する必要があります。
query_idを取得
接続がうまくいったら、StartQueryを使用してCloudWatch Logs Insightsのクエリを指定し、query_idを取得できるか確認しましょう。
query_response = client.start_query({
log_group_name: '/log_group_path',
start_time: (Time.zone.now - 3600).to_i * 1000,
end_time: Time.zone.now.to_i * 1000,
query_string: 'fields @message | limit 20'
})
start_query
は、CloudWatch Logsのログデータに対して指定したクエリを実行し、ログデータの検索を実行します。
リクエストパラメータについては以下の通りです。
-
log_group_name
クエリを実行するロググループを指定 -
start_time
クエリする時間範囲の開始時刻 -
end_time
クエリする時間範囲の終了時刻 -
query_string
使用するクエリ文字列
ここで指定したクエリが実際に実行される
start_queryについて:
実行後、query_id
が取得できれば成功です
query_response
=> <struct Aws::CloudWatchLogs::Types::StartQueryResponse query_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx">
ログを取得
取得したquery_id
を使ってログを取得します。
response = client.get_query_results(query_id: query_response[:query_id])
get_query_results
は、指定したクエリの結果を返します。
get_query_resultsについて:
返却された結果からログが確認できたら成功です。
response[:results]
=>
[
[#<struct Aws::CloudWatchLogs::Types::ResultField field="@message", value="I, [2024-01-01T13:56:10.298884 #1] INFO -- : [xxx-xxx-xxx-xxx-xxx] Started GET \"/_health\" for 10.0.10.196 at 2024-01-01 13:56:10 +0900\n">,
#<struct Aws::CloudWatchLogs::Types::ResultField field="@ptr", value="">],
[#<struct Aws::CloudWatchLogs::Types::ResultField field="@message", value="I, [2024-01-01T13:56:09.749941 #1] INFO -- : [xxx-xxx-xxx-xxx-xxx] Started GET \"/_health\" for 10.0.11.161 at 2024-01-01 13:56:09 +0900\n">,
#<struct Aws::CloudWatchLogs::Types::ResultField field="@ptr", value="">],
[#<struct Aws::CloudWatchLogs::Types::ResultField field="@message", value="I, [2024-01-01T13:56:09.576021 #1] INFO -- : [xxx-xxx-xxx-xxx-xxx] Started GET \"/_health\" for 10.0.12.74 at 2024-01-01 13:56:09 +0900\n">,
#<struct Aws::CloudWatchLogs::Types::ResultField field="@ptr", value="">],
[#<struct Aws::CloudWatchLogs::Types::ResultField field="@message", value="I, [2024-01-01T13:55:40.268645 #1] INFO -- : [xxx-xxx-xxx-xxx-xxx] Started GET \"/_health\" for 10.0.10.196 at 2024-01-01 13:55:40 +0900\n">,
#<struct Aws::CloudWatchLogs::Types::ResultField field="@ptr", value="">]
]
ログをCSV形式で出力するスクリプトを作成
記事のアクセス数順で、記事IDとアクセス数をCSV形式で出力するサービスクラスを作成します。
コード
class Aws::CloudwatchLogsService
require 'aws-sdk-cloudwatchlogs'
LOGGER = ActiveSupport::Logger.new($stdout)
def call
client = Aws::CloudWatchLogs::Client.new(region: 'ap-northeast-1')
columns = ['articlue_id', 'accessCount']
query_string = 'filter @message like /articlues\/.*\?include_comments/
| parse @message "articlues/*?" as articlue_id
| stats count(articlue_id) as accessCount by articlue_id
| sort accessCount desc
| limit 10000'
params = {
log_group_name: '/articlue',
start_time: Time.zone.now.beginning_of_day.to_i * 1000,
end_time: Time.zone.now.end_of_day.to_i * 1000,
query_string: query_string
}
query_response = client.start_query(params)
response = nil
# 再試行回数は20回まで
20.times do
sleep 1
response = client.get_query_results(query_id: query_response[:query_id])
break if response[:status] != 'Running'
end
unless response[:status] == 'Complete'
logger.warning("Fetch Data Failed. Status -> #{response[:status]}")
end
generate_csv_data(response, columns)
end
#
# CSV形式に書き出すデータを生成
#
def generate_csv_data(data, columns)
result = []
data[:results].each do |row|
record = []
columns.each do |column|
value = get_value(row, column)
record << value
end
result << record
end
result
end
def get_value(record, key)
result = record.select { |x| x[:field] == key }
result.empty? ? '' : result[0][:value]
end
end
実行結果
コンソールで実行してみると以下のような実行結果が得られます。
Aws::CloudwatchLogsService.new.call
=>
[["3820", "283"],
["2840", "243"],
["3912", "234"],
["1022", "210"],
["2192", "189"],
["1792", "172"],
["2832", "150"],
["3172", "142"],
["1047", "140"],
...
["3953", "1"],
["2947", "1"],
["2292", "1"]]
まとめ
最後のスクリプトに関しては、アプリの設計、仕様や元々のログの形式の説明を無視して載せているので分かりにくい部分があるかと思いますが、ログの取得から出力までの流れに着目してもらえれば幸いです。
CloudWatch Logsで取得したログをCSVとして出力できれば、他のデータ分析ツールに流用したり、アプリケーション上ではログの生成、取得が難しくても、別々で出力した同士をマージして一つのデータして管理できたりと、分析の幅が広がると思います。
今までCloudWatch Logs Insightsを使ったことがなかった方は、ぜひ使ってみてください!