ELBアクセスログ分析のためのAthena設定とクエリ例

最近、ELB(Application Load Balancer)のアクセスログを有効化し、問題が発生した際にアプリケーションの挙動を迅速に分析できるようにしました。S3に保存されるログを効率的に扱うために、Athenaを使用して専用のデータベースとテーブルを設定し、分析用のクエリを整理しました。

https://qiita.com/suzuki0430/items/28037f90904a5a4f0f27

## Athenaの設定

### データベースの作成
Athenaのクエリエディタを開き、以下のコマンドでログ用のデータベースを作成します。

```SQL
CREATE DATABASE IF NOT EXISTS production_logs;
```

![スクリーンショット 2024-08-15 8.54.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/4d03574e-3b73-a08d-5c86-f3ef7cfb25bf.png)

### テーブルの作成
ドキュメント「[パーティション射影を使用したALBアクセスログ用テーブルの作成](https://docs.aws.amazon.com/ja_jp/athena/latest/ug/application-load-balancer-logs.html#create-alb-connection-logs-table-partition-projection)」を参考にして、テーブルを設定します。日毎のパーティションを設けることで、クエリのパフォーマンスを向上させ、S3に新しく追加されたログが自動的にテーブルに取り込まれます。

```SQL
CREATE EXTERNAL TABLE IF NOT EXISTS alb_access_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,
            conn_trace_id 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://DOC-EXAMPLE-BUCKET/AWSLogs/<ACCOUNT-NUMBER>/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://DOC-EXAMPLE-BUCKET/AWSLogs/<ACCOUNT-NUMBER>/elasticloadbalancing/<REGION>/${day}"
            )
```

![スクリーンショット 2024-08-15 9.03.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/b431b9f8-f4c0-9f30-3d69-15a8cf8173f1.png)

## トラブルシューティングに使えそうなクエリ
問題が起こったときに頻繁に利用できそうなクエリをまとめました。

- `most_frequently_accessed_endpoints` : 最もアクセスされたエンドポイントの確認
```sql
SELECT request_url, COUNT(*) AS request_count
FROM "alb_access_logs"
GROUP BY request_url
ORDER BY request_count DESC
LIMIT 100;
```

- `longest_average_response_time_endpoints` : 応答時間が長いエンドポイントの特定
```sql
SELECT request_url, AVG(response_processing_time) AS avg_response_time
FROM "alb_access_logs"
GROUP BY request_url
ORDER BY avg_response_time DESC
LIMIT 100;
```

- `request_count_by_hour` : 時間帯別のリクエスト数
```sql
SELECT 
    date_trunc('hour', date_add('hour', 9, parse_datetime(time, 'yyyy-MM-dd''T''HH:mm:ss.SSSSSSZ'))) AS hour_jst, 
    COUNT(*) AS request_count
FROM "alb_access_logs"
WHERE 
    parse_datetime(time, 'yyyy-MM-dd''T''HH:mm:ss.SSSSSSZ') BETWEEN timestamp '2024-08-13 00:00:00.000' AND timestamp '2024-08-14 00:00:00.000'
GROUP BY 
    date_trunc('hour', date_add('hour', 9, parse_datetime(time, 'yyyy-MM-dd''T''HH:mm:ss.SSSSSSZ')))
ORDER BY hour_jst;
```

- `analysis_of_abnormal_status_codes` : ステータスコード別のリクエスト数
```sql
SELECT elb_status_code, COUNT(*) AS status_count
FROM "alb_access_logs"
GROUP BY elb_status_code
ORDER BY status_count DESC;
```

- `request_analysis_by_client_ip` : クライアントIPアドレス別のリクエスト数
```sql
SELECT COUNT(request_verb) AS
 count,
 request_verb,
 client_ip
FROM "alb_access_logs"
GROUP BY request_verb, client_ip
LIMIT 100;
```

## 参考

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/application-load-balancer-logs.html

https://aws.amazon.com/jp/athena/pricing/