[S3] S3を挟んでS3に一覧ログを記録してみました

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に出力して、定期的にそれを集計(一覧生成)するのを試してみました。 不要になった一次ログは、ライフサイクルとかで自動的に削除してしまうことにします。