diff --git a/src/openjd/adaptor_runtime/_entrypoint.py b/src/openjd/adaptor_runtime/_entrypoint.py index 5275ccf..17ca5d3 100644 --- a/src/openjd/adaptor_runtime/_entrypoint.py +++ b/src/openjd/adaptor_runtime/_entrypoint.py @@ -20,6 +20,10 @@ ConfigurationManager, ) from ._osname import OSName +from ._utils._logging import ( + _OPENJD_LOG_REGEX, + ConditionalFormatter, +) if TYPE_CHECKING: # pragma: no cover from .adaptors.configuration import AdaptorConfiguration @@ -92,7 +96,9 @@ def start(self) -> None: """ Starts the run of the adaptor. """ - formatter = logging.Formatter("%(levelname)s: %(message)s") + formatter = ConditionalFormatter( + "%(levelname)s: %(message)s", ignore_patterns=[_OPENJD_LOG_REGEX] + ) stream_handler = logging.StreamHandler(sys.stdout) stream_handler.setFormatter(formatter) diff --git a/src/openjd/adaptor_runtime/_utils/_logging.py b/src/openjd/adaptor_runtime/_utils/_logging.py new file mode 100644 index 0000000..8c4d6e3 --- /dev/null +++ b/src/openjd/adaptor_runtime/_utils/_logging.py @@ -0,0 +1,37 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +import logging +import re +from typing import ( + List, + Optional, +) + +_OPENJD_LOG_PATTERN = r"^openjd_\S+: " +_OPENJD_LOG_REGEX = re.compile(_OPENJD_LOG_PATTERN) + + +class ConditionalFormatter(logging.Formatter): + """ + A Formatter subclass that applies formatting conditionally. + """ + + def __init__( + self, + *args, + ignore_patterns: Optional[List[re.Pattern[str]]], + **kwargs, + ): + """ + Args: + ignore_patterns (Optional[List[re.Pattern[str]]]): List of patterns that, when matched, + indicate a log message must not be formatted (it is "ignored" by the formatter) + """ + self._ignore_patterns = ignore_patterns or [] + super().__init__(*args, **kwargs) + + def format(self, record: logging.LogRecord) -> str: + for ignore_pattern in self._ignore_patterns: + if ignore_pattern.match(record.msg): + return record.getMessage() + + return super().format(record) diff --git a/src/openjd/adaptor_runtime/adaptors/_adaptor_runner.py b/src/openjd/adaptor_runtime/adaptors/_adaptor_runner.py index 6b05893..3d3b1e8 100644 --- a/src/openjd/adaptor_runtime/adaptors/_adaptor_runner.py +++ b/src/openjd/adaptor_runtime/adaptors/_adaptor_runner.py @@ -81,6 +81,4 @@ def _cancel(self): def _fail(reason: str): - # TODO: Add a way to output "system" messages that ignore logging configuration. - # We don't ever want this message to get filtered out by Python's logging library. _logger.error(f"{_OPENJD_FAIL_STDOUT_PREFIX}{reason}") diff --git a/test/openjd/adaptor_runtime/unit/utils/test_logging.py b/test/openjd/adaptor_runtime/unit/utils/test_logging.py new file mode 100644 index 0000000..ffac161 --- /dev/null +++ b/test/openjd/adaptor_runtime/unit/utils/test_logging.py @@ -0,0 +1,57 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +import logging +import re +from typing import List +from unittest.mock import patch + +import pytest + +import openjd.adaptor_runtime._utils._logging as logging_mod +from openjd.adaptor_runtime._utils._logging import ConditionalFormatter + + +class TestConditionalFormatter: + @pytest.mark.parametrize( + ["patterns", "message", "should_be_ignored"], + [ + [ + [ + re.compile(r"^IGNORE:"), + ], + "IGNORE: This should be ignored", + True, + ], + [ + [re.compile(r"^IGNORE:"), re.compile(r"^IGNORE_TWO:")], + "IGNORE_TWO: This should also be ignored", + True, + ], + [ + [ + re.compile(r"^IGNORE:"), + ], + "INFO: This should not be ignored", + False, + ], + ], + ) + def test_ignores_patterns( + self, + patterns: List[re.Pattern[str]], + message: str, + should_be_ignored: bool, + ) -> None: + # GIVEN + record = logging.LogRecord("NAME", 0, "", 0, message, None, None) + formatter = ConditionalFormatter(ignore_patterns=patterns) + + # WHEN + with patch.object(logging_mod.logging.Formatter, "format") as mock_format: + formatter.format(record) + + # THEN + if should_be_ignored: + mock_format.assert_not_called() + else: + mock_format.assert_called_once_with(record)