AWS X-RayでLambdaとAWSリソースをトレースする（Python）

X-Rayをちゃんと使ってみてなかったので、調べて使ってみました。
ブラックボックスピタゴラスイ◯ッチやめよう。

- [X-Rayのトレース処理を追加](#x-rayのトレース処理を追加)
- [Annotationを使ってみる](#annotationを使ってみる)
- [メタデータを使ってみる](#メタデータを使ってみる)
- [AWSサービスの処理を追跡してみる](#awsサービスの処理を追跡してみる)
- [非同期処理をトレースしたい](#非同期処理をトレースしたい)

今回はAPI GatewayをイベントトリガーにしてLambdaファンクションをキックするようにします。

ソースたちはここに入っています。
https://github.com/yKarakita/lambda_xray

## 環境など
- Servereless Framework 1.22.0
- python3.6
- aws-xray-sdk 0.95


### サンプルファンクション
これを元にしてX-Rayを導入してみる。

```handler.py
import json

def hello(event, context):

    body = {
        "message": "Hello!"
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response
```

## X-Rayのトレース処理を追加

```handler.py
import json
from aws_xray_sdk.core import xray_recorder


def hello(event, context):
    xray_recorder.begin_subsegment('response hello')

    body = {
        "message": "Hello!"
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    xray_recorder.end_subsegment()

    return response
```

### デコレータを使う方法
デコレータを使っても同様のことができるのでこちらのほうがシンプルでいいかもしれない。

```handler.py
...

@xray_recorder.capture('response hello')
def hello(event, context):
    # ... some code
```

### デプロイ
X-RayのSDKはLambdaにデフォルトで組み込まれていないのでをデプロイパッケージに含める必要があります。
今回はServerlessのプラグイン`serverless-python-requirements`を使用して`$ sls deploy`時にrequirements.txtを読み込んで動的にデプロイパッケージを生成しています。

```requirements.txt
aws-xray-sdk==0.95
```

```bash
$ sls deploy
```
### APIをキック

```
$ curl https://<API Gatewayエンドポイント>/dev/hello
{"message": "Hello!"}
```
X-Rayコンソールから確認するとこんな感じ。
<img width="1083" alt="スクリーンショット 2018-02-24 09.22.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/c196233f-7838-f353-29f3-e7d1d4b49b16.png">

<img width="696" alt="スクリーンショット 2018-02-24 09.22.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/8ed47692-7451-30d5-2851-e13ec09e07e7.png">

うまくトレースできてました。



## Annotationを使ってみる

Annotationを使うと、Subsegmentを任意のキーで絞込みできるようになる。

書き方はこんな感じ。

```python
    subsegment.put_annotation('key', 'value')
```


```handler.py
...

@xray_recorder.capture('response hello')
def hello(event, context):
    # ... some code

    status_code = 200

    subsegment = xray_recorder.current_subsegment()
    subsegment.put_annotation('response_status_code', status_code)
```
コンソールからはこんな感じで確認できる。

<img width="555" alt="スクリーンショット 2018-02-24 11.31.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/996e0235-381c-1385-03b2-1ebf3f384886.png">



[フィルタ式を使用した AWS X-Ray コンソールでのトレースの検索
](https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-console-filters.html)

X-Rayコンソールの入力フィールドに`annotation.response_status_code = 200`といれるとannotationの`response_status_code`に200を入れてputしたものだけを絞りこめる。

<img width="278" alt="スクリーンショット 2018-02-24 21.42.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/b213f4de-f1f5-86da-df90-1efcec2e53bb.png">

結果のグループ化もAnnootationが使える。
<img width="1483" alt="スクリーンショット 2018-02-24 21.43.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/f4abe078-8ed2-6c17-7197-c40fcc3895c7.png">

これは便利そう！

## メタデータを使ってみる

サブセグメントにメタデータを追加することができます。
書き方はこんな感じ

```python
    subsegment.put_metadata('key', dict, 'namespace')
```

```handler.py
import uuid

from aws_xray_sdk.core import xray_recorder

@xray_recorder.capture('response hello')
def hello(event, context):
    # ... some code

    subsegment.put_metadata('key01', {'id': str(uuid.uuid4())}, 'namespace01')
    subsegment.put_metadata('key02', {'id': str(uuid.uuid4())}, 'namespace01')
```
<img width="574" alt="スクリーンショット 2018-02-24 11.23.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/33d135c8-2856-68fa-d6cb-f858fbf12f67.png">

うん、必要になったら使ってみようかな。

## AWSサービスの処理を追跡してみる

> https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-x-ray.html
> boto3 (セッションを使用している場合は botocore) にパッチを適用して、AWS のサービスにアクセスするクライアントを作成すると自動的に X-Ray によって追跡されるようにできます。


便利そう。

こんな感じで`boto3`にX-Rayのパッチを適用してあげることで、AWSリソースのトレースができるようになるみたいです。

```python
import boto3
from aws_xray_sdk.core import patch

patch(['boto3'])
```

API Gateway -> Lambda -> S3 Putのサンプル。

```handler.py
import json
import uuid
import os

import boto3
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch

patch(['boto3'])

s3_client = boto3.client('s3')

bucket_name = os.environ['S3_BUCKET']


@xray_recorder.capture('handler')
def hello(event, context):
    keyword = event['queryStringParameters']['keyword']

    put_object_into_s3(bucket_name=bucket_name, key=str(uuid.uuid4()), body=keyword)

    response = {
        "statusCode": 200,
        "body": json.dumps({'message': 'ok'})
    }

    return response


@xray_recorder.capture('put_object')
def put_object_into_s3(bucket_name, key, body):
    subsegment = xray_recorder.current_subsegment()
    response = s3_client.put_object(Bucket=bucket_name, Key=key, Body=body)
    status_code = response['ResponseMetadata']['HTTPStatusCode']
    subsegment.put_annotation('put_response', status_code)
```

```bash
$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?keyword=hogeeeeeeeeeeee
{"message": "ok"}
```
詳細とサービスマップはこんな感じ。うまくいってるっぽいすね。

<img width="1143" alt="スクリーンショット 2018-02-24 20.52.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/410da44f-fbdb-3c53-a101-6c361e817df0.png">

<img width="803" alt="スクリーンショット 2018-02-24 20.53.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/58ea7f7f-15b8-904a-0e23-60e44e2611d2.png">

### 非同期処理をトレースしたい
こんな感じの非同期処理だとどうだろ。

```
API Gateway
↓
Lambda(SNSにPubした後HTTPレスポンスを返す）
↓
SNS
↓
Lambda(SNSからメッセージをSubしてS3にPutする)
↓
S3
```
やってみます。
何も考えずにすべての関数にX-Rayデコレータをつけてみる。
まずAPI Gatewayからイベントを受け取ってSNSにPubするファンクション。

```handler.py
import json
import os

import boto3
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch

patch(['boto3'])

sns = boto3.resource('sns', 'ap-northeast-1')
sns_client = boto3.client('sns')

topic_arn = os.environ['SNS_TOPIC_ARN']

@xray_recorder.capture('handler')
def hello(event, context):
    keyword = event['queryStringParameters']['keyword']

    sns_client.publish(
        TopicArn=topic_arn,
        Message=keyword,
        Subject='object_body'
    )

    response = {
        "statusCode": 200,
        "body": json.dumps({'message': 'ok'})
    }

    return response
```

そしてSNSをSubして、S３にPutするファンクション。

```put_s3.py
import uuid
import os

import boto3
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch

patch(['boto3'])

s3_client = boto3.client('s3')

bucket_name = os.environ['S3_BUCKET']


@xray_recorder.capture('put_s3_handler')
def handler(event, context):
    subscribed_data = event['Records'][0]['Sns']
    body = subscribed_data['Message']

    put_object_into_s3(bucket_name, str(uuid.uuid4()), body)

@xray_recorder.capture('put_object')
def put_object_into_s3(bucket_name, bucket_key, body):
    subsegment = xray_recorder.current_subsegment()
    response = s3_client.put_object(Bucket=bucket_name, Key=bucket_key, Body=body)
    status_code = response['ResponseMetadata']['HTTPStatusCode']
    subsegment.put_annotation('put_response', status_code)
```

```bash
$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?keyword=hogeeeeeeeeeeee
{"message": "ok"}
```

実行するとこんな感じでSNSより前とそれ以降が分かれてしまい、うまくトレースできませんでした。
<img width="819" alt="スクリーンショット 2018-02-24 21.01.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/dbcc29e7-dfdb-3a9c-1d6d-6e65fc37aa0f.png">

調べてみたところ現時点でこれをうまくトレースさせたい場合は、SNSとその次のLambdaの間に、もうひとつ別のLambdaを用意する必要があるとのことでした。
Lambda -> SNS -> Lambda -> Lambda

最初のLambdaで生成されたTrace IDをSNSで中継役のLambdaに渡して、そいつがTrace IDを含めて次のLambdaを起動すればいけるみたい。

こちらの記事で詳しく紹介されています。
[AWS X-Rayで非同期メッセージをトレースする](https://qiita.com/yoshiyoshifujii/items/df18ff90b6332e0a56c6)


## まとめ
入れておこう。


## 参考
- [AWS X-Ray の概念](https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-concepts.html#xray-concepts-traces)
- [フィルタ式を使用した AWS X-Ray コンソールでのトレースの検索
](https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-console-filters.html)
- [Lambda ベースのアプリケーションのトラブルシューティング](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-x-ray.html#using-x-ray)
- [aws/aws-xray-sdk-python](https://github.com/aws/aws-xray-sdk-python)
- [aws-xray-sdkドキュメント](https://docs.aws.amazon.com/xray-sdk-for-python/latest/reference/index.html)