Skip to content
This repository has been archived by the owner on Jul 18, 2024. It is now read-only.

Commit

Permalink
orders: switch to Lambda Powertools for custom metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
nmoutschen committed Jul 9, 2020
1 parent 0168bfc commit c191f62
Show file tree
Hide file tree
Showing 6 changed files with 29 additions and 106 deletions.
28 changes: 9 additions & 19 deletions orders/src/create_order/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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:
Expand Down Expand Up @@ -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, _):
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion orders/src/create_order/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
aws-lambda-powertools==0.9.3
aws-lambda-powertools==1.0.1
aws_requests_auth
boto3
jsonschema==3.2.0
Expand Down
54 changes: 17 additions & 37 deletions orders/src/on_events/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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
Expand Down Expand Up @@ -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, _):
Expand All @@ -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),
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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)
2 changes: 1 addition & 1 deletion orders/src/on_events/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
aws-lambda-powertools==0.9.3
aws-lambda-powertools==1.0.1
boto3
47 changes: 0 additions & 47 deletions orders/tests/unit/test_on_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion shared/tests/integ/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
Expand Down

0 comments on commit c191f62

Please sign in to comment.