Skip to content

Commit

Permalink
chore(tests): refactor E2E logger to ease maintenance, writing tests …
Browse files Browse the repository at this point in the history
…and parallelization (#1460)
  • Loading branch information
heitorlessa authored Aug 19, 2022
1 parent 7567c84 commit 6930b42
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 200 deletions.
9 changes: 9 additions & 0 deletions aws_lambda_powertools/shared/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,12 @@
XRAY_SDK_CORE_MODULE: str = "aws_xray_sdk.core"

IDEMPOTENCY_DISABLED_ENV: str = "POWERTOOLS_IDEMPOTENCY_DISABLED"

LOGGER_LAMBDA_CONTEXT_KEYS = [
"function_arn",
"function_memory_size",
"function_name",
"function_request_id",
"cold_start",
"xray_trace_id",
]
44 changes: 31 additions & 13 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ mypy-boto3-lambda = "^1.24.0"
mypy-boto3-xray = "^1.24.0"
mypy-boto3-s3 = { version = "^1.24.0", python = ">=3.7" }
mypy-boto3-cloudformation = { version = "^1.24.0", python = ">=3.7" }
mypy-boto3-logs = { version = "^1.24.0", python = ">=3.7" }
types-requests = "^2.28.8"
typing-extensions = { version = "^4.3.0", python = ">=3.7" }
python-snappy = "^0.6.1"
Expand Down
25 changes: 25 additions & 0 deletions tests/e2e/logger/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest

from tests.e2e.logger.infrastructure import LoggerStack
from tests.e2e.utils.infrastructure import deploy_once


@pytest.fixture(autouse=True, scope="module")
def infrastructure(request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str):
"""Setup and teardown logic for E2E test infrastructure
Parameters
----------
request : pytest.FixtureRequest
pytest request fixture to introspect absolute path to test being executed
tmp_path_factory : pytest.TempPathFactory
pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up
worker_id : str
pytest-xdist worker identification to detect whether parallelization is enabled
Yields
------
Dict[str, str]
CloudFormation Outputs from deployed infrastructure
"""
yield from deploy_once(stack=LoggerStack, request=request, tmp_path_factory=tmp_path_factory, worker_id=worker_id)
14 changes: 4 additions & 10 deletions tests/e2e/logger/handlers/basic_handler.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import os

from aws_lambda_powertools import Logger

logger = Logger()

MESSAGE = os.environ["MESSAGE"]
ADDITIONAL_KEY = os.environ["ADDITIONAL_KEY"]


@logger.inject_lambda_context(log_event=True)
@logger.inject_lambda_context
def lambda_handler(event, context):
logger.debug(MESSAGE)
logger.info(MESSAGE)
logger.append_keys(**{ADDITIONAL_KEY: "test"})
logger.info(MESSAGE)
message, append_keys = event.get("message", ""), event.get("append_keys", {})
logger.append_keys(**append_keys)
logger.info(message)
return "success"
14 changes: 0 additions & 14 deletions tests/e2e/logger/handlers/no_context_handler.py

This file was deleted.

11 changes: 11 additions & 0 deletions tests/e2e/logger/infrastructure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from pathlib import Path

from tests.e2e.utils.infrastructure import BaseInfrastructureV2


class LoggerStack(BaseInfrastructureV2):
def __init__(self, handlers_dir: Path, feature_name: str = "logger") -> None:
super().__init__(feature_name, handlers_dir)

def create_resources(self):
self.create_lambda_functions()
150 changes: 22 additions & 128 deletions tests/e2e/logger/test_logger.py
Original file line number Diff line number Diff line change
@@ -1,143 +1,37 @@
import boto3
import json
from uuid import uuid4

import pytest
from e2e import conftest

from aws_lambda_powertools.shared.constants import LOGGER_LAMBDA_CONTEXT_KEYS
from tests.e2e.utils import data_fetcher


@pytest.fixture(scope="module")
def config() -> conftest.LambdaConfig:
return {
"parameters": {},
"environment_variables": {
"MESSAGE": "logger message test",
"LOG_LEVEL": "INFO",
"ADDITIONAL_KEY": "extra_info",
},
}


def test_basic_lambda_logs_visible(execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig):
# GIVEN
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn")
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
cw_client = boto3.client("logs")
@pytest.fixture
def basic_handler_fn(infrastructure: dict) -> str:
return infrastructure.get("BasicHandler", "")

# WHEN
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)

