Skip to content

Commit d776378

Browse files
authored
feat: Add batchItemFailures metric (#664)
* feat: Add batchItemFailures metric * fmt
1 parent 93d4a07 commit d776378

File tree

4 files changed

+167
-0
lines changed

4 files changed

+167
-0
lines changed

datadog_lambda/metric.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,33 @@ def submit_errors_metric(lambda_context):
214214
submit_enhanced_metric("errors", lambda_context)
215215

216216

217+
def submit_batch_item_failures_metric(response, lambda_context):
218+
"""Submit aws.lambda.enhanced.batch_item_failures metric with the count of batch item failures
219+
220+
Args:
221+
response (dict): Lambda function response object
222+
lambda_context (object): Lambda context dict passed to the function by AWS
223+
"""
224+
if not config.enhanced_metrics_enabled:
225+
logger.debug(
226+
"Not submitting batch_item_failures metric because enhanced metrics are disabled"
227+
)
228+
return
229+
230+
if not isinstance(response, dict):
231+
return
232+
233+
batch_item_failures = response.get("batchItemFailures")
234+
if batch_item_failures is not None and isinstance(batch_item_failures, list):
235+
lambda_metric(
236+
"aws.lambda.enhanced.batch_item_failures",
237+
len(batch_item_failures),
238+
timestamp=None,
239+
tags=get_enhanced_metrics_tags(lambda_context),
240+
force_async=True,
241+
)
242+
243+
217244
def submit_dynamodb_stream_type_metric(event):
218245
stream_view_type = (
219246
event.get("Records", [{}])[0].get("dynamodb", {}).get("StreamViewType")

datadog_lambda/wrapper.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,10 @@ def _before(self, event, context):
291291

292292
def _after(self, event, context):
293293
try:
294+
from datadog_lambda.metric import submit_batch_item_failures_metric
295+
296+
submit_batch_item_failures_metric(self.response, context)
297+
294298
status_code = extract_http_status_code_tag(self.trigger_tags, self.response)
295299

296300
if self.span:

tests/test_metric.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
_select_metrics_handler,
1313
flush_stats,
1414
lambda_metric,
15+
submit_batch_item_failures_metric,
1516
)
1617
from datadog_lambda.tags import dd_lambda_layer_tag
1718
from datadog_lambda.thread_stats_writer import ThreadStatsWriter
@@ -324,3 +325,80 @@ def decrypt(self, CiphertextBlob=None, EncryptionContext={}):
324325
mock_kms_client, MOCK_ENCRYPTED_API_KEY_BASE64
325326
)
326327
self.assertEqual(decrypted_key, EXPECTED_DECRYPTED_API_KEY)
328+
329+
330+
class TestBatchItemFailuresMetric(unittest.TestCase):
331+
def setUp(self):
332+
patcher = patch("datadog_lambda.metric.lambda_metric")
333+
self.mock_lambda_metric = patcher.start()
334+
self.addCleanup(patcher.stop)
335+
336+
patcher = patch("datadog_lambda.config.Config.enhanced_metrics_enabled", True)
337+
self.mock_enhanced_metrics_enabled = patcher.start()
338+
self.addCleanup(patcher.stop)
339+
340+
def test_submit_batch_item_failures_with_failures(self):
341+
response = {
342+
"batchItemFailures": [
343+
{"itemIdentifier": "msg-1"},
344+
{"itemIdentifier": "msg-2"},
345+
{"itemIdentifier": "msg-3"},
346+
]
347+
}
348+
context = unittest.mock.Mock()
349+
350+
with patch("datadog_lambda.metric.get_enhanced_metrics_tags") as mock_get_tags:
351+
mock_get_tags.return_value = ["tag1:value1"]
352+
submit_batch_item_failures_metric(response, context)
353+
354+
self.mock_lambda_metric.assert_called_once_with(
355+
"aws.lambda.enhanced.batch_item_failures",
356+
3,
357+
timestamp=None,
358+
tags=["tag1:value1"],
359+
force_async=True,
360+
)
361+
362+
def test_submit_batch_item_failures_with_no_failures(self):
363+
response = {"batchItemFailures": []}
364+
context = unittest.mock.Mock()
365+
366+
with patch("datadog_lambda.metric.get_enhanced_metrics_tags") as mock_get_tags:
367+
mock_get_tags.return_value = ["tag1:value1"]
368+
submit_batch_item_failures_metric(response, context)
369+
self.mock_lambda_metric.assert_called_once_with(
370+
"aws.lambda.enhanced.batch_item_failures",
371+
0,
372+
timestamp=None,
373+
tags=["tag1:value1"],
374+
force_async=True,
375+
)
376+
377+
def test_submit_batch_item_failures_with_no_field(self):
378+
response = {"statusCode": 200}
379+
context = unittest.mock.Mock()
380+
submit_batch_item_failures_metric(response, context)
381+
self.mock_lambda_metric.assert_not_called()
382+
383+
def test_submit_batch_item_failures_with_none_response(self):
384+
response = None
385+
context = unittest.mock.Mock()
386+
submit_batch_item_failures_metric(response, context)
387+
self.mock_lambda_metric.assert_not_called()
388+
389+
def test_submit_batch_item_failures_with_non_list_value(self):
390+
response = {"batchItemFailures": "invalid"}
391+
context = unittest.mock.Mock()
392+
submit_batch_item_failures_metric(response, context)
393+
self.mock_lambda_metric.assert_not_called()
394+
395+
@patch("datadog_lambda.config.Config.enhanced_metrics_enabled", False)
396+
def test_submit_batch_item_failures_enhanced_metrics_disabled(self):
397+
response = {
398+
"batchItemFailures": [
399+
{"itemIdentifier": "msg-1"},
400+
]
401+
}
402+
context = unittest.mock.Mock()
403+
submit_batch_item_failures_metric(response, context)
404+
self.mock_lambda_metric.assert_not_called()

