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

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

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


## 通常のログ出力(何もしない場合)
まずは何もしない場合です。

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

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

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

```console:
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`の書き込み許可を外すことで、自動でログストリームが作成されないため、結果としてログを出力しないように設定可能です。

|![アクション許可設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406169/f8b6ad31-8f9e-1da4-ccb8-022114c62c48.png)|
|:-:|
上記は`CreateLogStream`の許可を外して、`PutLogEvents`だけが残っている状態です。


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

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

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

|![ログストリームの作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406169/7c846417-18c3-e65a-0c46-138f2cb041eb.png)|
|:-:|

|![ログストリームの作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406169/38be44f9-7ce3-b464-cac1-34e859ed9912.png)|
|:-:|

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

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

```javascript:ログ出力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ドキュメント](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudWatchLogs.html#putLogEvents-property)

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

PutLogEventsを2回以上呼び出すとsequenceTokenを付与しないと拒否されてしまいます。
そのため、`describeLogStreams`メソッドを利用して、uploadSequenceTokenを取得し、その値をPutLogEventsに渡してあげています。
[describeLogStreams  AWSドキュメント](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudWatchLogs.html#describeLogStreams-property)

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

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