Skip to content

Commit 6930b42

Browse files
authored
chore(tests): refactor E2E logger to ease maintenance, writing tests and parallelization (#1460)
1 parent 7567c84 commit 6930b42

File tree

11 files changed

+245
-200
lines changed

11 files changed

+245
-200
lines changed

aws_lambda_powertools/shared/constants.py

+9
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,12 @@
2323
XRAY_SDK_CORE_MODULE: str = "aws_xray_sdk.core"
2424

2525
IDEMPOTENCY_DISABLED_ENV: str = "POWERTOOLS_IDEMPOTENCY_DISABLED"
26+
27+
LOGGER_LAMBDA_CONTEXT_KEYS = [
28+
"function_arn",
29+
"function_memory_size",
30+
"function_name",
31+
"function_request_id",
32+
"cold_start",
33+
"xray_trace_id",
34+
]

poetry.lock

+31-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ mypy-boto3-lambda = "^1.24.0"
6565
mypy-boto3-xray = "^1.24.0"
6666
mypy-boto3-s3 = { version = "^1.24.0", python = ">=3.7" }
6767
mypy-boto3-cloudformation = { version = "^1.24.0", python = ">=3.7" }
68+
mypy-boto3-logs = { version = "^1.24.0", python = ">=3.7" }
6869
types-requests = "^2.28.8"
6970
typing-extensions = { version = "^4.3.0", python = ">=3.7" }
7071
python-snappy = "^0.6.1"

tests/e2e/logger/conftest.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import pytest
2+
3+
from tests.e2e.logger.infrastructure import LoggerStack
4+
from tests.e2e.utils.infrastructure import deploy_once
5+
6+
7+
@pytest.fixture(autouse=True, scope="module")
8+
def infrastructure(request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str):
9+
"""Setup and teardown logic for E2E test infrastructure
10+
11+
Parameters
12+
----------
13+
request : pytest.FixtureRequest
14+
pytest request fixture to introspect absolute path to test being executed
15+
tmp_path_factory : pytest.TempPathFactory
16+
pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up
17+
worker_id : str
18+
pytest-xdist worker identification to detect whether parallelization is enabled
19+
20+
Yields
21+
------
22+
Dict[str, str]
23+
CloudFormation Outputs from deployed infrastructure
24+
"""
25+
yield from deploy_once(stack=LoggerStack, request=request, tmp_path_factory=tmp_path_factory, worker_id=worker_id)
+4-10
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
1-
import os
2-
31
from aws_lambda_powertools import Logger
42

53
logger = Logger()
64

7-
MESSAGE = os.environ["MESSAGE"]
8-
ADDITIONAL_KEY = os.environ["ADDITIONAL_KEY"]
9-
105

11-
@logger.inject_lambda_context(log_event=True)
6+
@logger.inject_lambda_context
127
def lambda_handler(event, context):
13-
logger.debug(MESSAGE)
14-
logger.info(MESSAGE)
15-
logger.append_keys(**{ADDITIONAL_KEY: "test"})
16-
logger.info(MESSAGE)
8+
message, append_keys = event.get("message", ""), event.get("append_keys", {})
9+
logger.append_keys(**append_keys)
10+
logger.info(message)
1711
return "success"

tests/e2e/logger/handlers/no_context_handler.py

-14
This file was deleted.

tests/e2e/logger/infrastructure.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from pathlib import Path
2+
3+
from tests.e2e.utils.infrastructure import BaseInfrastructureV2
4+
5+
6+
class LoggerStack(BaseInfrastructureV2):
7+
def __init__(self, handlers_dir: Path, feature_name: str = "logger") -> None:
8+
super().__init__(feature_name, handlers_dir)
9+
10+
def create_resources(self):
11+
self.create_lambda_functions()

tests/e2e/logger/test_logger.py

+22-128
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,37 @@
1-
import boto3
1+
import json
2+
from uuid import uuid4
3+
24
import pytest
3-
from e2e import conftest
45

6+
from aws_lambda_powertools.shared.constants import LOGGER_LAMBDA_CONTEXT_KEYS
57
from tests.e2e.utils import data_fetcher
68

79

8-
@pytest.fixture(scope="module")
9-
def config() -> conftest.LambdaConfig:
10-
return {
11-
"parameters": {},
12-
"environment_variables": {
13-
"MESSAGE": "logger message test",
14-
"LOG_LEVEL": "INFO",
15-
"ADDITIONAL_KEY": "extra_info",
16-
},
17-
}
18-
19-
20-
def test_basic_lambda_logs_visible(execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig):
21-
# GIVEN
22-
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn")
23-
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
24-
cw_client = boto3.client("logs")
10+
@pytest.fixture
11+
def basic_handler_fn(infrastructure: dict) -> str:
12+
return infrastructure.get("BasicHandler", "")
2513

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

29-
# THEN
30-
assert any(
31-
log.message == config["environment_variables"]["MESSAGE"]
32-
and log.level == config["environment_variables"]["LOG_LEVEL"]
33-
for log in filtered_logs
34-
)
15+
@pytest.fixture
16+
def basic_handler_fn_arn(infrastructure: dict) -> str:
17+
return infrastructure.get("BasicHandlerArn", "")
3518

3619

37-
def test_basic_lambda_no_debug_logs_visible(
38-
execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig
39-
):
20+
def test_basic_lambda_logs_visible(basic_handler_fn, basic_handler_fn_arn):
4021
# GIVEN
41-
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn")
42-
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
43-
cw_client = boto3.client("logs")
22+
message = "logs should be visible with default settings"
23+
custom_key = "order_id"
24+
additional_keys = {custom_key: f"{uuid4()}"}
25+
payload = json.dumps({"message": message, "append_keys": additional_keys})
4426

4527
# WHEN
46-
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)
28+
_, execution_time = data_fetcher.get_lambda_response(lambda_arn=basic_handler_fn_arn, payload=payload)
29+
data_fetcher.get_lambda_response(lambda_arn=basic_handler_fn_arn, payload=payload)
4730