tests/test_wrapper.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,3 +899,61 @@ def lambda_handler(event, context):
899899
assert response == expected_response
900900
assert len(LLMObs_enable_calls) == 1
901901
assert len(LLMObs_flush_calls) == 1
902+
903+
904+
@patch("datadog_lambda.config.Config.trace_enabled", False)
905+
def test_batch_item_failures_metric():
906+
with patch(
907+
"datadog_lambda.metric.submit_batch_item_failures_metric"
908+
) as mock_submit:
909+
910+
@wrapper.datadog_lambda_wrapper
911+
def lambda_handler(event, context):
912+
return {
913+
"batchItemFailures": [
914+
{"itemIdentifier": "msg-1"},
915+
{"itemIdentifier": "msg-2"},
916+
]
917+
}
918+
919+
lambda_handler({}, get_mock_context())
920+
mock_submit.assert_called_once()
921+
call_args = mock_submit.call_args[0]
922+
assert call_args[0] == {
923+
"batchItemFailures": [
924+
{"itemIdentifier": "msg-1"},
925+
{"itemIdentifier": "msg-2"},
926+
]
927+
}
928+
929+
930+
@patch("datadog_lambda.config.Config.trace_enabled", False)
931+
def test_batch_item_failures_metric_no_failures():
932+
with patch(
933+
"datadog_lambda.metric.submit_batch_item_failures_metric"
934+
) as mock_submit:
935+
936+
@wrapper.datadog_lambda_wrapper
937+
def lambda_handler(event, context):
938+
return {"batchItemFailures": []}
939+
940+
lambda_handler({}, get_mock_context())
941+
mock_submit.assert_called_once()
942+
call_args = mock_submit.call_args[0]
943+
assert call_args[0] == {"batchItemFailures": []}
944+
945+
946+
@patch("datadog_lambda.config.Config.trace_enabled", False)
947+
def test_batch_item_failures_metric_no_response():
948+
with patch(
949+
"datadog_lambda.metric.submit_batch_item_failures_metric"
950+
) as mock_submit:
951+
952+
@wrapper.datadog_lambda_wrapper
953+
def lambda_handler(event, context):
954+
return None
955+
956+
lambda_handler({}, get_mock_context())
957+
mock_submit.assert_called_once()
958+
call_args = mock_submit.call_args[0]
959+
assert call_args[0] is None

0 commit comments

Comments
 (0)