diff --git a/orders/src/create_order/main.py b/orders/src/create_order/main.py index f1e8c24..c291296 100644 --- a/orders/src/create_order/main.py +++ b/orders/src/create_order/main.py @@ -17,6 +17,8 @@ from aws_requests_auth.boto_utils import BotoAWSRequestsAuth from aws_lambda_powertools.tracing import Tracer # pylint: disable=import-error from aws_lambda_powertools.logging.logger import Logger # pylint: disable=import-error +from aws_lambda_powertools import Metrics # pylint: disable=import-error +from aws_lambda_powertools.metrics import MetricUnit # pylint: disable=import-error ENVIRONMENT = os.environ["ENVIRONMENT"] @@ -31,6 +33,7 @@ table = dynamodb.Table(TABLE_NAME) # pylint: disable=invalid-name,no-member logger = Logger() # pylint: disable=invalid-name tracer = Tracer() # pylint: disable=invalid-name +metrics = Metrics(namespace="ecommerce.orders") # pylint: disable=invalid-name with open(SCHEMA_FILE) as fp: @@ -232,6 +235,7 @@ def store_order(order: dict) -> None: table.put_item(Item=order) +@metrics.log_metrics(raise_on_empty_metrics=False) @logger.inject_lambda_context @tracer.capture_lambda_handler def handler(event, _): @@ -290,25 +294,11 @@ def handler(event, _): "orderId": order["orderId"], "order": order }) - # Generate custom metrics using the Embedded Metric Format - # See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html - print(json.dumps({ - "orderCreatedTotal": order["total"], - "orderCreated": 1, - "environment": ENVIRONMENT, - "_aws": { - # Timestamp is in milliseconds - "Timestamp": int(datetime.datetime.now().timestamp()*1000), - "CloudWatchMetrics": [{ - "Namespace": "ecommerce.orders", - "Dimensions": [["environment"]], - "Metrics": [ - {"Name": "orderCreatedTotal"}, - {"Name": "orderCreated"} - ] - }] - } - })) + + # Add custom metrics + metrics.add_dimension(name="environment", value=ENVIRONMENT) + metrics.add_metric(name="orderCreated", unit=MetricUnit.Count, value=1) + metrics.add_metric(name="orderCreatedTotal", unit=MetricUnit.Count, value=order["total"]) return { "success": True, diff --git a/orders/src/create_order/requirements.txt b/orders/src/create_order/requirements.txt index cdc2773..07fe90b 100644 --- a/orders/src/create_order/requirements.txt +++ b/orders/src/create_order/requirements.txt @@ -1,4 +1,4 @@ -aws-lambda-powertools==0.9.3 +aws-lambda-powertools==1.0.1 aws_requests_auth boto3 jsonschema==3.2.0 diff --git a/orders/src/on_events/main.py b/orders/src/on_events/main.py index 4aa81aa..39943b8 100644 --- a/orders/src/on_events/main.py +++ b/orders/src/on_events/main.py @@ -3,13 +3,14 @@ """ -import datetime -import json +from collections import defaultdict import os -from typing import List, Optional, Union +from typing import List, Optional import boto3 from aws_lambda_powertools.tracing import Tracer # pylint: disable=import-error from aws_lambda_powertools.logging.logger import Logger # pylint: disable=import-error +from aws_lambda_powertools import Metrics # pylint: disable=import-error +from aws_lambda_powertools.metrics import MetricUnit # pylint: disable=import-error ENVIRONMENT = os.environ["ENVIRONMENT"] @@ -20,36 +21,7 @@ table = dynamodb.Table(TABLE_NAME) # pylint: disable=invalid-name,no-member logger = Logger() # pylint: disable=invalid-name tracer = Tracer() # pylint: disable=invalid-name - - -@tracer.capture_method -def log_metrics(metric_names: Union[str, List[str]], metric_values: Union[int, List[int]]) -> None: - """ - Log custom metrics - """ - - if isinstance(metric_names, str): - metric_names = [metric_names] - if isinstance(metric_values, int): - metric_values = [metric_values] - - assert len(metric_names) <= len(metric_values) - - metrics = {metric_names[i]: metric_values[i] for i in range(len(metric_names))} - metrics["environment"] = ENVIRONMENT - metrics["_aws"] = { - # Timestamp is in milliseconds - "Timestamp": int(datetime.datetime.now().timestamp()*1000), - "CloudWatchMetrics": [{ - "Namespace": "ecommerce.orders", - "Dimensions": [["environment"]], - "Metrics": [ - {"Name": name} for name in metric_names - ] - }] - } - - print(json.dumps(metrics)) +metrics = Metrics(namespace="ecommerce.orders") # pylint: disable=invalid-name @tracer.capture_method @@ -97,6 +69,7 @@ def update_order(order_id: str, status: str, products: Optional[List[dict]] = No ) +@metrics.log_metrics @logger.inject_lambda_context @tracer.capture_lambda_handler def handler(event, _): @@ -106,6 +79,8 @@ def handler(event, _): order_ids = event["resources"] + metrics_data = defaultdict(int) + for order_id in order_ids: logger.info({ "message": "Got event of type {} from {} for order {}".format(event["detail-type"], event["source"], order_id), @@ -116,10 +91,10 @@ def handler(event, _): tracer.put_annotation("orderId", order_id) if event["source"] == "ecommerce.warehouse": if event["detail-type"] == "PackageCreated": - log_metrics("orderPackaged", 1) + metrics_data["orderPackaged"] += 1 update_order(order_id, "PACKAGED", event["detail"]["products"]) elif event["detail-type"] == "PackagingFailed": - log_metrics("orderFailed", 1) + metrics_data["orderFailed"] += 1 update_order(order_id, "PACKAGING_FAILED") else: logger.warning({ @@ -130,10 +105,10 @@ def handler(event, _): }) elif event["source"] == "ecommerce.delivery": if event["detail-type"] == "DeliveryCompleted": - log_metrics("orderFulfilled", 1) + metrics_data["orderFulfilled"] += 1 update_order(order_id, "FULFILLED") elif event["detail-type"] == "DeliveryFailed": - log_metrics("orderFailed", 1) + metrics_data["orderFailed"] += 1 update_order(order_id, "DELIVERY_FAILED") else: logger.warning({ @@ -149,3 +124,8 @@ def handler(event, _): "eventType": event["detail-type"], "orderId": order_id }) + + # Add custom metrics + metrics.add_dimension(name="environment", value=ENVIRONMENT) + for key, value in metrics_data.items(): + metrics.add_metric(name=key, unit=MetricUnit.Count, value=value) \ No newline at end of file diff --git a/orders/src/on_events/requirements.txt b/orders/src/on_events/requirements.txt index a3ad852..5dbd7d9 100644 --- a/orders/src/on_events/requirements.txt +++ b/orders/src/on_events/requirements.txt @@ -1,2 +1,2 @@ -aws-lambda-powertools==0.9.3 +aws-lambda-powertools==1.0.1 boto3 \ No newline at end of file diff --git a/orders/tests/unit/test_on_events.py b/orders/tests/unit/test_on_events.py index e87658c..9986f03 100644 --- a/orders/tests/unit/test_on_events.py +++ b/orders/tests/unit/test_on_events.py @@ -21,53 +21,6 @@ def order(get_order): return get_order() -def test_log_metrics(lambda_module, capsys): - """ - Test log_metrics() - """ - - lambda_module.log_metrics("METRIC_NAME", 100) - captured = capsys.readouterr() - metrics = json.loads(captured.out) - assert "_aws" in metrics - assert "CloudWatchMetrics" in metrics["_aws"] - assert len(metrics["_aws"]["CloudWatchMetrics"]) == 1 - assert metrics["_aws"]["CloudWatchMetrics"][0] == { - "Namespace": "ecommerce.orders", - "Dimensions": [["environment"]], - "Metrics": [ - {"Name": "METRIC_NAME"} - ] - } - assert "METRIC_NAME" in metrics - assert metrics["METRIC_NAME"] == 100 - - -def test_log_metrics_multiple(lambda_module, capsys): - """ - Test log_metrics() with multiple metrics - """ - - lambda_module.log_metrics(["METRIC_NAME1", "METRIC_NAME2"], [100, 200]) - captured = capsys.readouterr() - metrics = json.loads(captured.out) - assert "_aws" in metrics - assert "CloudWatchMetrics" in metrics["_aws"] - assert len(metrics["_aws"]["CloudWatchMetrics"]) == 1 - assert metrics["_aws"]["CloudWatchMetrics"][0] == { - "Namespace": "ecommerce.orders", - "Dimensions": [["environment"]], - "Metrics": [ - {"Name": "METRIC_NAME1"}, - {"Name": "METRIC_NAME2"} - ] - } - assert "METRIC_NAME1" in metrics - assert "METRIC_NAME2" in metrics - assert metrics["METRIC_NAME1"] == 100 - assert metrics["METRIC_NAME2"] == 200 - - def test_update_order(lambda_module): """ test update_order() diff --git a/shared/tests/integ/fixtures.py b/shared/tests/integ/fixtures.py index 069022b..b8e858a 100644 --- a/shared/tests/integ/fixtures.py +++ b/shared/tests/integ/fixtures.py @@ -64,7 +64,7 @@ def get_signature_key(key, dateStamp, regionName, serviceName): return {'x-amz-date':amzdate, 'Authorization':authorization_header} - def _listener(service_name: str, gen_function: Callable[[None], None], test_function: Optional[Callable[[dict], bool]]=None, wait_time: int=8): + def _listener(service_name: str, gen_function: Callable[[None], None], test_function: Optional[Callable[[dict], bool]]=None, wait_time: int=15): """ Listener fixture function """