以下の内容はhttps://techblog.techfirm.co.jp/entry/querying-log-using-aws-athenaより取得しました。


S3に出力したログをAmazon Athenaでクエリする

はじめに

ウェブアプリケーションの運用で、ログを解析するとき、
解析専用サーバを構築したり、有料の解析ソフトを使うケースが一般的だと思うが、
生ログをエクセルなどで解析するケースも稀ではないように思う。
たとえば、ALBのアクセスログはS3に保存することができるが、
ファイルは5分毎に分割され、gz形式で圧縮されているため、ログの中身を見るには多少手間がかかる。
Amazon Athenaでは、S3のログを、分割され圧縮された状態でも、
とくにそういったファイルの状態を意識することなくデータの抽出を行うことができる。
そこで本稿では、S3に出力されたログに対し、Amazon Athenaを使ってクエリを実行する方法を紹介する。

Amazon Athenaとは

S3内のデータを、SQLクエリを使って直接抽出できる。
サーバレスにクエリを実行でき、Apache Sparkと併用することも可能。
大規模データでも高速な処理が可能で、料金はクエリ実行時にスキャンしたデータ量に対してのみ発生。
東京リージョンでは、1TBあたり5.00ドルが課金される。

Amazon Athenaを利用するには

AWSマネジメントコンソールで、Amazon Athenaを開く。
クエリエディタを開き、まずはデータベースおよびテーブルの作成を行う。

クエリ結果の保存場所を作成

S3に、クエリの保存先用の場所を作成する。
クエリエディタの、[設定] 、 [管理]から設定する。

データベースの作成

Amazon Athenaでは、データベースを定義することができる。
データベースを分けておくと、それぞれのデータベースに同じ名前のテーブルを作成できるため、
クエリを使いまわすときなどは便利だと思うが、作成しなくても特に問題はない。
データベースを作成する場合は、クエリエディタから下記の様なCREATE文を実行する。

CREATE (DATABASE|SCHEMA) [IF NOT EXISTS] database_name

テーブルの作成

クエリエディタから次のようなDDLステートメントを実行する。
下記は、パーティション射影(Partition Projection)を使用したテーブル作成の例文。
パーティション射影によってパーティション管理を自動化することができる。

CREATE EXTERNAL TABLE IF NOT EXISTS alb_logs (
            type string,
            time string,
            elb string,
            client_ip string,
            client_port int,
            target_ip string,
            target_port int,
            request_processing_time double,
            target_processing_time double,
            response_processing_time double,
            elb_status_code int,
            target_status_code string,
            received_bytes bigint,
            sent_bytes bigint,
            request_verb string,
            request_url string,
            request_proto string,
            user_agent string,
            ssl_cipher string,
            ssl_protocol string,
            target_group_arn string,
            trace_id string,
            domain_name string,
            chosen_cert_arn string,
            matched_rule_priority string,
            request_creation_time string,
            actions_executed string,
            redirect_url string,
            lambda_error_reason string,
            target_port_list string,
            target_status_code_list string,
            classification string,
            classification_reason string
            )
            PARTITIONED BY
            (
             day STRING
            )
            ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
            WITH SERDEPROPERTIES (
            'serialization.format' = '1',
            'input.regex' = 
        '([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*):([0-9]*) ([^ ]*)[:-]([0-9]*) ([-.0-9]*) ([-.0-9]*) ([-.0-9]*) (|[-0-9]*) (-|[-0-9]*) ([-0-9]*) ([-0-9]*) \"([^ ]*) (.*) (- |[^ ]*)\" \"([^\"]*)\" ([A-Z0-9-_]+) ([A-Za-z0-9.-]*) ([^ ]*) \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\" ([-.0-9]*) ([^ ]*) \"([^\"]*)\" \"([^\"]*)\" \"([^ ]*)\" \"([^\s]+?)\" \"([^\s]+)\" \"([^ ]*)\" \"([^ ]*)\"')
            LOCATION 's3://your-alb-logs-directory/AWSLogs/<ACCOUNT-ID>/elasticloadbalancing/<REGION>/'
            TBLPROPERTIES
            (
             "projection.enabled" = "true",
             "projection.day.type" = "date",
             "projection.day.range" = "2022/01/01,NOW",
             "projection.day.format" = "yyyy/MM/dd",
             "projection.day.interval" = "1",
             "projection.day.interval.unit" = "DAYS",
             "storage.location.template" = "s3://your-alb-logs-directory/AWSLogs/<ACCOUNT-ID>/elasticloadbalancing/<REGION>/${day}"
            )

パーティション射影を使用するには、テーブルプロパティでパーティションキーのルールやフォーマットを指定する。
上の例では、projection.day.rangeで対象とするデータの期間を2022年1月1日から現在まで指定し、
strage.location.templateで、S3のログファイルの場所を指定している。
また、データベースを指定したい場合は、クエリエディタ付近にあるプルダウンから
任意のデータベースを指定するか、下記の様にデータベース名を指定してもよい。

CREATE EXTERNAL TABLE IF NOT EXISTS database_name.alb_logs

