[PR] あなたが Kindle で読みたいその本、Kindle に対応したら Twitter でお知らせします。

Lambda を使って CloudWatch Logs から S3 へ自動的にエクスポートする

Posted on

Lambda のログは自動的に CloudWatch Logs に保存されますが、他と連携する場合は S3 のほうが何かと都合がいいです。

CloudWatch Logs にはログデータを一括で Amazon S3 にエクスポートする機能があります。リアルタイム性が求められずバッチ処理で定期的にエクスポートする用途に向いています(リアルタイム性が求められる場合は、サブスクリプションを使用したログデータのリアルタイム処理が用意されています)。

というわけで Lambda と CloudWatch Events を使って、CloudWatch Logs から S3 へ自動的にエクスポートしてみます。

CloudWatch Logs API の CreateExportTask

S3 へのエクスポートは CloudWatch Logs API の CreateExportTask を使います。 fromto のパラメータを指定することで対象日時を絞れます。今回は日次単位でエクスポートしたいので、前日の 00:00:00 〜 23:59:59 を指定します。

ちなみに、Lambda と CloudWatch Logs のタイムゾーンは UTC で統一されています。タイムゾーンの変換は必要ないので、コードの中では UTC のまま処理します。

S3 のバケットポリシーを追加する

CloudWatch Logs が書き込めるように S3 のバケットポリシーを追加します。バケットは CloudWatch Logs と同じリージョンにする必要があります。

バケット名を xxxxx-lambda-log にしているので読み替えてください。

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

CloudWatch コンソールを使用してログデータを Amazon S3 にエクスポートする」を参考に、手動でエクスポートできることを確認しておきます。

Python でバッチスクリプトを書く

エクスポートするためのバッチスクリプトを Python で書きます。 lambda_names3_bucket_name は読み替えてください。

import datetime
import time
import boto3

lambda_name = 'xxxxx'
log_group_name = '/aws/lambda/' + lambda_name
s3_bucket_name = 'xxxxx-lambda-log'
s3_prefix = lambda_name + '/%s' % (datetime.date.today() - datetime.timedelta(days = 1))

def get_from_timestamp():
    today = datetime.date.today()
    yesterday = datetime.datetime.combine(today - datetime.timedelta(days = 1), datetime.time(0, 0, 0))
    timestamp = time.mktime(yesterday.timetuple())
    return int(timestamp)

def get_to_timestamp(from_ts):
    return from_ts + (60 * 60 * 24) - 1

def lambda_handler(event, context):
    from_ts = get_from_timestamp()
    to_ts = get_to_timestamp(from_ts)
    print 'Timestamp: from_ts %s, to_ts %s' % (from_ts, to_ts)

    client = boto3.client('logs')
    response = client.create_export_task(
        logGroupName      = log_group_name,
        fromTime          = from_ts * 1000,
        to                = to_ts * 1000,
        destination       = s3_bucket_name,
        destinationPrefix = s3_prefix
    )
    return response

実行された前日 0 時のタイムスタンプを取得して、そこから 86399 秒間 (00:00:00 〜 23:59:59) のログを対象にエクスポートしています。引数のタイムスタンプは秒ではなくミリ秒なので、Python で計算したタイムスタンプを 1000 倍しています。

うまく実行されていれば、CloudWatch Logs の View all exports to Amazon S3 でエクスポート結果が確認できます。実際のログは s3://xxxxx-lambda-log/xxxxx/yyyy-mm-dd/ にエクスポートされています。

CloudWatch Logs のエクスポート結果

CreateExportTask は非同期 API なので、エクスポートが終わるまでに最大 12 時間かかる場合があるようです(データ量によると思いますが、自分は数秒で完了しました)。なので、この時間までに必ずエクスポートしたいという要件には向いていません。

最後に CloudWatch Events でスケジュール実行のトリガーを設定します。 UTC 時間の 0 時 5 分に実行させるには cron(5 0 * * ? *) と指定します。

CloudWatch Events のトリガー設定

まとめ

数十行の Lambda ファンクションで CloudWatch Logs から S3 へのエクスポートが自動化できました。 Lambda を使って人がオペレーションする作業をどんどん減らしていきたいです。