CloudWatch Logsから、LmabdaでログをS3にエクスポートする。対象のロググループとバケット内の第一階層を引数で指定するようにした。今回の事例ではエクスポートの範囲は「前日0時〜実行当日の0時」となる。

参考
boto3 API Reference
LambdaよりCloudWatchログをS3に保存方法紹介

 

ちなみに過去マネコンから実行する手順書いた。
CloudWatchLogsからS3へログをエクスポートする

 

今回の検証に使用したアイテム(個人メモ)

アイテム 名称
Lambda用IAMロール lambda_basic_execution
Lambda関数 log-export-function
S3バケット log-export-xxxxxxxx

 

Lambda用IAMロールの権限はlogsフルアクセスのみ。S3もいると思ってたがなくてもできた。バケットポリシー側で許可しているからか。S3バケット名は環境変数で指定した。ちなみに対象バケットは、動作不可になるためオブジェクトロックを設定しないこと。

 

S3バケットポリシー(log-export-xxxxxxxx)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "logs.ap-northeast-1.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::log-export-xxxxxxxx"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "logs.ap-northeast-1.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::log-export-xxxxxxxx/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        }
    ]
}

 

Lambdaコード(Python3.9)

import boto3
import collections
from datetime import datetime, date, time, timedelta
import os

def lambda_handler(event, context):
    log_g = event.get('loggrp')
    key1 = event.get('key_name')
    
    # 日付情報取得
    yesterday = datetime.combine(date.today()-timedelta(1),time())
    today = datetime.combine(date.today(),time())
    unix_start = datetime(1970,1,1)
    
    # 日付範囲指定。範囲は実行時点を基準としたUNIXタイムスタンプの日本時間(+9h)
    from_t = int((yesterday-unix_start).total_seconds() * 1000)
    to_t = int((today-unix_start).total_seconds() * 1000)

    # S3 bucket + prefix定義(bucket/key1/YYYY/mmDD)
    bucket = os.environ['S3_BUCKET']
    key2 = yesterday.strftime("%Y")
    key3 = yesterday.strftime("%m-%d")
    s3_key = key1 + "/" + key2 + "/" + key3

    try:
        logs = boto3.client('logs')

        response = logs.create_export_task(
            logGroupName = log_g,
            fromTime = from_t,
            to = to_t,
            destination = bucket,
            destinationPrefix = s3_key
        )

    except Exception as e:
        print(e)

 

では、Lambda関数をCLIから実行してみる。ま、普通はEventBridge経由とかでやると思うけど。(でもEventBridgeって引数指定できるんかな?)

--payloadオプションのロググループ名、S3バケットの最初の階層となるサービス種別(ここではEC2)を指定している。これはJSONなので、file://parameter.jsonなどとしてファイル指定でもよい。

$ aws lambda invoke --function-name log-export-function \
--payload '{ "loggrp" : "/ec2/var/log/messages","key_name" : "EC2" }' \
--cli-binary-format raw-in-base64-out \
response.json

 

実行すると、Prefixはできていたが中身は書き込みテスト用ファイルしかない。このコード上の仕様は、前日の00:00:00 から当日の00:00:00までをエクスポートの対象としている。指定したロググループのログは、実行した時点で定義された日付範囲に含まれていないためである。

(つまり、12/18の日中にインスタンスを起動してログが送信されたとする。しかしエクスポート対象は前日の2021-12-17 00:00:00 〜 当日の2021-12-18 00:00:00までとなるため、この期間内にログがなければ対象は存在しないことになる)

ちなみに関数が実行されるEC2のタイムゾーンはUTC。Lamabaの環境変数でタイムゾーンをJSTにするのは非推奨らしいので、コード側で制御するのが望ましい、とのこと。タイムゾーンの問題を考え始めるといつも頭の中がジグソーパズル状態になる。

Lambda のタイムゾーンを環境変数TZで指定してはいけないっていう話
AWS Lambda でのタイムゾーン変換

 

「コード内での時間は常に UTC で扱い、表示する段階でローカル時間に変換する」を意識していればいいかなと思います。

 

これを当てはめると、「コード内の基準はUTCとし、JSTとして処理する必要になった時点でJSTに変換する」と考えればいいか。

ところでPythonでのタイムゾーン変換で検索すると大抵上記リンクと同様にpytzを使ったコードが紹介されている。pytzを使えば簡単なのだが、それができない事情があったから以下のように別の方法でやった。今回のコードではまた別の方法になっているが…、どこまでも執拗に追ってくるしつこい敵なので、一度タイムゾーン処理単体で記事を書こうかと思う。

CloudWatchアラーム + SNSからのメール本文をカスタマイズする(3)

 

話がそれたが、翌日再試行して、今度は無事エクスポートが実行された。今回のコード上の指定では以下スクショの階層でログがアップロードされる。(長いランダム文字列のプレフィックス配下に複数のログストリームのプレフィックス、最後に圧縮ログファイル)

S3

 

しかし、引数にLambda自体のログを指定するとエクスポートされない。なぜなんだ〜!…と思ったら、数時間後にリトライしたら期待値になった。試しにひとつログをDLして中身を覗いてみる。

$ gunzip -c 000000.gz
2021-12-18T09:30:45.236Z START RequestId: 4880df55-4576-4ccd-8edc-daecb18b0686 Version: $LATEST

 

このログは、マネコンの画面上ではJST(実際にLambdaを実行した時刻の日本時間)となっている。

2021-12-18T18:30:45.236+09:00      #Timestamp列の値
START RequestId: 4880df55-4576-4ccd-8edc-daecb18b0686 Version: $LATEST

 

うーん、これでいいのか?コード内でJSTに変換しているけど、逆にしなくていいのか?脳内パズル。

…だが、Lambda関数が実行されるUTCのTZから、エクスポート対象のログ範囲をUTCのTZで指定すると、実際にログが吐き出されたJSTの時刻とは異なる日付範囲になる。だから関数でJSTに変換するのは正しい、と捉えてよい気がする。

 

Travel


関連がありそうな記事