diff --git a/PetAdoptions/petfood-metric/Dockerfile b/PetAdoptions/petfood-metric/Dockerfile index 7f9ff77a..1457adb3 100644 --- a/PetAdoptions/petfood-metric/Dockerfile +++ b/PetAdoptions/petfood-metric/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM python:3.8-slim-buster +FROM --platform=linux/amd64 python:3.8-slim-buster WORKDIR /app diff --git a/PetAdoptions/petfood-metric/petfood-metric.py b/PetAdoptions/petfood-metric/petfood-metric.py index 03933e1b..be86fb04 100644 --- a/PetAdoptions/petfood-metric/petfood-metric.py +++ b/PetAdoptions/petfood-metric/petfood-metric.py @@ -1,7 +1,3 @@ -#!/usr/bin/env python # pylint: disable=C0103 - -"""Simple microservice to show Evidently features""" - import json import logging import os @@ -11,27 +7,17 @@ from aws_xray_sdk.core import patch_all, xray_recorder from flask import Flask, request - app = Flask(__name__) xray_recorder.configure(service='petfood-metric') patch_all() XRayMiddleware(app, xray_recorder) - -class StructuredMessage: # pylint: disable=R0903 - """Use to make JSON formatted logging work well for CWL""" - def __init__(self, message, /, **kwargs): - self.message = message - self.kwargs = kwargs - - def __str__(self): - return f'{self.message} - {self.kwargs}' - - -_ = StructuredMessage -logging.basicConfig(level=os.getenv('LOG_LEVEL', 20), format='%(message)s') -logger = logging.getLogger() - +logging.basicConfig( + level=os.getenv('LOG_LEVEL', logging.INFO), + format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) +logger = logging.getLogger(__name__) class EvidentlyProject: """Base for all Evidently interactions""" @@ -40,50 +26,49 @@ def __init__(self): self.client = boto3.client('evidently') self.project = os.getenv('EVIDENTLY_PROJECT', 'petfood') + @xray_recorder.capture('evidently_project_exists') def project_exists(self): """Returns False if the project does not currently exist""" - xray_recorder.begin_subsegment('evidently project_exists') try: - response = self.client.get_project(project=self.project) - logger.info(_('checking for evidently project', response=response)) - xray_recorder.end_subsegment() + self.client.get_project(project=self.project) + logger.info("Evidently project '%s' found", self.project) return True except self.client.exceptions.ResourceNotFoundException: - logger.warning(_('evidently project not found')) - xray_recorder.end_subsegment() - return None + logger.warning("Evidently project '%s' not found", self.project) + return False + @xray_recorder.capture('evidently_put_metric') def put_metric(self, entity_id, value): """Puts metric into Evidently""" data = json.dumps({ - 'userDetails': {'entityId': entity_id}, - 'details': {'donation': value} - }) + 'userDetails': {'entityId': entity_id}, + 'details': {'donation': value} + }) response = self.client.put_project_events( - events=[{'timestamp': time.time(), - 'data': data, - 'type': 'aws.evidently.custom'}], + events=[{ + 'timestamp': time.time(), + 'data': data, + 'type': 'aws.evidently.custom' + }], project=self.project ) - logger.warning(_('response to put_metric call', response=response)) - + logger.warning("Response to put_metric call: %s", response) @app.route('/metric//') def root_path(entity_id, value): """Base URL for our handler""" - logger.info(_('raw request headers', headers=request.headers)) + logger.info("Raw request headers: %s", request.headers) xray_recorder.begin_segment('petfood-metric') evidently = EvidentlyProject() - project = evidently.project_exists() - if not project: - return json.dumps({'statusCode': 404, 'body': 'evidently project not found'}) - evidently.put_metric(str(entity_id), float(value)) - # xray_recorder.end_segment() + if not evidently.project_exists(): + xray_recorder.end_segment() + return json.dumps({'statusCode': 404, 'body': 'Evidently project not found'}) + evidently.put_metric(entity_id, float(value)) + xray_recorder.end_segment() return json.dumps('ok') - @app.route('/status') def status_path(): - logger.info(_('raw request headers', headers=request.headers)) """Used for health checks""" + logger.info("Raw request headers: %s", request.headers) return json.dumps({'statusCode': 200, 'body': 'ok'}) diff --git a/PetAdoptions/petfood-metric/petfood-metric.py.orig b/PetAdoptions/petfood-metric/petfood-metric.py.orig new file mode 100644 index 00000000..03933e1b --- /dev/null +++ b/PetAdoptions/petfood-metric/petfood-metric.py.orig @@ -0,0 +1,89 @@ +#!/usr/bin/env python # pylint: disable=C0103 + +"""Simple microservice to show Evidently features""" + +import json +import logging +import os +import time +import boto3 +from aws_xray_sdk.ext.flask.middleware import XRayMiddleware +from aws_xray_sdk.core import patch_all, xray_recorder +from flask import Flask, request + + +app = Flask(__name__) +xray_recorder.configure(service='petfood-metric') +patch_all() +XRayMiddleware(app, xray_recorder) + + +class StructuredMessage: # pylint: disable=R0903 + """Use to make JSON formatted logging work well for CWL""" + def __init__(self, message, /, **kwargs): + self.message = message + self.kwargs = kwargs + + def __str__(self): + return f'{self.message} - {self.kwargs}' + + +_ = StructuredMessage +logging.basicConfig(level=os.getenv('LOG_LEVEL', 20), format='%(message)s') +logger = logging.getLogger() + + +class EvidentlyProject: + """Base for all Evidently interactions""" + + def __init__(self): + self.client = boto3.client('evidently') + self.project = os.getenv('EVIDENTLY_PROJECT', 'petfood') + + def project_exists(self): + """Returns False if the project does not currently exist""" + xray_recorder.begin_subsegment('evidently project_exists') + try: + response = self.client.get_project(project=self.project) + logger.info(_('checking for evidently project', response=response)) + xray_recorder.end_subsegment() + return True + except self.client.exceptions.ResourceNotFoundException: + logger.warning(_('evidently project not found')) + xray_recorder.end_subsegment() + return None + + def put_metric(self, entity_id, value): + """Puts metric into Evidently""" + data = json.dumps({ + 'userDetails': {'entityId': entity_id}, + 'details': {'donation': value} + }) + response = self.client.put_project_events( + events=[{'timestamp': time.time(), + 'data': data, + 'type': 'aws.evidently.custom'}], + project=self.project + ) + logger.warning(_('response to put_metric call', response=response)) + + +@app.route('/metric//') +def root_path(entity_id, value): + """Base URL for our handler""" + logger.info(_('raw request headers', headers=request.headers)) + xray_recorder.begin_segment('petfood-metric') + evidently = EvidentlyProject() + project = evidently.project_exists() + if not project: + return json.dumps({'statusCode': 404, 'body': 'evidently project not found'}) + evidently.put_metric(str(entity_id), float(value)) + # xray_recorder.end_segment() + return json.dumps('ok') + + +@app.route('/status') +def status_path(): + logger.info(_('raw request headers', headers=request.headers)) + """Used for health checks""" + return json.dumps({'statusCode': 200, 'body': 'ok'}) diff --git a/PetAdoptions/petfood-metric/requirements.txt b/PetAdoptions/petfood-metric/requirements.txt index d822d977..a49eb19d 100644 --- a/PetAdoptions/petfood-metric/requirements.txt +++ b/PetAdoptions/petfood-metric/requirements.txt @@ -1,4 +1,4 @@ aws-xray-sdk==2.9.0 -Flask==2.0.2 -boto3==1.20.21 +Flask==2.3.0 +boto3==1.34.64 gunicorn==20.1.0 diff --git a/PetAdoptions/petfood/Dockerfile b/PetAdoptions/petfood/Dockerfile index 2e4e52eb..f6978fa8 100644 --- a/PetAdoptions/petfood/Dockerfile +++ b/PetAdoptions/petfood/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM python:3.8-slim-buster +FROM --platform=linux/amd64 python:3.8-slim-buster WORKDIR /app @@ -10,4 +10,6 @@ RUN pip3 install -r requirements.txt COPY . . ENV FLASK_APP=petfood +ENV EVIDENTLY_PROJECT=petfood + CMD [ "python3", "-m" , "gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "--capture-output", "petfood:app"] diff --git a/PetAdoptions/petfood/petfood.py b/PetAdoptions/petfood/petfood.py old mode 100755 new mode 100644 index 8060f1e0..1873ae1e --- a/PetAdoptions/petfood/petfood.py +++ b/PetAdoptions/petfood/petfood.py @@ -1,5 +1,3 @@ -"""Simple microservice to show Evidently features""" - import json import logging import os @@ -9,7 +7,6 @@ from aws_xray_sdk.core import patch_all, xray_recorder from flask import Flask, request - app = Flask(__name__) plugins = ('EC2Plugin',) xray_recorder.configure(plugins=plugins, service='petfood') @@ -17,21 +14,12 @@ XRayMiddleware(app, xray_recorder) xray_recorder.begin_segment('petfood') - -class StructuredMessage: # pylint: disable=R0903 - """Use to make JSON formatted logging work well for CWL""" - def __init__(self, message, /, **kwargs): - self.message = message - self.kwargs = kwargs - - def __str__(self): - return f'{self.message} - {self.kwargs}' - - -_ = StructuredMessage -logging.basicConfig(level=os.getenv('LOG_LEVEL', 20), format='%(message)s') -logger = logging.getLogger() - +logging.basicConfig( + level=os.getenv('LOG_LEVEL', logging.INFO), + format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) +logger = logging.getLogger(__name__) class EvidentlyProject: """Base for all Evidently interactions""" @@ -42,18 +30,18 @@ def __init__(self): self.upsell_feature = 'petfood-upsell' self.upsell_text_feature = 'petfood-upsell-text' - @xray_recorder.capture('evidently project_exists') + @xray_recorder.capture('evidently_project_exists') def project_exists(self): """Returns False if the project does not currently exist""" try: - response = self.client.get_project(project=self.project) - logger.info(_('checking for evidently project', response=response)) + self.client.get_project(project=self.project) + logger.info("Evidently project '%s' found", self.project) return True except self.client.exceptions.ResourceNotFoundException: - logger.warning(_('evidently project not found')) - return None + logger.warning("Evidently project '%s' not found", self.project) + return False - @xray_recorder.capture('evidently get_upsell_evaluation') + @xray_recorder.capture('evidently_get_upsell_evaluation') def get_upsell_evaluation(self, entity_id): """Gets the feature evaluation for petfood-upsell""" try: @@ -67,10 +55,10 @@ def get_upsell_evaluation(self, entity_id): 'variation': response['variation'] } except self.client.exceptions.ResourceNotFoundException: - logger.warning(_('evidently feature ' + self.upsell_feature + ' not found for project')) + logger.warning("Evidently feature '%s' not found for project '%s'", self.upsell_feature, self.project) return return_default() - @xray_recorder.capture('evidently get_upsell_text') + @xray_recorder.capture('evidently_get_upsell_text') def get_upsell_text(self, entity_id): """Gets the feature evaluation for petfood-upsell-verbiage""" try: @@ -79,59 +67,47 @@ def get_upsell_text(self, entity_id): feature=self.upsell_text_feature, project=self.project ) - logger.info(_('evidently ' + self.upsell_text_feature, response=response)) + logger.info("Evidently feature '%s': %s", self.upsell_text_feature, response['value']['stringValue']) return response['value']['stringValue'] except self.client.exceptions.ResourceNotFoundException: - logger.warning(_('evidently feature ' + self.upsell_text_feature + ' not found for project')) + logger.warning("Evidently feature '%s' not found for project '%s'", self.upsell_text_feature, self.project) return 'Error getting upsell message - check that your feature exists in Evidently!' - @xray_recorder.capture('return_evidently_response') def return_evidently_response(evidently): """Create a response using an Evidently project""" - logger.info(_('building evidently response')) + logger.info("Building Evidently response") entity_id = str(random.randint(1, 100)) evaluation = evidently.get_upsell_evaluation(entity_id) - logger.warning(_('response from feature evaluation', evaluation=evaluation)) - response = json.dumps( - { - 'statusCode': 200, - 'message': evidently.get_upsell_text(entity_id), - 'variation': evaluation, - 'entityId': entity_id - } - ) - logger.warning(_('final response to request', response=response)) - return response - + logger.warning("Response from feature evaluation: %s", evaluation) + return json.dumps({ + 'statusCode': 200, + 'message': evidently.get_upsell_text(entity_id), + 'variation': evaluation, + 'entityId': entity_id + }) @xray_recorder.capture('return_default_response') def return_default(): """Returns the default response to the user""" - logger.warning(_('returning default response to the user')) - text = json.dumps( - { - 'message': 'Thank you for supporting our community!', - 'statusCode': 200 - } - ) - return text - + logger.warning("Returning default response to the user") + return json.dumps({ + 'message': 'Thank you for supporting our community!', + 'statusCode': 200 + }) @app.route('/') def root_path(): """Base URL for our handler""" - logger.info(_('raw request headers', headers=request.headers)) + logger.info("Raw request headers: %s", request.headers) evidently = EvidentlyProject() - project = evidently.project_exists() - if not project: + if not evidently.project_exists(): return return_default() else: return return_evidently_response(evidently) - @app.route('/status') def status_path(): """Used for health checks""" - logger.info(_('raw request headers', headers=request.headers)) + logger.info("Raw request headers: %s", request.headers) return json.dumps({'statusCode': 200, 'body': 'ok'}) diff --git a/PetAdoptions/petfood/policy.json b/PetAdoptions/petfood/policy.json new file mode 100644 index 00000000..8a22f79f --- /dev/null +++ b/PetAdoptions/petfood/policy.json @@ -0,0 +1,16 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "evidently:GetProject", + "evidently:PutProjectEvents", + "evidently:EvaluateFeature" + ], + "Resource": "*" + } + ] +} + diff --git a/PetAdoptions/petfood/requirements.txt b/PetAdoptions/petfood/requirements.txt index d822d977..a49eb19d 100644 --- a/PetAdoptions/petfood/requirements.txt +++ b/PetAdoptions/petfood/requirements.txt @@ -1,4 +1,4 @@ aws-xray-sdk==2.9.0 -Flask==2.0.2 -boto3==1.20.21 +Flask==2.3.0 +boto3==1.34.64 gunicorn==20.1.0