From 6885450686ec9f9fd44e75492feac644393a1043 Mon Sep 17 00:00:00 2001 From: eastagiletracker <310448263+eastagiletracker@users.noreply.github.com> Date: Thu, 13 Aug 2026 08:40:56 +0000 Subject: [PATCH] Detecting error responses returned by the datadog client, which does not raise them when it is muted (the default), and passing them to handleError so that events rejected by the API are reported instead of being silently dropped. --- datadog_logger/handler.py | 10 +++++- tests/test_datadog_logger.py | 62 ++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/datadog_logger/handler.py b/datadog_logger/handler.py index a619c71..c4c3e88 100644 --- a/datadog_logger/handler.py +++ b/datadog_logger/handler.py @@ -1,5 +1,6 @@ from collections.abc import Iterable from datadog.api.events import Event +from datadog.api.exceptions import ApiError import logging from typing import Any, Optional @@ -43,7 +44,14 @@ def emit(self, record: logging.LogRecord) -> None: if record.levelno in LOG_LEVEL_ALERT_TYPE_MAPPINGS: create_args["alert_type"] = LOG_LEVEL_ALERT_TYPE_MAPPINGS[record.levelno] - Event.create(**create_args) # type: ignore[arg-type] + response = Event.create(**create_args) # type: ignore[arg-type] + + # The datadog client is muted by default, which means it logs + # errors returned by the API and returns them to the caller + # instead of raising them, so failures to submit the event have + # to be detected here to be reported by handleError + if isinstance(response, dict) and response.get("errors"): + raise ApiError(response) except Exception: self.handleError(record) diff --git a/tests/test_datadog_logger.py b/tests/test_datadog_logger.py index edaedb0..29e82f7 100644 --- a/tests/test_datadog_logger.py +++ b/tests/test_datadog_logger.py @@ -215,3 +215,65 @@ def test_emit_calls_handle_error_when_it_raises_an_exception( handler.emit(record) mock_handle_error.assert_called_once_with(handler, record) + + @mock.patch("logging.Handler.handleError", autospec=True) + @mock.patch("datadog_logger.handler.Event", autospec=True) + def test_emit_calls_handle_error_when_response_contains_errors( + self, + mock_event_class: mock.Mock, + mock_handle_error: mock.Mock + ) -> None: + # The datadog client is muted by default, so it returns errors + # reported by the API instead of raising them + mock_event_class.create.return_value = {"errors": ["Forbidden"]} + + handler = DatadogLogHandler() + + record = logging.makeLogRecord({ + "msg": "Some message" + }) + + handler.emit(record) + + mock_handle_error.assert_called_once_with(handler, record) + + @mock.patch("logging.Handler.handleError", autospec=True) + @mock.patch("datadog_logger.handler.Event", autospec=True) + def test_emit_does_not_call_handle_error_when_event_is_created( + self, + mock_event_class: mock.Mock, + mock_handle_error: mock.Mock + ) -> None: + mock_event_class.create.return_value = { + "status": "ok", + "event": {"id": 1234, "title": "Some message"} + } + + handler = DatadogLogHandler() + + record = logging.makeLogRecord({ + "msg": "Some message" + }) + + handler.emit(record) + + mock_handle_error.assert_not_called() + + @mock.patch("logging.Handler.handleError", autospec=True) + @mock.patch("datadog_logger.handler.Event", autospec=True) + def test_emit_does_not_call_handle_error_when_response_has_no_errors( + self, + mock_event_class: mock.Mock, + mock_handle_error: mock.Mock + ) -> None: + mock_event_class.create.return_value = {"errors": []} + + handler = DatadogLogHandler() + + record = logging.makeLogRecord({ + "msg": "Some message" + }) + + handler.emit(record) + + mock_handle_error.assert_not_called()