Skip to content

Commit d4a9143

Browse files
committed
Use common logging decorator functions
1 parent 31f9dfd commit d4a9143

File tree

7 files changed

+38
-119
lines changed

7 files changed

+38
-119
lines changed
Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,16 @@
11
"""This module contains the logging decorator for sending the appropriate logs to Cloudwatch and Firehose."""
22

3-
import json
43
import os
54
import time
65
from datetime import datetime
76
from functools import wraps
87

9-
from common.clients import firehose_client, logger
8+
from common.log_decorator import generate_and_send_logs
109
from errors import InvalidHeaders, NoOperationPermissions
1110

1211
STREAM_NAME = os.getenv("SPLUNK_FIREHOSE_NAME", "immunisation-fhir-api-internal-dev-splunk-firehose")
1312

1413

15-
def send_log_to_firehose(log_data: dict) -> None:
16-
"""Sends the log_message to Firehose"""
17-
try:
18-
record = {"Data": json.dumps({"event": log_data}).encode("utf-8")}
19-
response = firehose_client.put_record(DeliveryStreamName=STREAM_NAME, Record=record)
20-
logger.info("Log sent to Firehose: %s", response) # TODO: Should we be logging full response?
21-
except Exception as error: # pylint:disable = broad-exception-caught
22-
logger.exception("Error sending log to Firehose: %s", error)
23-
24-
25-
def generate_and_send_logs(
26-
start_time,
27-
base_log_data: dict,
28-
additional_log_data: dict,
29-
is_error_log: bool = False,
30-
) -> None:
31-
"""Generates log data which includes the base_log_data, additional_log_data, and time taken (calculated using the
32-
current time and given start_time) and sends them to Cloudwatch and Firehose."""
33-
log_data = {
34-
**base_log_data,
35-
"time_taken": f"{round(time.time() - start_time, 5)}s",
36-
**additional_log_data,
37-
}
38-
log_function = logger.error if is_error_log else logger.info
39-
log_function(json.dumps(log_data))
40-
send_log_to_firehose(log_data)
41-
42-
4314
def file_level_validation_logging_decorator(func):
4415
"""
4516
Sends the appropriate logs to Cloudwatch and Firehose based on the result of the file_level_validation
@@ -65,7 +36,7 @@ def wrapper(*args, **kwargs):
6536
"statusCode": 200,
6637
"message": "Successfully sent for record processing",
6738
}
68-
generate_and_send_logs(start_time, base_log_data, additional_log_data=additional_log_data)
39+
generate_and_send_logs(STREAM_NAME, start_time, base_log_data, additional_log_data)
6940
return result
7041

7142
except (InvalidHeaders, NoOperationPermissions, Exception) as e:
@@ -78,7 +49,7 @@ def wrapper(*args, **kwargs):
7849
"message": message,
7950
"error": str(e),
8051
}
81-
generate_and_send_logs(start_time, base_log_data, additional_log_data, is_error_log=True)
52+
generate_and_send_logs(STREAM_NAME, start_time, base_log_data, additional_log_data, is_error_log=True)
8253
raise
8354

8455
return wrapper

lambdas/recordprocessor/tests/test_logging_decorator.py

Lines changed: 21 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
from boto3 import client as boto3_client
1111
from moto import mock_firehose, mock_s3
1212

13-
from tests.utils_for_recordprocessor_tests.mock_environment_variables import (
13+
from utils_for_recordprocessor_tests.mock_environment_variables import (
1414
MOCK_ENVIRONMENT_DICT,
1515
BucketNames,
1616
Firehose,
1717
)
18-
from tests.utils_for_recordprocessor_tests.values_for_recordprocessor_tests import (
18+
from utils_for_recordprocessor_tests.values_for_recordprocessor_tests import (
1919
MockFileDetails,
2020
ValidMockFileContent,
2121
)
@@ -24,10 +24,9 @@
2424
from common.clients import REGION_NAME
2525
from errors import InvalidHeaders, NoOperationPermissions
2626
from file_level_validation import file_level_validation
27-
from logging_decorator import generate_and_send_logs, send_log_to_firehose
2827

2928

30-
from tests.utils_for_recordprocessor_tests.utils_for_recordprocessor_tests import (
29+
from utils_for_recordprocessor_tests.utils_for_recordprocessor_tests import (
3130
GenericSetUp,
3231
GenericTearDown,
3332
)
@@ -76,67 +75,6 @@ def run(self, result=None):
7675
stack.enter_context(common_patch)
7776
super().run(result)
7877

79-
def test_send_log_to_firehose(self):
80-
"""
81-
Tests that the send_log_to_firehose function calls firehose_client.put_record with the correct arguments.
82-
NOTE: mock_firehose does not persist the data, so at this level it is only possible to test what the call args
83-
were, not that the data reached the destination.
84-
"""
85-
log_data = {"test_key": "test_value"}
86-
87-
with patch("logging_decorator.firehose_client") as mock_firehose_client:
88-
send_log_to_firehose(log_data)
89-
90-
expected_firehose_record = {"Data": json.dumps({"event": log_data}).encode("utf-8")}
91-
mock_firehose_client.put_record.assert_called_once_with(
92-
DeliveryStreamName=Firehose.STREAM_NAME, Record=expected_firehose_record
93-
)
94-
95-
def test_generate_and_send_logs(self):
96-
"""
97-
Tests that the generate_and_send_logs function logs the correct data at the correct level for cloudwatch
98-
and calls send_log_to_firehose with the correct log data
99-
"""
100-
base_log_data = {"base_key": "base_value"}
101-
additional_log_data = {"additional_key": "additional_value"}
102-
start_time = 1672531200
103-
104-
# CASE: Successful log - is_error_log arg set to False
105-
with ( # noqa: E999
106-
patch("logging_decorator.logger") as mock_logger, # noqa: E999
107-
patch("logging_decorator.send_log_to_firehose") as mock_send_log_to_firehose, # noqa: E999
108-
patch("logging_decorator.time") as mock_time, # noqa: E999
109-
): # noqa: E999
110-
mock_time.time.return_value = 1672531200.123456 # Mocks the end time to be 0.123456s after the start time
111-
generate_and_send_logs(start_time, base_log_data, additional_log_data, is_error_log=False)
112-
113-
expected_log_data = {
114-
"base_key": "base_value",
115-
"time_taken": "0.12346s",
116-
"additional_key": "additional_value",
117-
}
118-
log_data = json.loads(mock_logger.info.call_args[0][0])
119-
self.assertEqual(log_data, expected_log_data)
120-
mock_send_log_to_firehose.assert_called_once_with(expected_log_data)
121-
122-
# CASE: Error log - is_error_log arg set to True
123-
with ( # noqa: E999
124-
patch("logging_decorator.logger") as mock_logger, # noqa: E999
125-
patch("logging_decorator.send_log_to_firehose") as mock_send_log_to_firehose, # noqa: E999
126-
patch("logging_decorator.time") as mock_time, # noqa: E999
127-
): # noqa: E999
128-
mock_time.time.return_value = 1672531200.123456 # Mocks the end time to be 0.123456s after the start time
129-
generate_and_send_logs(start_time, base_log_data, additional_log_data, is_error_log=True)
130-
131-
expected_log_data = {
132-
"base_key": "base_value",
133-
"time_taken": "0.12346s",
134-
"additional_key": "additional_value",
135-
}
136-
log_data = json.loads(mock_logger.error.call_args[0][0])
137-
self.assertEqual(log_data, expected_log_data)
138-
mock_send_log_to_firehose.assert_called_once_with(expected_log_data)
139-
14078
def test_splunk_logger_successful_validation(self):
14179
"""Tests the splunk logger is called when file-level validation is successful"""
14280

@@ -147,12 +85,14 @@ def test_splunk_logger_successful_validation(self):
14785
)
14886

14987
with ( # noqa: E999
88+
patch("common.log_firehose.firehose_client") as mock_firehose_client, # noqa: E999
89+
patch("common.log_decorator.logger") as mock_logger, # noqa: E999
15090
patch("logging_decorator.datetime") as mock_datetime, # noqa: E999
151-
patch("logging_decorator.time") as mock_time, # noqa: E999
152-
patch("logging_decorator.logger") as mock_logger, # noqa: E999
153-
patch("logging_decorator.firehose_client") as mock_firehose_client, # noqa: E999
91+
patch("logging_decorator.time") as mock_start_time, # noqa: E999
92+
patch("common.log_decorator.time") as mock_end_time, # noqa: E999
15493
): # noqa: E999
155-
mock_time.time.side_effect = [1672531200, 1672531200.123456]
94+
mock_start_time.time.return_value = 1672531200
95+
mock_end_time.time.return_value = 1672531200.123456
15696
mock_datetime.now.return_value = datetime(2024, 1, 1, 12, 0, 0)
15797
file_level_validation(deepcopy(MOCK_FILE_DETAILS.event_full_permissions_dict))
15898

@@ -211,13 +151,15 @@ def test_splunk_logger_handled_failure(self):
211151
)
212152

213153
with ( # noqa: E999
154+
patch("common.log_firehose.firehose_client") as mock_firehose_client, # noqa: E999
155+
patch("common.log_decorator.logger") as mock_logger, # noqa: E999
214156
patch("logging_decorator.datetime") as mock_datetime, # noqa: E999
215-
patch("logging_decorator.time") as mock_time, # noqa: E999
216-
patch("logging_decorator.logger") as mock_logger, # noqa: E999
217-
patch("logging_decorator.firehose_client") as mock_firehose_client, # noqa: E999
157+
patch("logging_decorator.time") as mock_start_time, # noqa: E999
158+
patch("common.log_decorator.time") as mock_end_time, # noqa: E999
218159
): # noqa: E999
219160
mock_datetime.now.return_value = datetime(2024, 1, 1, 12, 0, 0)
220-
mock_time.time.side_effect = [1672531200, 1672531200.123456]
161+
mock_start_time.time.return_value = 1672531200
162+
mock_end_time.time.return_value = 1672531200.123456
221163
with self.assertRaises(expected_error_type):
222164
file_level_validation(deepcopy(event_dict))
223165

@@ -247,16 +189,18 @@ def test_splunk_logger_unhandled_failure(self):
247189
)
248190

249191
with ( # noqa: E999
192+
patch("common.log_firehose.firehose_client") as mock_firehose_client, # noqa: E999
193+
patch("common.log_decorator.logger") as mock_logger, # noqa: E999
250194
patch("logging_decorator.datetime") as mock_datetime, # noqa: E999
251-
patch("logging_decorator.time") as mock_time, # noqa: E999
252-
patch("logging_decorator.logger") as mock_logger, # noqa: E999
253-
patch("logging_decorator.firehose_client") as mock_firehose_client, # noqa: E999
195+
patch("logging_decorator.time") as mock_start_time, # noqa: E999
196+
patch("common.log_decorator.time") as mock_end_time, # noqa: E999
254197
patch(
255198
"file_level_validation.validate_content_headers",
256199
side_effect=ValueError("Test exception"),
257200
), # noqa: E999
258201
): # noqa: E999
259-
mock_time.time.side_effect = [1672531200, 1672531200.123456]
202+
mock_start_time.time.return_value = 1672531200
203+
mock_end_time.time.return_value = 1672531200.123456
260204
mock_datetime.now.return_value = datetime(2024, 1, 1, 12, 0, 0)
261205
with self.assertRaises(ValueError):
262206
file_level_validation(deepcopy(MOCK_FILE_DETAILS.event_full_permissions_dict))

lambdas/recordprocessor/tests/test_recordprocessor_edge_cases.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from unittest.mock import call, patch
55

66
from batch_processor import process_csv_to_fhir
7-
from tests.utils_for_recordprocessor_tests.utils_for_recordprocessor_tests import (
7+
from utils_for_recordprocessor_tests.utils_for_recordprocessor_tests import (
88
create_patch,
99
)
1010

@@ -21,7 +21,7 @@ def setUp(self):
2121
self.mock_make_and_move = create_patch("file_level_validation.make_and_upload_ack_file")
2222
self.mock_move_file = create_patch("file_level_validation.move_file")
2323
self.mock_get_permitted_operations = create_patch("file_level_validation.get_permitted_operations")
24-
self.mock_firehose_client = create_patch("logging_decorator.firehose_client")
24+
self.mock_firehose_client = create_patch("common.log_firehose.firehose_client")
2525
self.mock_update_audit_table_status = create_patch("batch_processor.update_audit_table_status")
2626

2727
def tearDown(self):

lambdas/recordprocessor/tests/test_recordprocessor_main.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from utils_for_recordprocessor_tests.mock_environment_variables import (
1414
MOCK_ENVIRONMENT_DICT,
1515
BucketNames,
16+
Firehose,
1617
Kinesis,
1718
)
1819
from utils_for_recordprocessor_tests.utils_for_recordprocessor_tests import (
@@ -437,15 +438,17 @@ def test_e2e_kinesis_failed(self):
437438
kinesis_client.delete_stream(StreamName=Kinesis.STREAM_NAME, EnforceConsumerDeletion=True)
438439

439440
with ( # noqa: E999
440-
patch("logging_decorator.send_log_to_firehose") as mock_send_log_to_firehose, # noqa: E999
441+
patch("common.log_decorator.send_log_to_firehose") as mock_send_log_to_firehose, # noqa: E999
441442
patch("logging_decorator.datetime") as mock_datetime, # noqa: E999
442-
patch("logging_decorator.time") as mock_time, # noqa: E999
443+
patch("logging_decorator.time") as mock_start_time, # noqa: E999
444+
patch("common.log_decorator.time") as mock_end_time, # noqa: E999
443445
): # noqa: E999
444-
mock_time.time.side_effect = [1672531200, 1672531200.123456]
446+
mock_start_time.time.return_value = 1672531200
447+
mock_end_time.time.return_value = 1672531200.123456
445448
mock_datetime.now.return_value = datetime(2024, 1, 1, 12, 0, 0)
446449
main(test_file.event_full_permissions)
447450

448-
# Since the failure occured at row level, not file level, the ack file should still be created
451+
# Since the failure occurred at row level, not file level, the ack file should still be created
449452
# and firehose logs should indicate a successful file level validation
450453
table_entry = dynamo_db_client.get_item(
451454
TableName=AUDIT_TABLE_NAME,
@@ -463,7 +466,7 @@ def test_e2e_kinesis_failed(self):
463466
"statusCode": 200,
464467
"message": "Successfully sent for record processing",
465468
}
466-
mock_send_log_to_firehose.assert_called_with(expected_log_data)
469+
mock_send_log_to_firehose.assert_called_with(Firehose.STREAM_NAME, expected_log_data)
467470
self.assertDictEqual(
468471
table_entry,
469472
{

lambdas/recordprocessor/tests/utils_for_recordprocessor_tests/mock_environment_variables.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Sqs:
3737
"SOURCE_BUCKET_NAME": BucketNames.SOURCE,
3838
"ACK_BUCKET_NAME": BucketNames.DESTINATION,
3939
"SHORT_QUEUE_PREFIX": "imms-batch-internal-dev",
40+
"SPLUNK_FIREHOSE_NAME": Firehose.STREAM_NAME,
4041
"KINESIS_STREAM_NAME": Kinesis.STREAM_NAME,
4142
"KINESIS_STREAM_ARN": f"arn:aws:kinesis:{REGION_NAME}:123456789012:stream/{Kinesis.STREAM_NAME}",
4243
"FIREHOSE_STREAM_NAME": Firehose.STREAM_NAME,

lambdas/shared/src/common/log_decorator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515

1616
def generate_and_send_logs(
17-
stream_name,
18-
start_time,
17+
stream_name: str,
18+
start_time: float,
1919
base_log_data: dict,
2020
additional_log_data: dict,
2121
use_ms_precision: bool = False,

lambdas/shared/src/common/log_firehose.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
)
77

88

9-
def send_log_to_firehose(stream_name, log_data: dict) -> None:
9+
def send_log_to_firehose(stream_name: str, log_data: dict) -> None:
1010
"""Sends the log_message to Firehose"""
1111
try:
1212
record = {"Data": json.dumps({"event": log_data}).encode("utf-8")}

0 commit comments

Comments
 (0)