Lambda(SAM)からAmazon CloudWatch Logs(LocalStack) のログイベントを取得する方法 メモ

* LocalStack上で動くAmazon CloudWatch LogsのログイベントをAWS SAM CLIで動くLambdaから取得する方法をメモする。



## 構成
![sam-logs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586535/60dc4043-b508-5c3e-0bc5-619e2c43d37e.png)


## LocalStack 準備

* `docker-compose.yml`

  ```yaml
  version: "3.8"
  networks:
    container-link:
      name: docker.internal
  services:
    localstack:
      container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
      image: localstack/localstack
      ports:
        - "127.0.0.1:53:53"                # only required for Pro
        - "127.0.0.1:53:53/udp"            # only required for Pro
        - "127.0.0.1:443:443"              # only required for Pro
        - "127.0.0.1:4510-4530:4510-4530"  # only required for Pro
        - "127.0.0.1:4566:4566"
        - "127.0.0.1:4571:4571"
      environment:
        - SERVICES=${SERVICES- }
        - DEBUG=${DEBUG- }
        - DATA_DIR=${DATA_DIR- }
        - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- }
        - LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY- }  # only required for Pro
        - HOST_TMP_FOLDER=${TMPDIR:-/tmp/}localstack
        - DOCKER_HOST=unix:///var/run/docker.sock
      volumes:
        - "${TMPDIR:-/tmp}/localstack:/tmp/localstack"
        - "/var/run/docker.sock:/var/run/docker.sock"
      networks:
        - container-link
  ```

* 起動

  ```shell
  docker-compose up
  ```

  



## Cloudwatch logs準備

* AWS CLIを使用し、ロググループを作成する。

### 1. ロググループを作成する

* ロググループ`test-log-group`を作成する。

```shell
$ aws logs create-log-group --log-group-name test-log-group --endpoint-url=http://localhost:4566 --profile localstack
```



### 2. ログストリームを作成する

* ロググループ`test-log-group`にログストリーム`test-log-stream`を作成する。

```bash
$ aws logs create-log-stream --log-group-name test-log-group --log-stream-name test-log-stream --endpoint-url=http://localhost:4566 --profile localstack
```



### 3.  ログイベントをPUTする

#### 初回PUT時

* ログストリーム`test-log-stream`にログイベントをPUTする

```shell
$ aws logs put-log-events --log-group-name test-log-group --log-stream-name test-log-stream --log-events timestamp=1635589048,message='LOG EVENTS 1' --endpoint-url=http://localhost:4566 --profile localstack
```

#### 2回目以降のPUT時

* トークンを取得してから、ログイベントをPUTする。

```shell
$ TOKEN=$(aws logs describe-log-streams --log-group-name test-log-group --query 'logStreams[].uploadSequenceToken' --endpoint-url=http://localhost:4566 --profile localstack --output text)
$ aws logs put-log-events \
        --log-group-name test-log-group \
        --log-stream-name test-log-stream \
        --log-events timestamp=1635589048,message='LOG EVENTS 2' \
        --sequence-token ${TOKEN} --endpoint-url=http://localhost:4566 --profile localstack
```

### 4. ログイベントを確認する

```shell
$ aws logs get-log-events --log-group-name test-log-group --log-stream-name test-log-stream --endpoint-url=http://localhost:4566 --profile localstack
{
    "events": [
        {
            "timestamp": 1635589048,
            "message": "LOG EVENTS 1",
            "ingestionTime": 1635591467580
        },
        {
            "timestamp": 1635589048,
            "message": "LOG EVENTS 2",
            "ingestionTime": 1635591480764
        }
    ],
    "nextForwardToken": "f/00000000000000000000000000000000000000000000000000000001",
    "nextBackwardToken": "b/00000000000000000000000000000000000000000000000000000000"
}
```



## Lambda準備

### Lambda

* ひな形作成

  ```shell
  sam init
  ```

  ※ランタイムはPython3.8を選択。

* `template.yml`

  * Hello World Exampleを利用する。

    

* `requrements.txt`

  ```
  requests
  boto3
  ```

  ※CloudWatch logsアクセス用にboto3追加

  

* Lambda処理(`app.py`)修正

  ```python
  import json
  from boto3.session import Session
  from datetime import datetime
  
  
  # Cloudwatch logs 接続設定
  session = Session(
      aws_access_key_id='dummy',
      aws_secret_access_key='dummy',
      region_name='ap-northeast-1'
  )
  client = session.client(
      service_name='logs', 
      endpoint_url='http://localstack:4566'
  )
  
  
  def lambda_handler(event, context):
      # ロググループ名
      group_name="test-log-group"
      # ログストリーム取得
      streams = client.describe_log_streams(
          logGroupName="test-log-group",
          orderBy='LastEventTime',
          descending=True
      )
      response = {}
      response["group"] = group_name
      response["streams"]=[]
      for stream in streams['logStreams']:
          stream_name = stream['logStreamName']
          stream_response = {}
          stream_response["stream_name"] = stream_name
          # ログ取得
          logs = client.get_log_events(
              logGroupName=group_name,
              logStreamName=stream_name,
              startFromHead=True
          )
          body = logs['events']
          stream_response["logs"]=[]        
          for line in body:
              message = '[{}] {}'.format(datetime.fromtimestamp(int(str(line['timestamp'])[:10])), line['message'])
              stream_response["logs"].append(message)
          response["streams"].append(stream_response)
      return {
          "statusCode": 200,
          "body": json.dumps(response),
      }
  
  ```



## 動作確認

* ビルド

  ```
  sam build
  ```

* 実行

  ```
  sam local start-api --docker-network docker.internal
  ```

* リクエスト

  ```http
  GET /hello HTTP/1.1
  Host: localhost:3000
  Content-Type: application/json
  ```

* レスポンス

  ```json
  {
      "group": "test-log-group",
      "streams": [
          {
              "stream_name": "test-log-stream",
              "logs": [
                  "[2021-10-30 10:17:28] LOG EVENTS 1",
                  "[2021-10-30 10:17:28] LOG EVENTS 2"
              ]
          }
      ]
  }
  ```

  

## 参考情報

* [CloudWatchのログをBoto3(AWS SDK for Python)でローカルファイルに出力してみた](https://dev.classmethod.jp/articles/cloudwatch-logs-output2file/)