瞬時に、柔軟に、そして簡単に。Amazon Athena で行うログ分析

この記事は個人ブログと同じ内容です

www.ritolab.com


ログの分析をどのような手段で実施するか

ログの多くはテキストファイルであることが多く、DB に格納されていないことがほとんどです。

しかも、ログファイルは大抵、複数の環境から吐き出されたり、量や時間で分割して出力されたりするため、複数のファイルにまたがっていることがほとんど。

そんな環境下でも、ログ分析の環境を簡単に構築し、柔軟に解析していけるのが、Amazon Athena です。

AWS Athena

AWS Athena は、Amazon S3 に保存されたデータを標準的な SQL を使って直接分析できるインタラクティブなクエリサービスです。サーバーの管理や設定は不要で、数秒でクエリを実行し、結果を取得できます。

aws.amazon.com

使いやすい上に瞬時にログ分析の環境を用意できる。

最初に結論からお伝えしておくと、ログ分析に Athena を用いるのはとてもオススメです。

特に調査や障害など、急にスポットでログ分析が発生した時などは瞬時に分析環境を構築できるため、初動が非常に早くなります。

前提とデータの用意

今回は、ログの分析を行ってみます。

以下のフォーマットを持つ、nginx ライクなログを 1000 行、擬似的に作成しました。APIアクセスログを模しています。

192.168.0.247 - - [2024-02-18 14:56:55] "POST /api/v1/tasks/24361 HTTP/1.1" 201 7101 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9876.54321 Safari/537.36"
192.168.0.142 - - [2024-02-18 14:56:56] "GET /api/v1/messages/20050 HTTP/1.1" 200 2942 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36"
.
.
.

この access.log を、 S3 のバケットに配置してあります。

データベース作成

まずは、Athena でデータベースを作成します。初期は default になっています。データベースを作成せずに default のまま使うこともできます。

エディターにデータベース作成のクエリを入力し、実行ボタンを押下します。

CREATE DATABASE log_analysis;

実行が成功すると、左メニューのデータベースのセレクトボックスから、log_analysis を選択できるようになります。

テーブル作成

作成したデータベース log_analysis にテーブルを作成していきます。

作成テーブルは、最初に用意して S3 に配置したログのテーブルです。

左メニュー「テーブルとビュー」に「作成」というセレクトボックスがあります。そこから、「S3バケットデータ」を選択します。

「S3 バケットデータからテーブルを作成」という画面に遷移します。ここで必要な入力を行い、テーブルを作成します。

テーブルの詳細セクションでは、テーブル名とその説明を入力します。データベース選択セクションでは、先程作成した log_analysis を選択します。

データセットセクションでは、ログファイルが設定されている S3 のバケットディレクトリを指定します。

データ形式セクションでは、ファイル(今回でいうとログファイル)のパースに必要な情報を指定します。

