Step Functions と Lambda で Cloudwatch Logs を S3 へ転送
前回のエンジニアブログ AWS Step Functions では、Step Functions と Lambda を組み合わせて Cloudwatch Logs を 定期的に S3 へ保存する方法について、主に AWS Step Functions の部分をご紹介しました。今日はその続編として、AWS Step Functions から呼び出される Lambda 関数を見てみましょう。
処理の全体像については、前回の記事をご参照ください。
事前準備
事前準備として、Lambda 関数 に Cloudwatch Logs を実行する権限を確認しておきましょう。
ログを作成する権限の他に、エクスポートタスクを参照する権限(DescribeExportTasks)が必要な事に注意してください。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"logs:CreateExportTask",
"logs:CreateLogStream",
"logs:CreateLogGroup",
"logs:PutLogEvents",
"logs:DescribeExportTasks"
],
"Resource": "*"
}
]
}
Lambda関数
AWS Step Functions から呼び出される Lambda関数は2種類です。
- export_log_to_s3:Cloudwatch Logs を S3 へ送る エクスポートタスクを作成する
- check_export_task:エクスポートタスクが完了したかチェックする
エクスポートタスクを作成する
まずは、ログのエクスポートタスクを作成する関数を見てみましょう。
Step Functionsからのパラメータは event. の部分で受け取ることができます。
エクスポートタスク作成後は、次のチェック関数へ渡すための タスクID を返します。
下記の関数は「前月分のログを抽出する」という条件になっていますが、注意が必要なのはログ抽出条件の日時を算出する部分です。
Cloudwatch Logs はUTCで管理されているので、格納されているログの時刻との差異があります。JSTで「前月の1日0:00」を指定したい場合には、9時間前の「前々月の末日15:00」時刻を設定します。
const aws = require('aws-sdk');
const cwl = new aws.CloudWatchLogs();
aws.config.update({region: 'ap-northeast-1'});
exports.handler = async function handler(event, context) {
const s3BucketName = event.s3BucketName;
const logGroupName = event.logGroupName;
const s3Prefix = event.s3Prefix;
const today = new Date();
const from = new Date(today.getFullYear(), today.getMonth() - 1, 0, 15, 0, 0, 0); // 前々月の末日15:00(UTC)=前月の1日0:00(JST)
const to = new Date(today.getFullYear(), today.getMonth(), 0, 14, 59, 59, 999); // 前月の末日14:59.59(UTC)=前月の末日23:59.59(JST)
try {
const ret = await exportTask(s3BucketName, s3Prefix, logGroupName, from, to);
context.succeed({
s3BucketName: s3BucketName,
s3Prefix: s3Prefix,
logGroupName: logGroupName,
taskId: ret.taskId
});
} catch (e) {
if (e.code == 'ResourceNotFoundException') {
context.succeed({
s3BucketName: s3BucketName,
s3Prefix: s3Prefix,
logGroupName: logGroupName,
taskId: event.taskId
});
} else {
context.fail(e);
}
}
};
async function exportTask(s3BucketName, s3prefix, logGroupName, from, to) {
const date = to.toLocaleDateString('ja-JP', {year: 'numeric', month: '2-digit'});
const destinationPrefix = date + '/' +s3prefix ;
const params = {
destination: s3BucketName,
from: from.valueOf(),
logGroupName: logGroupName,
to: to.valueOf(),
destinationPrefix: destinationPrefix
};
return await cwl.createExportTask(params).promise();
}
エクスポートタスクの完了を確認する
次はエクスポートタスクの完了をチェックする関数です。
前段の関数で返された タスクID を使って、describeExportTasks で確認できます。
リトライや Wait の処理は StepFunctions 側がやってくれるので、関数の中身はエクスポートタスクの完了・未完了を返すのみです。
const aws = require('aws-sdk');
const cwl = new aws.CloudWatchLogs();
exports.handler = handler;
class CheckError extends Error {
constructor(name) {
super(name);
this.name = name;
}
}
async function handler(event, context) {
const s3BucketName = event.s3BucketName;
const logGroupName = event.logGroupName;
const destPrefix = event.destPrefix;
const taskId = event.taskId;
try {
const ret = await describeTask(taskId);
const retTask = ret.exportTasks[0];
const statusCode = retTask.status.code;
if (statusCode == 'COMPLETED') {
context.succeed({
s3BucketName: s3BucketName,
logGroupName: logGroupName,
destPrefix: destPrefix,
taskId: taskId,
status: retTask.status.code
});
} else {
context.fail(new CheckError(statusCode));
return;
}
} catch (e) {
context.fail(e);
}
}
async function describeTask(taskId) {
const params = {
taskId: taskId
};
return await cwl.describeExportTasks(params).promise();
}
パラメータ定義部分は Step Functions 側に持っているので、S3へ移動したいログが増えた場合にも 関数は変更する必要はありませんし、余計なリトライや Wait の処理もないので、Lambdaだけで作成するよりはだいぶすっきりしたと思います。