Skip to content

Commit 98d1589

Browse files
JamesW1-NHSmfjarvis
authored andcommitted
VED-698 CRUD Splunk (#731)
* local_id * vaccine_type * pre-check body * temp. revert * _log_data_from_body * _log_data_from_body II * supplier * cleanup, add tests * smell * log partial detail where available * LF * review cleanup
1 parent c1d2220 commit 98d1589

File tree

2 files changed

+220
-15
lines changed

2 files changed

+220
-15
lines changed

backend/src/log_structure.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,35 @@
66

77
from log_firehose import FirehoseLogger
88

9+
from models.utils.validation_utils import get_vaccine_type
10+
911
logging.basicConfig()
1012
logger = logging.getLogger()
1113
logger.setLevel("INFO")
1214

1315

1416
firehose_logger = FirehoseLogger()
1517

18+
def _log_data_from_body(event) -> dict:
19+
log_data = {}
20+
if event.get("body") is None:
21+
return log_data
22+
try:
23+
imms = json.loads(event["body"])
24+
except json.decoder.JSONDecodeError:
25+
return log_data
26+
try:
27+
vaccine_type = get_vaccine_type(imms)
28+
log_data["vaccine_type"] = vaccine_type
29+
except Exception:
30+
pass
31+
try:
32+
local_id = imms["identifier"][0]["value"] + "^" + imms["identifier"][0]["system"]
33+
log_data["local_id"] = local_id
34+
except Exception:
35+
pass
36+
return log_data
37+
1638

1739
def function_info(func):
1840
"""This decorator prints the execution information for the decorated function."""
@@ -24,6 +46,7 @@ def wrapper(*args, **kwargs):
2446
headers = event.get("headers", {})
2547
correlation_id = headers.get("X-Correlation-ID", "X-Correlation-ID not passed")
2648
request_id = headers.get("X-Request-ID", "X-Request-ID not passed")
49+
supplier_system = headers.get("SupplierSystem", "SupplierSystem not passed")
2750
actual_path = event.get("path", "Unknown")
2851
resource_path = event.get("requestContext", {}).get("resourcePath", "Unknown")
2952
logger.info(f"Starting {func.__name__} with X-Correlation-ID: {correlation_id} and X-Request-ID: {request_id}")
@@ -32,6 +55,7 @@ def wrapper(*args, **kwargs):
3255
"date_time": str(datetime.now()),
3356
"X-Correlation-ID": correlation_id,
3457
"X-Request-ID": request_id,
58+
"supplier": supplier_system,
3559
"actual_path": actual_path,
3660
"resource_path": resource_path,
3761
}
@@ -43,6 +67,7 @@ def wrapper(*args, **kwargs):
4367
logger.info(f"Result:{result}")
4468
end = time.time()
4569
log_data["time_taken"] = f"{round(end - start, 5)}s"
70+
log_data.update(_log_data_from_body(event))
4671
status = "500"
4772
status_code = "Exception"
4873
diagnostics = str()
@@ -78,6 +103,7 @@ def wrapper(*args, **kwargs):
78103
log_data["error"] = str(e)
79104
end = time.time()
80105
log_data["time_taken"] = f"{round(end - start, 5)}s"
106+
log_data.update(_log_data_from_body(event))
81107
logger.exception(json.dumps(log_data))
82108
firehose_log["event"] = log_data
83109
firehose_logger.send_log(firehose_log)

backend/tests/test_log_structure_wrapper.py

Lines changed: 194 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
@patch('log_structure.logger')
99
class TestFunctionInfoWrapper(unittest.TestCase):
1010

11+
def setUp(self):
12+
self.redis_patcher = patch("models.utils.validation_utils.redis_client")
13+
self.mock_redis_client = self.redis_patcher.start()
14+
15+
def tearDown(self):
16+
patch.stopall()
17+
1118
@staticmethod
1219
def mock_success_function(_event, _context):
1320
return "Success"
@@ -18,14 +25,23 @@ def mock_function_raises(_event, _context):
1825

