継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

FireLens からログだけ Lambda を使って抽出し,Athena でクエリする

タダです.

ECS に乗っているコンテナのログを FireLens を経由して Kinesis Firehose -> S3 にログを出しました.ただ,下記の JSON 形式でログが保存されているため,Athena でクエリしようとするとしてもログの詳細を見る時辛いなと思っていました.

{
    "container_id": "xxx",
    "container_name": "xxx",
    "ecs_cluster": "xxx",
    "ecs_task_arn": "arn:aws:ecs:xxx:xxx:task/xxx/xxx",
    "ecs_task_definition": "xxx:xxx",
    "log": "xxx"
}

関連記事

sadayoshi-tada.hatenablog.com

そこで,Lambda を使って log の部分だけ抽出して S3 に保存し,そのログを Athena でクエリしていきます.

FireLens から送られてくるログを抽出するためのコード

Lambda は Python 3.8 で書いているのですが,ログを抽出し,S3に保存するところのみの部分を抜粋してます.Firehose から送られてくるデータからログの詳細を抽出して Fireshose に戻し S3 に保存されます.

import json
import base64
from botocore.exceptions import ClientError

def lambda_handler(event, context):  
    log_output = []  
    for record in event['records']:
        payload = json.loads(base64.b64decode(record['data']))
        log_detail = payload["log"]

     output_record = {
        'recordId': record['recordId'],
        'result': 'Ok',
        'data': base64.b64encode(json.dumps(log_detail).encode('utf-8')).decode('utf-8')
    }
    
    try:
        log_output.append(output_record)
    except ClientError as e:
        print(e.response['Error']['Message'])
    return {'records': log_output} 

S3 に保存されたログのイメージ

ファイルを開くと,ログ詳細だけが保存されるようになったのでこれで Athena でいい感じにクエリしていけそうです.

{
    "request_headers": {
        "x-forwarded-for": "xx.xx.xx.xx",
        "x-forwarded-proto": "http",
        "x-forwarded-port": "xxxx",
        "host": "xx.xx.xx.xx:xxxx"
    },
    "remote_addr": "xx.xx.xx.xx",
    "request_uri": "/",
    "request_method": "GET",
    "request_time": "2021/05/20 15:17:02",
    "response_time": "0.0004",
    "status": 200,
    "response_headers": {
        "content-length": "xx",
        "content-type": "application/json"
    }
}

参考情報

https://docs.aws.amazon.com/ja_jp/firehose/latest/dev/data-transformation.html#data-transformation-status-model

Athena のテーブル作り

S3 に保存したファイルは上記のような形式で保存されているので,このログファイルをクエリできるようにテーブル作ります.Athena のテーブルは下記のクエリで作りました.JSON の中で内包されている JSON にもクエリできるよう struct 以降で定義しています.また,ログはYYYY/MM/DDのフォルダ配下に入っているので,Partition Projection を使っています.

CREATE EXTERNAL TABLE IF NOT EXISTS `partition_table`(
  `request_headers` struct<`x-forwarded-for`:string,`x-forwarded-proto`:string,`x-forwarded-port`:string,`host`:string>, 
  `remote_addr` string, 
  `request_uri` string , 
  `request_method` string , 
  `request_time` string , 
  `response_time` string , 
  `status` bigint , 
  `response_headers` struct<`content-length`:string, `content-type`:string>
  )
PARTITIONED BY (
  `dateday` string 
)
ROW FORMAT SERDE 
  'org.openx.data.jsonserde.JsonSerDe'
LOCATION
  's3://バケット名/パス'
TBLPROPERTIES (
  'projection.enabled' = 'true',
  'projection.dateday.type' = 'date',
  'projection.dateday.range' = '2021/05/20,NOW',
  'projection.dateday.format' = 'yyyy/MM/dd',
  'projection.dateday.interval' = '1',
  'projection.dateday.interval.unit' = 'DAYS',
  'storage.location.template' = 's3://バケット名/パス/${dateday}'
);

クエリを投げてみる

作ったテーブルに対してクエリを投げてみます.

SELECT * FROM "xxx"."partition_table" where dateday = '2021/05/20';

こんな感じで返ってきます.

1   {x-forwarded-for=null, x-forwarded-proto=null, x-forwarded-port=null, host=xx.xx.xx.xx:xxxx}    xx.xx.xx.xx /   GET 2021/05/20 09:41:18 0.0005  200 {content-length=xx, content-type=application/json}  2021/05/20
2   {x-forwarded-for=null, x-forwarded-proto=null, x-forwarded-port=null, host=xx.xx.xx.xx:xxxx}    xx.xx.xx.xx /   GET 2021/05/20 11:59:25 0.0004  200 {content-length=xx, content-type=application/json}  2021/05/20
3   {x-forwarded-for=null, x-forwarded-proto=null, x-forwarded-port=null, host=xx.xx.xx.xx:xxxx}    xx.xx.xx.xx /   GET 2021/05/20 09:18:47 0.0004  200 {content-length=xx, content-type=application/json}  2021/05/20

内包されている JSON にクエリしてみます.例えば,request_headerの中にあるhostをクエリしてみます.

SELECT request_headers."host" FROM "xxx"."partition_table" where dateday = '2021/05/20';

これでhostにはいっている IP アドレスだけ返ってきます.これでログから必要な情報をクエリしてきそうなのでログ解析が以前よりは楽になりそうです.

1   xx.xx.xx.xx:xxxx
2   xx.xx.xx.xx:xxxx
3   xx.xx.xx.xx:xxxx

参考情報

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/partition-projection.html

まとめ

FireLens 経由で送られるログを必要な情報だけ Lambda で抽出し,Athena でクエリしやすくしてみました.また,Athena の Partion Projection も以前から気になっていたので使えてよかったです.同様の対応をされている方の参考になれば嬉しいです.