テーブル作成のDDLステートメントに関する詳細はAWS公式ドキュメントから確認できる。
パーティション射影を使用した Athena での ALB ログ用のテーブルの作成

パーティションを使用するメリット

パーティション化することで、下記2点に対してメリットを得られる。

  • 検索速度
  • コスト
検索速度

パーティションを指定しない場合、クエリを実行する際はすべてのデータが検索対象となる。
対象データが多いと、検索結果を得られるまで時間がかかる場合がある。

コスト

前項で述べた通りテーブルをパーティション化すると、クエリ実行時のスキャン対象データを限定できるようになる。
Amazon Athenaの料金はスキャン対象のデータ量に対して課金されるため、
スキャン対象のデータが少なければ当然請求金額も少なくなり、コスト面でのメリットになる。

クエリを実行してみる

それではここでいくつかのクエリを紹介してみたいと思う。

日時の範囲指定

まずは、WHERE句で、抽出したいログの時間帯を範囲指定するクエリを紹介する。

日付型への変換とタイムゾーンの変更

LBのログに出力される日時は、タイムゾーンがUTCで、文字列型であるため、
WHERE句で時間や日時を指定するには、parse_datetimeを使用しdate型に変換する。
また、WHERE句で日時指定する際に日本時間を使用したい場合は、タイムゾーンにAsia/Tokyoを指定する。
下記は、日本時間2023年1月1日から1月10日までを指定するクエリ。

SELECT
 parse_datetime(time,'yyyy-MM-dd''T''HH:mm:ss.SSSSSS''Z') AT TIME ZONE 'Asia/Tokyo' as time,
 request_verb,
 request_url,
 client_ip,
 elb_status_code,
 target_status_code,
 target_processing_time,
 response_processing_time,
 user_agent
FROM alb_logs
WHERE parse_datetime(time,'yyyy-MM-dd''T''HH:mm:ss.SSSSSS''Z') AT TIME ZONE 'Asia/Tokyo'
 BETWEEN timestamp '2023-01-01 00:00 Asia/Tokyo' AND timestamp '2023-01-10 23:59 Asia/Tokyo'
ORDER BY time;

1時間ごとのアクセス数を集計

次に紹介するのは時間毎のアクセス数を集計するクエリ。

substrでtimeのYYYY-MM-DDTHH部分を抽出

substr関数を使用し、アクセス日時の先頭から13文字を抽出する。
同じ時刻のデータをcountし、1時間当たりのアクセス数を集計する。
下記例では、2023年1月1日から1月11日までの、ステータスコードが504および460の、1時間ごとのアクセス数を集計する。

SELECT substr(time,1,13) AS time_every_hour, count(target_status_code)
FROM alb_logs
WHERE parse_datetime(time,'yyyy-MM-dd''T''HH:mm:ss.SSSSSS''Z') AT TIME ZONE 'Asia/Tokyo'
 BETWEEN timestamp '2023-01-01 00:00 Asia/Tokyo' AND timestamp '2023-01-11 23:59 Asia/Tokyo'
 AND (elb_status_code = 504 OR elb_status_code = 460)
group BY substr(time,1,13)
order by time_every_hour

array型のカラムをWHERE句やSELECTで指定

ALBのログでは該当しないが、WAFのログの様にarrayデータ型のカラムから部分的にデータを取得したり、
WHERE句でarrayデータ型のカラムを条件指定の対象に使用したいときは、UNNEST演算子を使用する。

配列型の要素を1行のデータに変換

例えばWAFのログのlabelsは、下記の様な内容になっている。

"labels":[{"name":"awswaf:managed:token:absent"},{"name":"awswaf:managed:aws:bot-control:bot:verified"},{"name":"awswaf:managed:aws:bot-control:bot:category:search_engine"},{"name":"awswaf:managed:aws:bot-control:bot:name:googlebot"}]

下記は、任意のWAFルールに該当したリクエストをWHERE句で指定し抽出するクエリで、
labelsをネストし、labelsのnameをWHERE句で指定できるようにしている。
事前に、テーブル名が「waf_logs」というAWS WAFログのテーブルを作成しているものとする。

SELECT from_unixtime(timestamp / 1000, 'Asia/Tokyo') as jst, action, terminatingrulematchdetails, httprequest, label.name
FROM waf_logs, UNNEST(labels) t(label)
WHERE from_unixtime(timestamp / 1000, 'Asia/Tokyo')
 BETWEEN timestamp '2023-01-01 00:00' AND timestamp '2023-01-31 23:59'
AND (label.name LIKE '%HostingProviderIPList%' OR label.name LIKE '%Custom_MicrosoftAds_NotMicrosoftAds%')
ORDER BY timestamp;

さいごに

ファイルの中身をダイレクトにクエリできるAmazon Athenaは、とても便利で使い勝手がいいサービスだと思う。
対象の複数ファイルをダウンロードしたり、解凍したりする必要がなく、
慣れ親しんだSQLでクエリできるため、S3にあるファイルでも時間をかけずに解析できる。
利用したことがない人はぜひ試してみてほしい。




以上の内容はhttps://techblog.techfirm.co.jp/entry/querying-log-using-aws-athenaより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14