Skip to content

Commit cc6c3e9

Browse files
committed
feat: Add batchItemFailures metric
1 parent 93d4a07 commit cc6c3e9

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

datadog_lambda/metric.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,27 @@ 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("aws.lambda.enhanced.batch_item_failures", len(batch_item_failures), timestamp=None, tags=get_enhanced_metrics_tags(lambda_context), force_async=True)
236+
237+
217238
def submit_dynamodb_stream_type_metric(event):
218239
stream_view_type = (
219240
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: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,3 +899,52 @@ 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("datadog_lambda.metric.submit_batch_item_failures_metric") as mock_submit:
907+
@wrapper.datadog_lambda_wrapper
908+
def lambda_handler(event, context):
909+
return {
910+
"batchItemFailures": [
911+
{"itemIdentifier": "msg-1"},
912+
{"itemIdentifier": "msg-2"},
913+
]
914+
}
915+
916+
lambda_handler({}, get_mock_context())
917+
mock_submit.assert_called_once()
918+
call_args = mock_submit.call_args[0]
919+
assert call_args[0] == {
920+
"batchItemFailures": [
921+
{"itemIdentifier": "msg-1"},
922+
{"itemIdentifier": "msg-2"},
923+
]
924+
}
925+
926+
927+
@patch("datadog_lambda.config.Config.trace_enabled", False)
928+
def test_batch_item_failures_metric_no_failures():
929+
with patch("datadog_lambda.metric.submit_batch_item_failures_metric") as mock_submit:
930+
@wrapper.datadog_lambda_wrapper
931+
def lambda_handler(event, context):
932+
return {"batchItemFailures": []}
933+
934+
lambda_handler({}, get_mock_context())
935+
mock_submit.assert_called_once()
936+
call_args = mock_submit.call_args[0]
937+
assert call_args[0] == {"batchItemFailures": []}
938+
939+
940+
@patch("datadog_lambda.config.Config.trace_enabled", False)
941+
def test_batch_item_failures_metric_no_response():
942+
with patch("datadog_lambda.metric.submit_batch_item_failures_metric") as mock_submit:
943+
@wrapper.datadog_lambda_wrapper
944+
def lambda_handler(event, context):
945+
return None
946+
947+
lambda_handler({}, get_mock_context())
948+
mock_submit.assert_called_once()
949+
call_args = mock_submit.call_args[0]
950+
assert call_args[0] is None

0 commit comments

Comments
 (0)