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
Beispiel #2
0
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),
    }
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #7
0
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"]