4831
# THEN
49-
assert not any(
50-
log.message == config["environment_variables"]["MESSAGE"] and log.level == "DEBUG" for log in filtered_logs
51-
)
52-
53-
54-
def test_basic_lambda_contextual_data_logged(execute_lambda: conftest.InfrastructureOutput):
55-
# GIVEN
56-
required_keys = (
57-
"xray_trace_id",
58-
"function_request_id",
59-
"function_arn",
60-
"function_memory_size",
61-
"function_name",
62-
"cold_start",
63-
)
64-
65-
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn")
66-
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
67-
cw_client = boto3.client("logs")
68-
69-
# WHEN
70-
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)
71-
72-
# THEN
73-
assert all(keys in logs.dict(exclude_unset=True) for logs in filtered_logs for keys in required_keys)
74-
75-
76-
def test_basic_lambda_additional_key_persistence_basic_lambda(
77-
execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig
78-
):
79-
# GIVEN
80-
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn")
81-
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
82-
cw_client = boto3.client("logs")
83-
84-
# WHEN
85-
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)
86-
87-
# THEN
88-
assert any(
89-
log.extra_info
90-
and log.message == config["environment_variables"]["MESSAGE"]
91-
and log.level == config["environment_variables"]["LOG_LEVEL"]
92-
for log in filtered_logs
93-
)
94-
95-
96-
def test_basic_lambda_empty_event_logged(execute_lambda: conftest.InfrastructureOutput):
32+
logs = data_fetcher.get_logs(function_name=basic_handler_fn, start_time=execution_time)
9733

98-
# GIVEN
99-
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn")
100-
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
101-
cw_client = boto3.client("logs")
102-
103-
# WHEN
104-
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)
105-
106-
# THEN
107-
assert any(log.message == {} for log in filtered_logs)
108-
109-
110-
def test_no_context_lambda_contextual_data_not_logged(execute_lambda: conftest.InfrastructureOutput):
111-
112-
# GIVEN
113-
required_missing_keys = (
114-
"function_request_id",
115-
"function_arn",
116-
"function_memory_size",
117-
"function_name",
118-
"cold_start",
119-
)
120-
121-
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="nocontexthandlerarn")
122-
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
123-
cw_client = boto3.client("logs")
124-
125-
# WHEN
126-
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)
127-
128-
# THEN
129-
assert not any(keys in logs.dict(exclude_unset=True) for logs in filtered_logs for keys in required_missing_keys)
130-
131-
132-
def test_no_context_lambda_event_not_logged(execute_lambda: conftest.InfrastructureOutput):
133-
134-
# GIVEN
135-
lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="nocontexthandlerarn")
136-
timestamp = execute_lambda.get_lambda_execution_time_timestamp()
137-
cw_client = boto3.client("logs")
138-
139-
# WHEN
140-
filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client)
141-
142-
# THEN
143-
assert not any(log.message == {} for log in filtered_logs)
34+
assert len(logs) == 2
35+
assert len(logs.get_cold_start_log()) == 1
36+
assert len(logs.get_log(key=custom_key)) == 2
37+
assert logs.have_keys(*LOGGER_LAMBDA_CONTEXT_KEYS) is True

0 commit comments

Comments
 (0)