Distributed Tracing with AWS X-Ray in Flask Application

Distributed Tracing with AWS X-Ray in Flask Application

·

3 min read

Integrate AWS X-Ray to the Flask application for tracing

  • AWS X-Ray SDK for Python

    GitHub Repo

    Add library aws-xray-sdk to the requirements.txt

      aws-xray-sdk
    

    Run this command in a terminal to install the libraries for local development

      pip install -r requirements.txt
    
  • Add code in app.py to add X-Ray Flask middleware to instruct for X-RAY

      from aws_xray_sdk.core import xray_recorder
      from aws_xray_sdk.ext.flask.middleware import XRayMiddleware
    
      app = Flask(__name__)
    
      xray_url = os.getenv("AWS_XRAY_URL")
      xray_recorder.configure(service='Cruddur', dynamic_naming=xray_url)
      XRayMiddleware(app, xray_recorder)
    
  • Use AWS CLI to create a group for X-RAY

      $ aws xray create-group    --group-name "Cruddur"    --filter-expression "service(\"backend-flask\")"
      {
          "Group": {
              "GroupName": "Cruddur",
              "GroupARN": "arn:aws:xray:ap-southeast-2:461075076403:group/Cruddur/ELVUCCCTCVRE7VMACDBN3ST2HQ3NVW73EGPW6JUYMDJZH3VKJ3ZA",
              "FilterExpression": "service(\"backend-flask\")",
              "InsightsConfiguration": {
                  "InsightsEnabled": false,
                  "NotificationsEnabled": false
              }
          }
      }
    

    The group Cruddur is created. image

  • Create a sampling rule

    Add a file aws/json/xray.json

      {
          "SamplingRule": {
              "RuleName": "Cruddur",
              "ResourceARN": "*",
              "Priority": 9000,
              "FixedRate": 0.1,
              "ReservoirSize": 5,
              "ServiceName": "Cruddur",
              "ServiceType": "*",
              "Host": "*",
              "HTTPMethod": "*",
              "URLPath": "*",
              "Version": 1
          }
      }
    
      $ aws xray create-sampling-rule --cli-input-json file://aws/json/xray.json
      {
          "SamplingRuleRecord": {
              "SamplingRule": {
                  "RuleName": "Cruddur",
                  "RuleARN": "arn:aws:xray:ap-southeast-2:461075076403:sampling-rule/Cruddur",
                  "ResourceARN": "*",
                  "Priority": 9000,
                  "FixedRate": 0.1,
                  "ReservoirSize": 5,
                  "ServiceName": "backend-flask",
                  "ServiceType": "*",
                  "Host": "*",
                  "HTTPMethod": "*",
                  "URLPath": "*",
                  "Version": 1,
                  "Attributes": {}
              },
              "CreatedAt": "2023-03-20T07:37:58+00:00",
              "ModifiedAt": "2023-03-20T07:37:58+00:00"
          }
      }
    
  • Add daemon service to Docker Compose

      xray-daemon:
        image: "amazon/aws-xray-daemon"
        environment:
          AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}"
          AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}"
          AWS_REGION: "us-east-1"
        command:
          - "xray -o -b xray-daemon:2000"
        ports:
          - 2000:2000/udp
    

    Add two env vars to backend-flask in docker-compose.yml file

      AWS_XRAY_URL: "*4567-${GITPOD_WORKSPACE_ID}.${GITPOD_WORKSPACE_CLUSTER_HOST}*"
      AWS_XRAY_DAEMON_ADDRESS: "xray-daemon:2000"
    
  • Add segments to X-RAY

    The AWS X-Ray SDK for Python provides a predefined decorator that can be used to instrument Flask application endpoints. The decorator is called xray_recorder.capture() and it can be used to automatically trace your endpoint function with an X-Ray segment.

    Here's an example of how to use the xray_recorder.capture() decorator:

      from flask import Flask
      from aws_xray_sdk.core import xray_recorder
    
      app = Flask(__name__)
    
      @app.route('/example_endpoint')
      @xray_recorder.capture('example_endpoint')
      def example_endpoint():
         # Your code here
         return 'Hello, World!'
    

    In this example, we apply the xray_recorder.capture() decorator to our example_endpoint() function. We provide a name for the segment as an argument to the decorator.

    When the endpoint function is called, the xray_recorder.capture() decorator automatically creates a new X-Ray segment with the provided name, records the start and end times of the segment, and captures any subsegments or metadata associated with the endpoint's execution.

    The xray_recorder.capture() decorator can be a convenient way to add X-Ray tracing to your Flask application without having to manually create and manage X-Ray segments in your code.

    • Add the decorator to the home activity endpoint

        @app.route("/api/activities/home", methods=['GET'])
        @xray_recorder.capture('home_endpoint')
        def data_home():
            data = HomeActivities.run()
            return data, 200
      

      The segment home_endpoint is added. image

      • No need to call xray_recorder.begin_segment explicitely when using xray_recorder.capture() decorator

        It's worth noting that using begin_segment() and end_segment() can be more error-prone and less convenient than using the capture() decorator or the in_segment() context manager. These methods automatically manage the lifecycle of segments and subsegments for you, making it easier to trace the execution of your code without introducing potential bugs.

  • Add subsegments

    • You may use xray_recorder.begin_subsegment() to add a subsegment. But remember to use xray_recorder.end_subsegment() to close it. Otherwise it doesn't work.
    • Or alternatively, you may use xray_recorder.in_subsegment() context manager

      with xray_recorder.in_subsegment('subsegment_mock') as subsegment_mock:
        dict = {
            "now": now.isoformat(),
            "results-size": len(model['data'])
        }
        subsegment_mock.put_metadata('key', dict, 'namespace')
      

      The subsegment subsegment_mock is added. image