Lambdaのデフォルトログを出力させずに意図したログのみを出力する


Lambda実行時にデフォルトで表示されるログを非表示にしつつ、アプリケーションで意図したログだけをCloudWatch Logsに出力する方法です。

CloudWatch Logsの料金は高いので、なるべく保存するログを減らしたいという目的で対応しました。

通常のログ出力(何もしない場合)

まずは何もしない場合です。

今回のテストで利用するLambdaです。
Node.js 12.X です。

Lambda
exports.handler = async (event) => {
    console.log('log test');
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

出力されるログは以下のようになります。

START RequestId: XXXXXX Version: $LATEST
2020-03-13T13:33:26.678Z    XXXXXX  INFO    log test
END RequestId:  XXXXXX 
REPORT RequestId:  XXXXXX   Duration: 17.18 ms  Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 70 MB  Init Duration: 108.39 ms    

このようにLambda上で出力するように書いたのは、log testだけなのですが、START~ END~ REPORT~ というLambdaがデフォルトで出力するログが一緒に出力されています。
今回の記事は、これらのデフォルトのログは出力しないようにしつつ、Lambdaで書いたメッセージだけを出力したいという内容です。

デフォルトのログを出力しないようにする

IAMの権限修正

Lambdaのデフォルトログを出力しないようにするには、IAMを修正します。

Lambdaを作成した時にロールも同時に作成した場合、そのロールにはCloudWatch logサービスへの書き込み許可が設定されています。

また、CloudWatch logsのCreateLogStream, PutLogEventsアクションに対して書き込み許可が設定されています。

このうちCreateLogStreamの書き込み許可を外すことで、自動でログストリームが作成されないため、結果としてログを出力しないように設定可能です。

上記はCreateLogStreamの許可を外して、PutLogEventsだけが残っている状態です。

ただしこの対応を行うとconsole.logも出力されなくなります。

Streamの作成

CreateLogStreamの許可を外したことで、ログストリームが自動で作成されなくなりました。
そのため、ログストリームを手動で作成します。

CloudWatchのロググループでログストリームの作成を行ってください。

手動でログ出力

では残りはログを出力する処理を書いていきます。

ログの出力はaws-sdkを利用します。
では全体的なコードをお見せします。

ログ出力Lambda
const LOG_GROUP_NAME = '/aws/lambda/loglog';
const LOG_STREAM_NAME = 'test';

const AWS = require('aws-sdk');
const cloudwatchlogs = new AWS.CloudWatchLogs({region: 'ap-northeast-1'});

const outLog = async (msg) => {
  const describeParams = {
    logGroupName: LOG_GROUP_NAME,
    logStreamNamePrefix: LOG_STREAM_NAME,
  };
  const data = await cloudwatchlogs.describeLogStreams(describeParams).promise();
  console.log(data);
  let sequenceToken = null;
  if (data.logStreams[0]) {
    sequenceToken = data.logStreams[0].uploadSequenceToken;
  }
  console.log('sequenceToken:' + sequenceToken);

  const params = {
    logEvents: [{
      message: msg,
      timestamp: Date.now()
    }],
    logGroupName: LOG_GROUP_NAME,
    logStreamName: LOG_STREAM_NAME,
    sequenceToken: sequenceToken,
  };
  console.log('put start');
  await cloudwatchlogs.putLogEvents(params).promise();
  console.log('put end');
};

exports.handler = async (event) => {

    await outLog('log test');
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

解説

ログの出力はputLogEventsメソッドを利用します。putLogEvents AWSドキュメント

入力パラメータには、ログを出力するためのロググループやログストリーム、出力するメッセージなどを指定してください。

PutLogEventsを2回以上呼び出すとsequenceTokenを付与しないと拒否されてしまいます。
そのため、describeLogStreamsメソッドを利用して、uploadSequenceTokenを取得し、その値をPutLogEventsに渡してあげています。
describeLogStreams AWSドキュメント

今回outLogというfunctionを作成しましたが、どのLambdaでも使うということであれば、Lambda-Layerにしてしまうのも良いと思います。

その他

今回の対応ではCreateLogStreamを拒否にしましたが、CreateLogStreamは許可としつつ、
PutLogEventsで許可するリソースを一部に限定(例えば固定値+日付など)にすることで、Lambdaでログストリームの作成+作成したログストリームにログ出力などの対応でも問題ありません。
その方がログが整理されて出力可能なのでより良いと思います。