この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
CX事業本部の平内(SIN)です。
「処理ごとに蓄積される一覧(ログ)を、リアルタイムにS3上に作成する」というニーズを叶えようと、昨日、一昨日と下記のような試みをしましたが、どちらもイマイチでした。
結局、「一旦DynamoDBとかに入れてから処理する」という結論になるのですが・・・
今回は、S3をDB代わりに使用して「一旦S3にログを出力して、定期的に、一覧を生成する」というのを試してみました。
こちらは、S3の「結果整合性」と戦うことはありませんので、もしよかったら、読み進めて頂ければ幸いです。
注意:すいません、DynamoDBを使用するよりメリットが有る!とかでは無いです。
2 処理+ログ記録
「処理+ログ記録」側の実装は、下記のようなものです。
import * as AWS from 'aws-sdk';
exports.handler = async (event: any) => {
await log(event.message);
}
const prefix = 'Target'; // 識別子(的なもの)
const bucketName = `log-sample-temporary`;
async function log(message: string) {
const dt = new Date();
const s3 = new AWS.S3();
const body = {
message: `${createDateTime(dt)} ${message}`
}
const params = {
Bucket: bucketName,
Key: createKeyName(dt),
Body: JSON.stringify(body)
};
return await s3.putObject(params).promise();
}
function createKeyName(dt: Date){
const Y = dt.getFullYear();
const M = ("00" + (dt.getMonth()+1)).slice(-2);
const D = ("00" + dt.getDate()).slice(-2);
const h = ("00" + (dt.getHours())).slice(-2);
const m = ("00" + (dt.getMinutes())).slice(-2);
const s = ("00" + (dt.getSeconds())).slice(-2);
const ms = ("000" + (dt.getMilliseconds())).slice(-3);
const rand = Math.random().toString(10).slice(-10);
return `${prefix}/${Y}-${M}-${D}-${h}${m}${s}-${ms}-${rand}`;
}
function createDateTime(dt: Date){
const Y = dt.getFullYear();
const M = ("00" + (dt.getMonth()+1)).slice(-2);
const D = ("00" + dt.getDate()).slice(-2);
const h = ("00" + (dt.getHours())).slice(-2);
const m = ("00" + (dt.getMinutes())).slice(-2);
const s = ("00" + (dt.getSeconds())).slice(-2);
const ms = ("000" + (dt.getMilliseconds())).slice(-3);
return `${Y}/${M}/${D} ${h}:${m}:${s}.${ms}`;
}
上のコードでは、prefixをつけて、階層下にログファイルを生成しています。
3 集計処理
集計側では、最初に、prefixsの一覧を作成して、prefixsごとに、その中のログを列挙して、prefixs.logという名前の一覧を生成しています。
import * as AWS from 'aws-sdk';
const bucketName = `log-sample-temporary`;
exports.handler = async (event: any) => {
console.log(JSON.stringify(event));
// prefixs一覧の作成
const prefixs = await getPrefixs();
// 一覧の作成
const s3 = new AWS.S3();
await Promise.all(prefixs.map( async prefix => {
const log = await getLog(prefix);
console.log(log);
await s3.putObject({Bucket: bucketName, Key: `${prefix}.log`, Body: log} ).promise();
}));
}
async function getPrefixs(): Promise<string[]> {
const s3 = new AWS.S3();
let result:string[] = [];
const params = {
Bucket: bucketName,
};
const list = await s3.listObjects(params).promise();
if(list && list.Contents){
await Promise.all(list.Contents.map( async content => {
const key = content.Key!;
const tmp = key.split('/');
if(tmp.length >= 2) {
if(result.indexOf(tmp[0])==-1){
result.push(tmp[0])
}
}
}));
}
return result;
}
async function getLog(prefix: string): Promise<string> {
const s3 = new AWS.S3();
let result = '';
let keys: string[] = [];
const params = {
Prefix: prefix,
Bucket: bucketName,
};
const list = await s3.listObjects(params).promise();
if(list && list.Contents){
list.Contents.forEach(content=>{
const key = content.Key!;
if(key != `${prefix}.log`){
keys.push(key);
}
})
}
keys = keys.sort();
await Promise.all(keys.map( async key => {
const data = await s3.getObject( {Bucket: bucketName, Key: key} ).promise();
const json = JSON.parse(data.Body!.toString("utf-8"));
result += `${json.message}\n`;
}));
return result;
}
生成された一覧です。
2019/11/04 15:14:31.819 1
2019/11/04 15:14:31.939 5
2019/11/04 15:14:31.981 3
2019/11/04 15:14:32.206 6
2019/11/04 15:14:32.274 2
2019/11/04 15:11:57.156 75
2019/11/04 15:11:57.172 71
2019/11/04 15:14:32.396 0
2019/11/04 15:14:32.603 7
2019/11/04 15:14:32.640 14
2019/11/04 15:14:32.788 8
2019/11/04 15:14:32.940 10
2019/11/04 15:14:33.040 12
2019/11/04 15:14:33.076 16
2019/11/04 15:14:33.500 15
2019/11/04 15:14:33.501 17
・・・略
4 トリガー
当初、S3へのPutをトリガーにして、試してみたのですが、集計処理が高速で起動すると処理しきれないので、CloudWatch Eventsにしました。この処理は、ログの量に応じて、時間がかかるので、スケジュールの間隔は、それに応じて決定する必要があります。
同時実行は、並列動作しても、ほとんど意味ないので1でいいでしょう。
5 最後に
今回は、ログをS3に出力して、定期的にそれを集計(一覧生成)するのを試してみました。 不要になった一次ログは、ライフサイクルとかで自動的に削除してしまうことにします。