def test_schema_validation_no_namespace(metric, dimension): # GIVEN we don't add any namespace # WHEN we attempt to serialize a valid EMF object # THEN it should fail namespace validation with pytest.raises(SchemaValidationError, match=".*Namespace must be string"): with single_metric(**metric): pass
def lambda_handler(event, context): """Sample pure Lambda function Parameters ---------- event: dict, required API Gateway Lambda Proxy Input Format Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format # noqa: E501 context: object, required Lambda Context runtime methods and attributes Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------ API Gateway Lambda Proxy Output Format: dict Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html """ sums_values() async_http_ret = asyncio.run(async_tasks()) if "charge_id" in event: logger.structure_logs(append=True, payment_id="charge_id") global _cold_start if _cold_start: logger.debug("Recording cold start metric") metrics.add_metric(name="ColdStart", unit=MetricUnit.Count, value=1) metrics.add_dimension(name="function_name", value=context.function_name) _cold_start = False try: ip = requests.get("http://checkip.amazonaws.com/") metrics.add_metric(name="SuccessfulLocations", unit="Count", value=1) except requests.RequestException as e: # Send some context about this error to Lambda Logs logger.exception(e, exc_info=True) raise with single_metric(name="UniqueMetricDimension", unit="Seconds", value=1) as metric: metric.add_dimension(name="unique_dimension", value="for_unique_metric") resp = { "message": "hello world", "location": ip.text.replace("\n", ""), "async_http": async_http_ret } logger.info("Returning message to the caller") return { "statusCode": 200, "body": json.dumps(resp), }
def test_schema_validation_no_namespace(metric, dimension): # GIVEN we don't add any namespace # WHEN we attempt to serialize a valid EMF object # THEN it should fail namespace validation with pytest.raises(SchemaValidationError, match="Must contain a metric namespace."): with single_metric(**metric) as my_metric: my_metric.add_dimension(**dimension)
def test_schema_validation_incorrect_metric_unit(metric, dimension, namespace): # GIVEN we pass a metric unit that is not supported by CloudWatch metric["unit"] = "incorrect_unit" # WHEN we try adding a new metric # THEN it should fail metric unit validation with pytest.raises(MetricUnitError): with single_metric(**metric) as my_metric: my_metric.add_dimension(**dimension)
def test_schema_validation_incorrect_metric_value(metric, dimension, namespace): # GIVEN we pass an incorrect metric value (non-numeric) metric["value"] = "some_value" # WHEN we attempt to serialize a valid EMF object # THEN it should fail validation and raise SchemaValidationError with pytest.raises(MetricValueError): with single_metric(**metric): pass
def test_schema_no_namespace(metric, dimension): # GIVEN we add any metric or dimension # but no namespace # WHEN we attempt to serialize a valid EMF object # THEN it should fail validation and raise SchemaValidationError with pytest.raises(SchemaValidationError): with single_metric(**metric) as my_metric: my_metric.add_dimension(**dimension)
def test_all_possible_metric_units(metric, dimension, namespace): # GIVEN we add a metric for each metric unit supported by CloudWatch # where metric unit as MetricUnit key e.g. "Seconds", "BytesPerSecond" for unit in MetricUnit: metric["unit"] = unit.name # WHEN we iterate over all available metric unit keys from MetricUnit enum # THEN we raise no MetricUnitError nor SchemaValidationError with single_metric(namespace=namespace, **metric) as my_metric: my_metric.add_dimension(**dimension) # WHEN we iterate over all available metric unit keys from MetricUnit enum all_metric_units = [unit.value for unit in MetricUnit] for unit in all_metric_units: metric["unit"] = unit # e.g. "Seconds", "Bytes/Second" # THEN we raise no MetricUnitError nor SchemaValidationError with single_metric(namespace=namespace, **metric) as my_metric: my_metric.add_dimension(**dimension)
def test_exceed_number_of_dimensions(metric, namespace): # GIVEN we we have more dimensions than CloudWatch supports dimensions = [{"name": f"test_{i}", "value": "test"} for i in range(11)] # WHEN we attempt to serialize them into a valid EMF object # THEN it should fail validation and raise SchemaValidationError with pytest.raises(SchemaValidationError, match="must contain less than or equal to 9 items"): with single_metric(**metric, namespace=namespace) as my_metric: for dimension in dimensions: my_metric.add_dimension(**dimension)
def test_incorrect_metric_unit(metric, dimension, namespace): # GIVEN we pass a metric unit not supported by CloudWatch metric["unit"] = "incorrect_unit" # WHEN we attempt to add a new metric # THEN it should fail validation and raise MetricUnitError with pytest.raises(MetricUnitError): with single_metric(**metric) as my_metric: my_metric.add_dimension(**dimension) my_metric.add_namespace(**namespace)
def test_schema_incorrect_value(metric, dimension, namespace): # GIVEN we pass an incorrect metric value (non-number/float) metric["value"] = "some_value" # WHEN we attempt to serialize a valid EMF object # THEN it should fail validation and raise SchemaValidationError with pytest.raises(MetricValueError): with single_metric(**metric) as my_metric: my_metric.add_dimension(**dimension) my_metric.add_namespace(**namespace)
def test_namespace_var_precedence(monkeypatch, capsys, metric, dimension, namespace): # GIVEN we use POWERTOOLS_METRICS_NAMESPACE monkeypatch.setenv("POWERTOOLS_METRICS_NAMESPACE", "a_namespace") # WHEN creating a metric and explicitly set a namespace with single_metric(namespace=namespace, **metric) as my_metrics: my_metrics.add_dimension(**dimension) output = capture_metrics_output(capsys) # THEN namespace should match the explicitly passed variable and not the env var assert namespace == output["_aws"]["CloudWatchMetrics"][0]["Namespace"]
def test_single_metric_logs_one_metric_only(capsys, metric, dimension, namespace): # GIVEN we try adding more than one metric # WHEN using single_metric context manager with single_metric(namespace=namespace, **metric) as my_metric: my_metric.add_metric(name="second_metric", unit="Count", value=1) my_metric.add_dimension(**dimension) output = capture_metrics_output(capsys) expected = serialize_single_metric(metric=metric, dimension=dimension, namespace=namespace) # THEN we should only have the first metric added remove_timestamp(metrics=[output, expected]) assert expected == output
def test_exceed_number_of_dimensions(metric, namespace): # GIVEN we we have more dimensions than CloudWatch supports dimensions = [] for i in range(11): dimensions.append({"name": f"test_{i}", "value": "test"}) # WHEN we attempt to serialize them into a valid EMF object # THEN it should fail validation and raise SchemaValidationError with pytest.raises(SchemaValidationError): with single_metric(**metric) as my_metric: my_metric.add_namespace(**namespace) for dimension in dimensions: my_metric.add_dimension(**dimension)
def test_multiple_namespaces_exception(metric, dimension, namespace): # GIVEN we attempt to add multiple namespaces namespace_a = {"name": "OtherNamespace"} namespace_b = {"name": "AnotherNamespace"} # WHEN an EMF object can only have one # THEN we should raise UniqueNamespaceError exception with pytest.raises(UniqueNamespaceError): with single_metric(**metric) as my_metric: my_metric.add_dimension(**dimension) my_metric.add_namespace(**namespace) my_metric.add_namespace(**namespace_a) my_metric.add_namespace(**namespace_b)
def test_namespace_env_var(monkeypatch, capsys, metric, dimension, namespace): # GIVEN POWERTOOLS_METRICS_NAMESPACE is set monkeypatch.setenv("POWERTOOLS_METRICS_NAMESPACE", namespace) # WHEN creating a metric without explicitly adding a namespace with single_metric(**metric) as my_metric: my_metric.add_dimension(**dimension) output = capture_metrics_output(capsys) expected = serialize_single_metric(metric=metric, dimension=dimension, namespace=namespace) # THEN we should add a namespace using POWERTOOLS_METRICS_NAMESPACE env var value remove_timestamp(metrics=[output, expected]) assert expected == output
def test_service_env_var(monkeypatch, capsys, metric, namespace): # GIVEN we use POWERTOOLS_SERVICE_NAME monkeypatch.setenv("POWERTOOLS_SERVICE_NAME", "test_service") # WHEN creating a metric without explicitly adding a dimension with single_metric(**metric, namespace=namespace): pass output = capture_metrics_output(capsys) expected_dimension = {"name": "service", "value": "test_service"} expected = serialize_single_metric(metric=metric, dimension=expected_dimension, namespace=namespace) # THEN a metric should be logged using the implicitly created "service" dimension remove_timestamp(metrics=[output, expected]) assert expected == output
def test_single_metric_with_service(capsys, metric, dimension): # GIVEN we pass namespace parameter to single_metric # WHEN creating a metric with single_metric(**metric, namespace="test_service") as my_metrics: my_metrics.add_dimension(**dimension) output = json.loads(capsys.readouterr().out.strip()) expected = serialize_single_metric(metric=metric, dimension=dimension, namespace={"name": "test_service"}) remove_timestamp(metrics=[output, expected]) # Timestamp will always be different # THEN namespace should match value passed as service assert expected["_aws"] == output["_aws"]
def test_single_metric_one_metric_only(capsys, metric, dimension, namespace): # GIVEN we attempt to add more than one metric # WHEN using single_metric context manager with single_metric(**metric) as my_metric: my_metric.add_metric(name="second_metric", unit="Count", value=1) my_metric.add_metric(name="third_metric", unit="Seconds", value=1) my_metric.add_dimension(**dimension) my_metric.add_namespace(**namespace) output = json.loads(capsys.readouterr().out.strip()) expected = serialize_single_metric(metric=metric, dimension=dimension, namespace=namespace) remove_timestamp(metrics=[output, expected]) # Timestamp will always be different # THEN we should only have the first metric added assert expected["_aws"] == output["_aws"]
def test_namespace_var_precedence(monkeypatch, capsys, metric, dimension, namespace): # GIVEN we use POWERTOOLS_METRICS_NAMESPACE monkeypatch.setenv("POWERTOOLS_METRICS_NAMESPACE", namespace["name"]) # WHEN creating a metric and explicitly set a namespace with single_metric(**metric, namespace=namespace["name"]) as my_metrics: my_metrics.add_dimension(**dimension) monkeypatch.delenv("POWERTOOLS_METRICS_NAMESPACE") output = json.loads(capsys.readouterr().out.strip()) expected = serialize_single_metric(metric=metric, dimension=dimension, namespace=namespace) remove_timestamp(metrics=[output, expected]) # Timestamp will always be different # THEN namespace should match the explicitly passed variable and not the env var assert expected["_aws"] == output["_aws"]
def test_namespace_env_var(monkeypatch, capsys, metric, dimension, namespace): # GIVEN we use POWERTOOLS_METRICS_NAMESPACE monkeypatch.setenv("POWERTOOLS_METRICS_NAMESPACE", namespace["name"]) # WHEN creating a metric but don't explicitly # add a namespace with single_metric(**metric) as my_metrics: my_metrics.add_dimension(**dimension) monkeypatch.delenv("POWERTOOLS_METRICS_NAMESPACE") output = json.loads(capsys.readouterr().out.strip()) expected = serialize_single_metric(metric=metric, dimension=dimension, namespace=namespace) remove_timestamp(metrics=[output, expected]) # Timestamp will always be different # THEN we should add a namespace implicitly # with the value of POWERTOOLS_METRICS_NAMESPACE env var assert expected["_aws"] == output["_aws"]