1926
def test_successful_execution(self, mock_logger, mock_firehose_logger):
2027
# Arrange
28+
test_correlation = "test_correlation"
29+
test_request = "test_request"
30+
test_supplier = "test_supplier"
31+
test_actual_path = "/test"
32+
test_resource_path = "/test"
33+
34+
self.mock_redis_client.hget.return_value = "FLU"
2135
wrapped_function = function_info(self.mock_success_function)
2236
event = {
2337
'headers': {
24-
'X-Correlation-ID': 'test_correlation',
25-
'X-Request-ID': 'test_request'
38+
'X-Correlation-ID': test_correlation,
39+
'X-Request-ID': test_request,
40+
'SupplierSystem': test_supplier
2641
},
27-
'path': '/test',
28-
'requestContext': {'resourcePath': '/test'}
42+
'path': test_actual_path,
43+
'requestContext': {'resourcePath': test_resource_path},
44+
'body': "{\"identifier\": [{\"system\": \"http://test\", \"value\": \"12345\"}], \"protocolApplied\": [{\"targetDisease\": [{\"coding\": [{\"system\": \"http://snomed.info/sct\", \"code\": \"840539006\", \"display\": \"Disease caused by severe acute respiratory syndrome coronavirus 2\"}]}]}]}"
2945
}
3046

3147
# Act
@@ -41,23 +57,37 @@ def test_successful_execution(self, mock_logger, mock_firehose_logger):
4157

4258
self.assertIn('function_name', logged_info)
4359
self.assertIn('time_taken', logged_info)
44-
self.assertEqual(logged_info['X-Correlation-ID'], 'test_correlation')
45-
self.assertEqual(logged_info['X-Request-ID'], 'test_request')
46-
self.assertEqual(logged_info['actual_path'], '/test')
47-
self.assertEqual(logged_info['resource_path'], '/test')
60+
self.assertEqual(logged_info['X-Correlation-ID'], test_correlation)
61+
self.assertEqual(logged_info['X-Request-ID'], test_request)
62+
self.assertEqual(logged_info['supplier'], test_supplier)
63+
self.assertEqual(logged_info['actual_path'], test_actual_path)
64+
self.assertEqual(logged_info['resource_path'], test_resource_path)
65+
self.assertEqual(logged_info['local_id'], '12345^http://test')
66+
self.assertEqual(logged_info['vaccine_type'], 'FLU')
4867

4968
def test_exception_handling(self, mock_logger, mock_firehose_logger):
69+
# Arrange
70+
test_correlation = "failed_test_correlation"
71+
test_request = "failed_test_request"
72+
test_supplier = "failed_test_supplier"
73+
test_actual_path = "/failed_test"
74+
test_resource_path = "/failed_test"
75+
76+
self.mock_redis_client.hget.return_value = "FLU"
5077

5178
#Act
5279
decorated_function_raises = function_info(self.mock_function_raises)
5380

5481
with self.assertRaises(ValueError):
5582
#Assert
5683
event = {'headers': {
57-
'X-Correlation-ID': 'failed_test_correlation',
58-
'X-Request-ID': 'failed_test_request'
84+
'X-Correlation-ID': test_correlation,
85+
'X-Request-ID': test_request,
86+
'SupplierSystem': test_supplier
5987
},
60-
'path': '/failed_test', 'requestContext': {'resourcePath': '/failed_test'}}
88+
'path': test_actual_path, 'requestContext': {'resourcePath': test_resource_path},
89+
'body': "{\"identifier\": [{\"system\": \"http://test\", \"value\": \"12345\"}], \"protocolApplied\": [{\"targetDisease\": [{\"coding\": [{\"system\": \"http://snomed.info/sct\", \"code\": \"840539006\", \"display\": \"Disease caused by severe acute respiratory syndrome coronavirus 2\"}]}]}]}"}
90+
6191
context = {}
6292
decorated_function_raises(event, context)
6393

@@ -70,8 +100,157 @@ def test_exception_handling(self, mock_logger, mock_firehose_logger):
70100

