タダです.
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" }
関連記事
そこで,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" } }
参考情報
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 も以前から気になっていたので使えてよかったです.同様の対応をされている方の参考になれば嬉しいです.