今回、ログは正規表現でパースするため、このような指定になっています。

  • テーブルタイプ
  • ファイル形式
  • SerDe ライブラリ
  • SerDe プロパティ - オプション
    • 名前: input.regex
    • 値: ^(\\S+) - - \\[([^\\[]+)\\] "(\\S+) (\\S+) (\\S+)" (\\S+) (\\S+) "(\\S+)" "(.*)"

列の詳細セクションでは、作成するテーブルのカラムを定義します。

  • ip: string,
  • time: string,
  • method: string,
  • uri: string,
  • protocol: string,
  • status: int,
  • bytes_sent: int,
  • referer: string,
  • user_agent: string

注意点は、データ形式セクションでログのパースを正規表現で記載しましたが、正規表現によってキャプチャ(抽出)する数と、ここで指定するカラムの数は同じである必要があります。

このセクション以降はオプションなので今回は設定しません。最下部にある「テーブルを作成」ボタンを押下すると、テーブルが作成されます。

-- DDL
CREATE EXTERNAL TABLE IF NOT EXISTS `mydatabase`.`nginx_logs` (
  `ip` string, 
  `time` string,
  `method` string,
  `uri` string,
  `protocol` string,
  `status` int,
  `bytes_sent` string,
  `referer` string,
  `user_agent` string
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
  'input.regex' = '^(\\S+) - - \\[([^\\[]+)\\] "(\\S+) (\\S+) (\\S+)" (\\S+) (\\S+) "(\\S+)" "(.*)"'
)
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://sample-logs/logs/'
;

クエリを実行しログデータを取得する

テーブルが作成されたので、実際にクエリを実行してログのデータを取得してみます。

SELECT 
*
FROM nginx_logs
;

取得できました。SQL を記述してログのデータが取得できるのはとても利便性が高いですね。

ip time method uri protocol status bytes_sent referer user_agent
192.168.0.247 2024-02-18 14:56:55 POST /api/v1/tasks/24361 HTTP/1.1 201 7101 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9876.54321 Safari/537.36
192.168.0.142 2024-02-18 14:56:56 GET /api/v1/messages/20050 HTTP/1.1 200 2942 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
192.168.0.17 2024-02-18 14:56:57 GET /api/v1/users/29621 HTTP/1.1 401 6058 - Mozilla/5.0 (Macintosh; Intel Mac OS X 1101) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.1234.5678 Safari/537.36
192.168.0.243 2024-02-18 14:56:58 PUT /api/v1/orders/49212 HTTP/1.1 204 6035 - Mozilla/5.0 (Macintosh; Intel Mac OS X 1152) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9876.54321 Safari/537.36
192.168.0.176 2024-02-18 14:56:59 POST /api/v1/products/15518 HTTP/1.1 201 8669 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.1234.5678 Safari/537.36
192.168.0.144 2024-02-18 14:57:00 GET /api/v1/users/26990 HTTP/1.1 200 9565 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9876.54321 Safari/537.36
192.168.0.70 2024-02-18 14:57:01 POST /api/v1/users/30991 HTTP/1.1 201 7781 - Mozilla/5.0 (Macintosh; Intel Mac OS X 1152) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9876.54321 Safari/537.36
192.168.0.201 2024-02-18 14:57:02 PUT /api/v1/tasks/7064 HTTP/1.1 204 7816 - Mozilla/5.0 (Macintosh; Intel Mac OS X 1101) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.1234.5678 Safari/537.36
192.168.0.166 2024-02-18 14:57:03 GET /api/v1/users/12541 HTTP/1.1 200 6082 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9876.54321 Safari/537.36
192.168.0.233 2024-02-18 14:57:04 DELETE /api/v1/products/1588 HTTP/1.1 204 5112 - Mozilla/5.0 (Macintosh; Intel Mac OS X 1152) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.9876.54321 Safari/537.36
192.168.0.47 2024-02-18 14:57:05 PUT /api/v1/orders/8586 HTTP/1.1 204 7961 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36

ログデータの分析

SQL でログデータを分析していけるということで、簡単な分析を行ってみます。

エンドポイントの成功率・エラー率

各エンドポイントの成功率やエラー率を算出してみます。

WITH log_with_endpoint_added AS (
    SELECT 
        method,
        regexp_extract(uri, '^/api/v1/\w+') as endpoint,
        status
    FROM nginx_logs
)
, log_with_call_count_added AS (
    SELECT
        method, 
        endpoint, 
        status,
        status / 100 as status_class,
        COUNT(endpoint) as call_count
    FROM log_with_endpoint_added
    GROUP BY method, endpoint, status
)
, number_results_per_endpoint AS (
    SELECT
        endpoint, 
        method, 
        SUM(call_count) as all_count,
        SUM(CASE WHEN status_class = 2 THEN call_count ELSE 0 END) as succeeded_count,
        SUM(CASE WHEN status_class = 4 THEN call_count ELSE 0 END) as client_error_count,
        SUM(CASE WHEN status_class = 5 THEN call_count ELSE 0 END) as server_error_count
    FROM log_with_call_count_added
    GROUP BY endpoint, method
)
SELECT 
    endpoint,
    method,
    all_count as count,
    CONCAT(CAST(ROUND((CAST(succeeded_count as DOUBLE) / all_count) * 100, 2) as VARCHAR), '%') as succeeded_rate,
    CONCAT(CAST(ROUND((CAST(client_error_count as DOUBLE) / all_count) * 100, 2) as VARCHAR), '%') as client_error_rate,
    CONCAT(CAST(ROUND((CAST(server_error_count as DOUBLE) / all_count) * 100, 2) as VARCHAR), '%') as server_error_rate
FROM number_results_per_endpoint
ORDER BY server_error_rate desc
;

endpoint method count succeeded_rate client_error_rate server_error_rate
/api/v1/users DELETE 15 73.33% 20.00% 6.67%
/api/v1/products DELETE 22 81.82% 13.64% 4.55%
/api/v1/tasks GET 96 84.38% 11.46% 4.17%
/api/v1/orders GET 88 85.23% 11.36% 3.41%
/api/v1/products POST 59 74.58% 22.03% 3.39%
/api/v1/users POST 59 83.05% 13.56% 3.39%
/api/v1/messages GET 95 88.42% 8.42% 3.16%
/api/v1/users PUT 32 87.50% 9.38% 3.13%
/api/v1/products PUT 40 80.00% 17.50% 2.50%
/api/v1/products GET 88 79.55% 18.18% 2.27%
/api/v1/users GET 91 78.02% 19.78% 2.20%
/api/v1/messages POST 49 83.67% 14.29% 2.04%
/api/v1/tasks POST 59 88.14% 10.17% 1.69%
/api/v1/messages PUT 29 82.76% 17.24% 0.00%
/api/v1/messages DELETE 17 100.00% 0.00% 0.00%
/api/v1/orders DELETE 18 94.44% 5.56% 0.00%
/api/v1/tasks DELETE 8 87.50% 12.50% 0.00%
/api/v1/tasks PUT 39 92.31% 7.69% 0.00%
/api/v1/orders PUT 40 82.50% 17.50% 0.00%
/api/v1/orders POST 56 89.29% 10.71% 0.00%

SQL でクエリを書けるので、集計などもこうして柔軟に分析を行っていけます。

複数リソースを用いた分析

Athena を使えば、ログファイルを柔軟に分析できることがわかりました。次に、ログファイルだけではなく、別のリソースを掛け合わせて使ってみます。

ログファイル × CSV ファイル

先ほどのログファイルのデータを使って、このアプリケーション API において使われていないエンドポイントを抽出してみます。

新たに、エンドポイントの一覧を収録した CSV ファイルを用意しました。

endpoints.csv

/api/v1/products
/api/v1/orders
/api/v1/users
/api/v1/messages
/api/v1/tasks
/api/v1/bookmarks
/api/v1/friends

表というよりは、エンドポイントの一覧が書き連ねてあるだけの CSV ファイルです。

これを S3 に配置します。

この CSV に対応するテーブルを Athena に作成します。(nginx_logs テーブルを作成したのと同じ要領で、CSV をパースする設定にてテーブルを作成します。)

-- DDL
CREATE EXTERNAL TABLE IF NOT EXISTS `mydatabase`.`endpoints` (`endpoint` string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES ('field.delim' = ',')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://sample-logs/documents/'
TBLPROPERTIES ('classification' = 'csv');

作成した endpoints テーブルを SELECT してみます。

あとは、先程の nginx_logs テーブルのデータにこの endpoints テーブルのデータをぶつければ、使用されていないエンドポイントが抽出できます。

WITH log_with_endpoint_added AS (
    SELECT 
        method,
        regexp_extract(uri, '^/api/v1/\w+') as endpoint,
        status
    FROM nginx_logs
)
, endpoint_used AS (
    SELECT 
        DISTINCT endpoint
    FROM log_with_endpoint_added
)

SELECT 
*
FROM endpoints
WHERE not exists(
    SELECT * FROM endpoint_used WHERE endpoints.endpoint=endpoint_used.endpoint
)
;

こんな風に、様々な形式のデータを取り込み、分析できる柔軟性を Athena は持ち合わせています。

瞬時に、柔軟に、そして簡単に。

AWS Athena は、標準的な SQL を使用してデータにアクセスできるため、SQL のスキルを持つ多くのユーザーにとって親しみやすい環境です。今回は取り上げませんでしたが、notebook の実行も可能なため、SQL の範囲を超えるような分析も行える点も良いです。

また、データのフォーマットに対する柔軟性も高く、JSONCSV、Parquet、ORC などのさまざまな形式のデータに対応しています。これにより、さまざまなデータソースからのデータを統合して分析することができます。

個人的には、急なログ分析の必要が生じた際に瞬時に分析環境を構築できる点は、非常に心強く、利便性が高いと感じています。

ログなどの分析を行う際は、Amazon Athena を試してみてください。


現在 back check 開発チームでは一緒に働く仲間を募集中です。 herp.careers