From 749a6e526fb3be9ce31a94f452c5b3f96045fedf Mon Sep 17 00:00:00 2001 From: Marc Bestmann Date: Mon, 29 Jan 2024 13:18:17 +0100 Subject: [PATCH] add format overriding by environment variables (#722) * add format overriding by environment variables Signed-off-by: Marc Bestmann * add test for env var based formatting Signed-off-by: Marc Bestmann --------- Signed-off-by: Marc Bestmann --- launch/launch/logging/__init__.py | 22 ++++++++++++++++++++ launch/test/launch/test_logging.py | 33 ++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/launch/launch/logging/__init__.py b/launch/launch/logging/__init__.py index 30e6a738b..8d5e73fa6 100644 --- a/launch/launch/logging/__init__.py +++ b/launch/launch/logging/__init__.py @@ -206,10 +206,21 @@ def set_screen_format(self, screen_format, *, screen_style=None): :param screen_format: format specification used when logging to the screen, as expected by the `logging.Formatter` constructor. Alternatively, aliases for common formats are available, see above. + This format can also be overridden by the environment variable + 'OVERRIDE_LAUNCH_SCREEN_FORMAT'. :param screen_style: the screen style used if no alias is used for screen_format. No style can be provided if a format alias is given. """ + # Check if the environment variable is set + screen_format_env = os.environ.get('OVERRIDE_LAUNCH_SCREEN_FORMAT') + # If the environment variable is set override the given format + if screen_format_env not in [None, '']: + # encoded escape characters correctly + screen_format = screen_format_env.encode( + 'latin1').decode('unicode_escape') + # Set the style correspondingly + screen_style = '{' if screen_format is not None: if screen_format == 'default': screen_format = '[{levelname}] [{name}]: {msg}' @@ -258,9 +269,20 @@ def set_log_format(self, log_format, *, log_style=None): as expected by the `logging.Formatter` constructor. Alternatively, the 'default' alias can be given to log verbosity level, logger name and logged message. + This format can also be overridden by the environment variable + 'OVERRIDE_LAUNCH_LOG_FORMAT'. :param log_style: the log style used if no alias is given for log_format. No style can be provided if a format alias is given. """ + # Check if the environment variable is set + log_format_env = os.environ.get('OVERRIDE_LAUNCH_LOG_FORMAT') + # If the environment variable is set override the given format + if log_format_env not in [None, '']: + # encoded escape characters correctly + log_format = log_format_env.encode( + 'latin1').decode('unicode_escape') + # Set the style correspondingly + log_style = '{' if log_format is not None: if log_format == 'default': log_format = '{created:.7f} [{levelname}] [{name}]: {msg}' diff --git a/launch/test/launch/test_logging.py b/launch/test/launch/test_logging.py index 18fc54c75..631fdad11 100644 --- a/launch/test/launch/test_logging.py +++ b/launch/test/launch/test_logging.py @@ -31,7 +31,13 @@ def log_dir(tmpdir_factory): return str(tmpdir_factory.mktemp('logs')) -def test_bad_logging_launch_config(): +@pytest.fixture +def mock_clean_env(monkeypatch): + monkeypatch.delenv('OVERRIDE_LAUNCH_SCREEN_FORMAT', raising=False) + monkeypatch.delenv('OVERRIDE_LAUNCH_LOG_FORMAT', raising=False) + + +def test_bad_logging_launch_config(mock_clean_env): """Tests that setup throws at bad configuration.""" launch.logging.reset() @@ -83,7 +89,7 @@ def test_output_loggers_bad_configuration(log_dir): }, ) ]) -def test_output_loggers_configuration(capsys, log_dir, config, checks): +def test_output_loggers_configuration(capsys, log_dir, config, checks, mock_clean_env): checks = {'stdout': set(), 'stderr': set(), 'both': set(), **checks} launch.logging.reset() launch.logging.launch_config.log_dir = log_dir @@ -162,7 +168,7 @@ def test_output_loggers_configuration(capsys, log_dir, config, checks): assert (not os.path.exists(own_log_path) or 0 == os.stat(own_log_path).st_size) -def test_screen_default_format_with_timestamps(capsys, log_dir): +def test_screen_default_format_with_timestamps(capsys, log_dir, mock_clean_env): """Test screen logging when using the default logs format with timestamps.""" launch.logging.reset() launch.logging.launch_config.level = logging.DEBUG @@ -181,7 +187,7 @@ def test_screen_default_format_with_timestamps(capsys, log_dir): assert 0 == len(capture.err) -def test_screen_default_format(capsys): +def test_screen_default_format(capsys, mock_clean_env): """Test screen logging when using the default logs format.""" launch.logging.reset() @@ -218,6 +224,25 @@ def test_log_default_format(log_dir): assert re.match(r'[0-9]+\.[0-9]+ \[ERROR\] \[some-proc\]: baz', lines[0]) is not None +def test_logging_env_var_format(capsys, monkeypatch): + monkeypatch.setenv('OVERRIDE_LAUNCH_SCREEN_FORMAT', 'TESTSCREEN {message} {name} TESTSCREEN') + monkeypatch.setenv('OVERRIDE_LAUNCH_LOG_FORMAT', 'TESTLOG {message} {name} TESTLOG') + launch.logging.reset() + + logger = launch.logging.get_logger('some-proc') + logger.addHandler(launch.logging.launch_config.get_screen_handler()) + + logger.info('bar') + capture = capsys.readouterr() + lines = capture.out.splitlines() + assert 'TESTSCREEN bar some-proc TESTSCREEN' == lines.pop() + + launch.logging.launch_config.get_log_file_handler().flush() + with open(launch.logging.launch_config.get_log_file_path(), 'r') as f: + lines = f.readlines() + assert 'TESTLOG bar some-proc TESTLOG\n' == lines[0] + + def test_log_handler_factory(log_dir): """Test logging using a custom log handlers.""" class TestStreamHandler(launch.logging.handlers.Handler):