Lambdaを使ってS3に上げたログをDynamoDBに自動登録

#やりたいこと
S3にログデータをアップするとLambdaがDynamoDBに自動登録してくれる。

#やること
主なフェーズとしては以下の流れです。順にまとめていきます。

1. S3バケットの作成
2. IAMロールの作成
3. DynamoDBテーブルの作成
4. Lambda関数作成
5. テストデータでの動作確認

なお今回は、名前と年齢のスペース区切りデータを処理する関数を書きます。

```input.txt
alice 20
bob 31
```

超簡略化してますが、取得するデータやテーブルに登録するキーを変更すれば色々と応用可能ですね。



##1. S3バケットの作成
S3のコンソールを開き、「バケットを作成する」から、ログデータをアップロードするためのバケットを作成しました。
データは暗号化を行っておきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/254738/3c276da6-c551-7547-824d-baa2750bf259.png)

##2. IAMロールの作成
新しいIAMロールを作成し、以下のポリシーをアタッチしました。

- AmazonDynamoDBFullAccess
- AmazonS3ReadOnlyAccess
- CloudWatchLogsFullAccess

![image.png](https://qiita-image-store.s3.amazonaws.com/0/254738/4c4ad7d1-3a41-d3e8-f073-99ecc325ba3c.png)

##3. DynamoDBテーブルの作成
DynamoDBのコンソールを開き、テーブルの作成から新しいDynamoDBテーブルを作成しました。
プライマリキーには**name/文字列**を指定しました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/254738/50cb0357-a593-54b1-ae3f-2dfdebb93e6f.png)

##4. Lambda関数作成
準備ができたので、いよいよLambda関数を作成していきます。

Lambdaのコンソールから「関数の作成」をクリックします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/254738/db13a21f-850c-31aa-8f77-bceca6c500b1.png)

「一から作成」を選び、必要な設定を入力し「関数の作成」をクリックします。

- 名前: 関数の名前を入力
- ランタイム: Python 3.6を選択
- 既存のロール: 2で作成したIAMロールを選択

![image.png](https://qiita-image-store.s3.amazonaws.com/0/254738/76dc73bd-f498-21cc-7767-00070d0820cc.png)

設定ページが表示されたら、まずトリガを設定します。
左側のペインから「S3」を選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/254738/dc65efb0-1a03-9cba-6599-f56116c24e3a.png)

追加が完了したら、追加された「S3」をクリックして、バケット名とトリガとして「PUT」を選択します。

次に関数名をクリックして、コードの入力画面に進みます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/254738/b072cf77-b728-6338-8698-7065256a40a8.png)

以下のコードを記述して「保存」します。

```script.py
### This lambda function parses text data for each s3 put event
### and register these to assigned dynamo db.
### Runtime: python 3.6

import boto3
import json
import urllib.parse
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
 
dynamodb = boto3.client('dynamodb')
s3 = boto3.client('s3')

def lambda_handler(event, context):
    #target dynamo table name
    dynamotable = 'YOUR_DYNAMO_TABLE_NAME'
    
    #get bucket name and file name by s3 put event
    bucket = event['Records'][0]['s3']['bucket']['name']
    filename = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

    #get uploaded text file contents
    try: 
        res = s3.get_object(Bucket=bucket, Key=filename)
    except Exception as e:
         return logger.error("Failed to get uploaded file: {}".format(e))

    # file type check
    if (res['ContentType'] == 'text/plain'):
        # get body
        body = res['Body'].read()
        bodystr = body.decode('UTF-8')
        lines = bodystr.split('\r\n') # for windows line endings
        
        #get items of each line
        for item in lines:
            data = item.split(' ')
            name = data[0]
            age = data[1]
            
            #register data to the dynamo db
            try:
                dynamodb.put_item(
                TableName = dynamotable,
                Item = {'name':{'S':name},'age':{'N':age}},
                Expected = {'name':{'Exists':False}}
                )
            except Exception as e:
                print(e)
    else:
        logger.error('Invalid File Type: ' + res['ContentType'])
```

##5. テストデータでの動作確認
以下のテキストデータをS3バケットにアップロードして、dynamoDBへ自動で登録されるか確認します。

```input.txt
alice 20
bob 31
```

いつもの手順でバケットにアップロードします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/254738/e3f4ee2f-ab31-8055-5707-88654f485804.png)

DynamoDBのテーブルを更新してみます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/254738/90f61479-ef6e-ba72-1abf-25aea34732f6.png)

追加されました！

アップロードのテストでうまくいかなかったときは、コード内でバケット名やファイル名を直接指定して、
Lambda関数のテスト機能を使ってデバッグするのが楽でした。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/254738/613ec29c-dc3b-b8f8-4916-60d08e19fbc8.png)

あとは、CloudWatchのイベントを見ると、ちゃんとログが吐かれているのがわかります。
画像ファイルなどをアップロードすると、エラーログが出力できます。

#まとめ
テキストデータをパースして、DBに登録できるようになりました。
テキストのフォーマットに合わせて区切り文字や取得するデータの位置などを変えれば、応用が効きます。
複数ファイルアップロードした時に、並列処理できるようなエンハンスもしてみたいと思います。
設定いじって、Pythonのコード書くだけで実現可能というのは、サーバーレスの恩恵を感じますね。

#参考サイト

S3をトリガーとするLambdaの冪等性をDynamoDBで実現してみた
https://dev.classmethod.jp/cloud/aws/lambda-dynamodb/

AWS Lambda（Python 3）でS3にあるファイルを読み込む
https://qiita.com/KoyanagiHitoshi/items/7b306b0f80806dfe2f61

LmabdaからS3のファイルを読み込む
https://www.noobs.tokyo/entry/2018/10/08/201938