71101
self.assertIn('function_name', logged_info)
72102
self.assertIn('time_taken', logged_info)
73-
self.assertEqual(logged_info['X-Correlation-ID'], 'failed_test_correlation')
74-
self.assertEqual(logged_info['X-Request-ID'], 'failed_test_request')
75-
self.assertEqual(logged_info['actual_path'], '/failed_test')
76-
self.assertEqual(logged_info['resource_path'], '/failed_test')
103+
self.assertEqual(logged_info['X-Correlation-ID'], test_correlation)
104+
self.assertEqual(logged_info['X-Request-ID'], test_request)
105+
self.assertEqual(logged_info['supplier'], test_supplier)
106+
self.assertEqual(logged_info['actual_path'], test_actual_path)
107+
self.assertEqual(logged_info['resource_path'], test_resource_path)
77108
self.assertEqual(logged_info['error'], str(ValueError("Test error")))
109+
self.assertEqual(logged_info['local_id'], '12345^http://test')
110+
self.assertEqual(logged_info['vaccine_type'], 'FLU')
111+
112+
def test_body_missing(self, mock_logger, mock_firehose_logger):
113+
# Arrange
114+
test_correlation = "failed_test_correlation_body_missing"
115+
test_request = "failed_test_request_body_missing"
116+
test_supplier = "failed_test_supplier_body_missing"
117+
test_actual_path = "/failed_test_body_missing"
118+
test_resource_path = "/failed_test_body_missing"
119+
120+
wrapped_function = function_info(self.mock_success_function)
121+
event = {
122+
'headers': {
123+
'X-Correlation-ID': test_correlation,
124+
'X-Request-ID': test_request,
125+
'SupplierSystem': test_supplier
126+
},
127+
'path': test_actual_path,
128+
'requestContext': {'resourcePath': test_resource_path},
129+
}
130+
131+
# Act
132+
result = wrapped_function(event, {})
133+
134+
# Assert
135+
args, kwargs = mock_logger.info.call_args
136+
logged_info = json.loads(args[0])
137+
138+
self.assertEqual(logged_info['X-Correlation-ID'], test_correlation)
139+
self.assertEqual(logged_info['X-Request-ID'], test_request)
140+
self.assertEqual(logged_info['supplier'], test_supplier)
141+
self.assertEqual(logged_info['actual_path'], test_actual_path)
142+
self.assertEqual(logged_info['resource_path'], test_resource_path)
143+
self.assertNotIn('local_id', logged_info)
144+
self.assertNotIn('vaccine_type', logged_info)
145+
146+
def test_body_not_json(self, mock_logger, mock_firehose_logger):
147+
# Arrange
148+
test_correlation = "failed_test_correlation_body_not_json"
149+
test_request = "failed_test_request_body_not_json"
150+
test_supplier = "failed_test_supplier_body_not_json"
151+
test_actual_path = "/failed_test_body_not_json"
152+
test_resource_path = "/failed_test_body_not_json"
153+
154+
# Act
155+
decorated_function_raises = function_info(self.mock_function_raises)
156+
157+
with self.assertRaises(ValueError):
158+
#Assert
159+
event = {'headers': {
160+
'X-Correlation-ID': test_correlation,
161+
'X-Request-ID': test_request,
162+
'SupplierSystem': test_supplier
163+
},
164+
'path': test_actual_path, 'requestContext': {'resourcePath': test_resource_path},
165+
'body': "invalid"}
166+
167+
context = {}
168+
decorated_function_raises(event, context)
169+
170+
#Assert
171+
args, kwargs = mock_logger.exception.call_args
172+
logged_info = json.loads(args[0])
173+
174+
self.assertEqual(logged_info['X-Correlation-ID'], test_correlation)
175+
self.assertEqual(logged_info['X-Request-ID'], test_request)
176+
self.assertEqual(logged_info['supplier'], test_supplier)
177+
self.assertEqual(logged_info['actual_path'], test_actual_path)
178+
self.assertEqual(logged_info['resource_path'], test_resource_path)
179+
self.assertNotIn('local_id', logged_info)
180+
self.assertNotIn('vaccine_type', logged_info)
181+
182+
def test_body_invalid_identifier(self, mock_logger, mock_firehose_logger):
183+
# Arrange
184+
test_correlation = "failed_test_correlation_invalid_identifier"
185+
test_request = "failed_test_request_invalid_identifier"
186+
test_supplier = "failed_test_supplier_invalid_identifier"
187+
test_actual_path = "/failed_test_invalid_identifier"
188+
test_resource_path = "/failed_test_invalid_identifier"
189+
190+
self.mock_redis_client.hget.return_value = "FLU"
191+
192+
# Act
193+
decorated_function_raises = function_info(self.mock_function_raises)
194+
195+
with self.assertRaises(ValueError):
196+
#Assert
197+
event = {'headers': {
198+
'X-Correlation-ID': test_correlation,
199+
'X-Request-ID': test_request,
200+
'SupplierSystem': test_supplier
201+
},
202+
'path': test_actual_path, 'requestContext': {'resourcePath': test_resource_path},
203+
'body': "{\"identifier\": [], \"protocolApplied\": [{\"targetDisease\": [{\"coding\": [{\"system\": \"http://snomed.info/sct\", \"code\": \"840539006\", \"display\": \"Disease caused by severe acute respiratory syndrome coronavirus 2\"}]}]}]}"}
204+
205+
context = {}
206+
decorated_function_raises(event, context)
207+
208+
#Assert
209+
args, kwargs = mock_logger.exception.call_args
210+
logged_info = json.loads(args[0])
211+
212+
self.assertEqual(logged_info['X-Correlation-ID'], test_correlation)
213+
self.assertEqual(logged_info['X-Request-ID'], test_request)
214+
self.assertEqual(logged_info['supplier'], test_supplier)
215+
self.assertEqual(logged_info['actual_path'], test_actual_path)
216+
self.assertEqual(logged_info['resource_path'], test_resource_path)
217+
self.assertNotIn('local_id', logged_info)
218+
self.assertEqual(logged_info['vaccine_type'], 'FLU')
219+
220+
def test_body_invalid_protocol_applied(self, mock_logger, mock_firehose_logger):
221+
# Arrange
222+
test_correlation = "failed_test_correlation_invalid_protocol"
223+
test_request = "failed_test_request_invalid_protocol"
224+
test_supplier = "failed_test_supplier_invalid_protocol"
225+
test_actual_path = "/failed_test_invalid_protocol"
226+
test_resource_path = "/failed_test_invalid_protocol"
227+
228+
self.mock_redis_client.hget.return_value = "FLU"
229+
230+
# Act
231+
decorated_function_raises = function_info(self.mock_function_raises)
232+
233+
with self.assertRaises(ValueError):
234+
#Assert
235+
event = {'headers': {
236+
'X-Correlation-ID': test_correlation,
237+
'X-Request-ID': test_request,
238+
'SupplierSystem': test_supplier
239+
},
240+
'path': test_actual_path, 'requestContext': {'resourcePath': test_resource_path},
241+
'body': "{\"identifier\": [{\"system\": \"http://test\", \"value\": \"12345\"}], \"protocolApplied\": []}"}
242+
243+
context = {}
244+
decorated_function_raises(event, context)
245+
246+
#Assert
247+
args, kwargs = mock_logger.exception.call_args
248+
logged_info = json.loads(args[0])
249+
250+
self.assertEqual(logged_info['X-Correlation-ID'], test_correlation)
251+
self.assertEqual(logged_info['X-Request-ID'], test_request)
252+
self.assertEqual(logged_info['supplier'], test_supplier)
253+
self.assertEqual(logged_info['actual_path'], test_actual_path)
254+
self.assertEqual(logged_info['resource_path'], test_resource_path)
255+
self.assertEqual(logged_info['local_id'], '12345^http://test')
256+
self.assertNotIn('vaccine_type', logged_info)

0 commit comments

Comments
 (0)