AthenaでS3のJSONログを検索する

## 概要

S3に保存したJSON形式のログファイルをAthenaのクエリで取得します。Athenaを初めて使ったので自分用のメモです。

## 準備

### 検索対象となるJSONログのフォーマット
以下のような形式です。ログイン履歴のようなファイルです。

```json:log01.json
{
    "userName": "user01",
    "logBody": {
        "account": "123456789012",
        "time": "2018-08-30T00:45:28Z",
        "detail": {
            "clientIpAddress": "0.0.0.0",
            "actionType": "successfulLogin"
        }
    }
}
```

 - S3には「logs」フォルダを作成して、その中に以下の通りログを保存します。
 <img width="720" alt="スクリーンショット 2020-04-12 21.07.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/4d128d38-cd0a-96cf-1369-0c4c75a5c4f3.png">

## Athenaの操作

### 1. workgroupの作成

 - Create workgroupから作成します。
 - 次のページでの必須項目は以下の2つ
   - Workgroup name
     - 任意の名前
   - Query result location
     - 実行結果を保存する場所です。S3のバケットなどを指定します。__分析したいログがあるフォルダを選ぶと、その次のクエリ実行時に実行結果ファイルも取得対象になってしまいます。__
  
<img width="830" alt="スクリーンショット 2020-04-12 21.12.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/18be2d68-eede-a800-40ef-abb5a6a92949.png">

  - 別のワークグループがある場合、`Switch workgroup`ボタンで切り替えます。
  - ワークグループを切り替えても、右上のsettingsでQuery result locationを確認しましょう。変わっていないことがあります。変わっていない場合には、こちらも変更します。


### 2. DBとテーブルの作成

 - コンソールとクエリの2通りで作成できます。但し、JSONで用いる`struct`タイプはコンソールからだと作成できません。なので、クエリで作成して実行します。
 - ここでは、`sample`というDBに`login_logs`という名前のテーブルを作成しています。

```SQL:tableCreate
CREATE EXTERNAL TABLE IF NOT EXISTS sample.login_logs (
         `userName` string,
         `logBody` struct<
            `account`: string,
            `time`: string,
            `detail`: struct<
                `clientIpAddress`: string,
                `actionType`: string
             >
          >
  )
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES ('ignore.malformed.json' = 'true' )
LOCATION 's3://<Bucket Name>/logs/'
TBLPROPERTIES ('has_encrypted_data'='false' );
```

 - ポイントになるのは以下の点です。
   - `LOCATION`でクエリ対象としたいログがあるバケット/フォルダを指定します。サブフォルダがある場合、再帰的に検索対象になるため、トップのフォルダを指定します。
   - JSONで入れ子になっている箇所は`struct`を用います。`struct`内は`:`で項目名と定義を挟んでいます。
   - `(バッククォート)で囲っているのは、予約語と被らないようにするためです。大半は被りませんが、囲った方が安全でしょう。
   - `'ignore.malformed.json' = 'true'`は誤った形式のレコードを無視するためです。詳細は以下公式を参照。
     - [JSON データ読み取りのベストプラクティス](https://docs.aws.amazon.com/ja_jp/athena/latest/ug/parsing-JSON.html)
 - 実行すると以下のようなテーブルができます。JSON部分は構造体として作成されるため、カラムは2つとなります。
 - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/83eeb482-5d45-5752-d19f-9dbe16e80aea.png)

  

### 3. クエリの実行

 - 以下のようなselectクエリを作って実行します。

```SQL:select
select
    userName as Username,
    logBody.account as Account,
    logBody.time as Time,
    logBody.detail.clientIpAddress as ClientIpAddress,
    logBody.detail.actionType as ActionType
from sample.login_logs
```

 - 実行結果
<img width="1235" alt="スクリーンショット 2020-04-12 22.37.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/419ff869-c3ab-7ae5-0842-ff261e2932e4.png">



 - 例として、「ログイン失敗のみ」という条件で絞るなら、以下のようなwhere句をつけます。

```SQL:select
select
    userName as Username,
    logBody.account as Account,
    logBody.time as Time,
    logBody.detail.clientIpAddress as ClientIpAddress,
    logBody.detail.actionType as ActionType
from sample.login_logs
where logBody.detail.actionType = 'failureLogin'
```

 - 実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/7a407c3d-40de-e24b-e137-1b01fc6b41aa.png)


## その他注意事項

 - 先述の通り、`Query result location`を検索したいログと同じフォルダにした場合、そこに作成された結果のフォルダ(Unsaved)も検索対象となってしまい、クエリの実行結果に余計な結果が混ざることになります。
 - 例として、以下のようにjsonファイルと同じディレクトリにUnsaved等が作成されると、Unsavedも検索対象となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/ac34b1e6-8636-f665-a9c9-612297f58444.png)
 - ログの形式が合わないため、大半は空白行が混ざります。同じ症状の方は結果の出力先を確認ください。

## 参考

 - [JSONSerDe によるマッピングを使って、入れ子の JSON から Amazon Athena のテーブルを作成する](https://aws.amazon.com/jp/blogs/news/create-tables-in-amazon-athena-from-nested-json-and-mappings-using-jsonserde/)
 - [【初心者向け】AWS Athena の 使い方](https://qiita.com/yoshiyama_hana/items/9b2f26b214de92a0f36c)