# THEN
assert any(
log.message == config["environment_variables"]["MESSAGE"]
and log.level == config["environment_variables"]["LOG_LEVEL"]
for log in filtered_logs
)
@pytest.fixture
def basic_handler_fn_arn(infrastructure: dict) -> str:
return infrastructure.get("BasicHandlerArn", "")


def test_basic_lambda_no_debug_logs_visible(
execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig
):
def test_basic_lambda_logs_visible(basic_handler_fn, basic_handler_fn_arn):
# GIVEN
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn")
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
cw_client = boto3.client("logs")
message = "logs should be visible with default settings"
custom_key = "order_id"
additional_keys = {custom_key: f"{uuid4()}"}
payload = json.dumps({"message": message, "append_keys": additional_keys})

# WHEN
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)
_, execution_time = data_fetcher.get_lambda_response(lambda_arn=basic_handler_fn_arn, payload=payload)
data_fetcher.get_lambda_response(lambda_arn=basic_handler_fn_arn, payload=payload)

# THEN
assert not any(
log.message == config["environment_variables"]["MESSAGE"] and log.level == "DEBUG" for log in filtered_logs
)


def test_basic_lambda_contextual_data_logged(execute_lambda: conftest.InfrastructureOutput):
# GIVEN
required_keys = (
"xray_trace_id",
"function_request_id",
"function_arn",
"function_memory_size",
"function_name",
"cold_start",
)

lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn")
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
cw_client = boto3.client("logs")

# WHEN
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)

# THEN
assert all(keys in logs.dict(exclude_unset=True) for logs in filtered_logs for keys in required_keys)


def test_basic_lambda_additional_key_persistence_basic_lambda(
execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig
):
# GIVEN
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn")
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
cw_client = boto3.client("logs")

# WHEN
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)

# THEN
assert any(
log.extra_info
and log.message == config["environment_variables"]["MESSAGE"]
and log.level == config["environment_variables"]["LOG_LEVEL"]
for log in filtered_logs
)


def test_basic_lambda_empty_event_logged(execute_lambda: conftest.InfrastructureOutput):
logs = data_fetcher.get_logs(function_name=basic_handler_fn, start_time=execution_time)

# GIVEN
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn")
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
cw_client = boto3.client("logs")

# WHEN
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)

# THEN
assert any(log.message == {} for log in filtered_logs)


def test_no_context_lambda_contextual_data_not_logged(execute_lambda: conftest.InfrastructureOutput):

# GIVEN
required_missing_keys = (
"function_request_id",
"function_arn",
"function_memory_size",
"function_name",
"cold_start",
)

lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="nocontexthandlerarn")
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
cw_client = boto3.client("logs")

# WHEN
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)

# THEN
assert not any(keys in logs.dict(exclude_unset=True) for logs in filtered_logs for keys in required_missing_keys)


def test_no_context_lambda_event_not_logged(execute_lambda: conftest.InfrastructureOutput):

# GIVEN
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="nocontexthandlerarn")
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
cw_client = boto3.client("logs")

# WHEN
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)

# THEN
assert not any(log.message == {} for log in filtered_logs)
assert len(logs) == 2
assert len(logs.get_cold_start_log()) == 1
assert len(logs.get_log(key=custom_key)) == 2
assert logs.have_keys(*LOGGER_LAMBDA_CONTEXT_KEYS) is True
Loading

0 comments on commit 6930b42

Please sign in to comment.