This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-0-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-0-test by this push:
new 2e07d5962da [v3-0-test] Separate configurations for colorized and json
logs in Task SDK / Celery Executor (#51082) (#51344)
2e07d5962da is described below
commit 2e07d5962dab36fdc5a92e76fee26b227017799a
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Jun 3 15:51:21 2025 +0530
[v3-0-test] Separate configurations for colorized and json logs in Task SDK
/ Celery Executor (#51082) (#51344)
* Enhance logging configuration by adding 'enable_colors' parameter to
'logging_processors' and 'configure_logging' functions. This allows for
customizable console output with or without colors based on user preference or
configuration settings.
(cherry picked from commit e25351df3c8f0f5b4c5ba91870f2d256395f6edf)
Co-authored-by: Peter Bryant <[email protected]>
---
task-sdk/src/airflow/sdk/log.py | 58 +++++++++++++++++--------
task-sdk/tests/task_sdk/log/test_log.py | 77 +++++++++++++++++++++++++++++++++
2 files changed, 117 insertions(+), 18 deletions(-)
diff --git a/task-sdk/src/airflow/sdk/log.py b/task-sdk/src/airflow/sdk/log.py
index d11b8809f55..554a96cd9e6 100644
--- a/task-sdk/src/airflow/sdk/log.py
+++ b/task-sdk/src/airflow/sdk/log.py
@@ -140,7 +140,7 @@ class StdBinaryStreamHandler(logging.StreamHandler):
@cache
-def logging_processors(enable_pretty_log: bool, mask_secrets: bool = True):
+def logging_processors(enable_pretty_log: bool, mask_secrets: bool = True,
colored_console_log: bool = True):
if enable_pretty_log:
timestamper = structlog.processors.MaybeTimeStamper(fmt="%Y-%m-%d
%H:%M:%S.%f")
else:
@@ -176,20 +176,34 @@ def logging_processors(enable_pretty_log: bool,
mask_secrets: bool = True):
)
if enable_pretty_log:
- rich_exc_formatter = structlog.dev.RichTracebackFormatter(
- # These values are picked somewhat arbitrarily to produce
useful-but-compact tracebacks. If
- # we ever need to change these then they should be configurable.
- extra_lines=0,
- max_frames=30,
- indent_guides=False,
- suppress=suppress,
- )
- my_styles = structlog.dev.ConsoleRenderer.get_default_level_styles()
- my_styles["debug"] = structlog.dev.CYAN
+ if colored_console_log:
+ rich_exc_formatter = structlog.dev.RichTracebackFormatter(
+ # These values are picked somewhat arbitrarily to produce
useful-but-compact tracebacks. If
+ # we ever need to change these then they should be
configurable.
+ extra_lines=0,
+ max_frames=30,
+ indent_guides=False,
+ suppress=suppress,
+ )
+ my_styles =
structlog.dev.ConsoleRenderer.get_default_level_styles()
+ my_styles["debug"] = structlog.dev.CYAN
- console = structlog.dev.ConsoleRenderer(
- exception_formatter=rich_exc_formatter, level_styles=my_styles
- )
+ console = structlog.dev.ConsoleRenderer(
+ exception_formatter=rich_exc_formatter, level_styles=my_styles
+ )
+ else:
+ # Create a console renderer without colors - use the same
RichTracebackFormatter
+ # but rely on ConsoleRenderer(colors=False) to disable colors
+ rich_exc_formatter = structlog.dev.RichTracebackFormatter(
+ extra_lines=0,
+ max_frames=30,
+ indent_guides=False,
+ suppress=suppress,
+ )
+ console = structlog.dev.ConsoleRenderer(
+ colors=False,
+ exception_formatter=rich_exc_formatter,
+ )
processors.append(console)
return processors, {
"timestamper": timestamper,
@@ -252,14 +266,20 @@ def configure_logging(
output: BinaryIO | TextIO | None = None,
cache_logger_on_first_use: bool = True,
sending_to_supervisor: bool = False,
+ colored_console_log: bool | None = None,
):
"""Set up struct logging and stdlib logging config."""
if log_level == "DEFAULT":
log_level = "INFO"
- if "airflow.configuration" in sys.modules:
- from airflow.configuration import conf
+ from airflow.configuration import conf
+
+ log_level = conf.get("logging", "logging_level", fallback="INFO")
- log_level = conf.get("logging", "logging_level", fallback="INFO")
+ # If colored_console_log is not explicitly set, read from configuration
+ if colored_console_log is None:
+ from airflow.configuration import conf
+
+ colored_console_log = conf.getboolean("logging",
"colored_console_log", fallback=True)
lvl = structlog.stdlib.NAME_TO_LEVEL[log_level.lower()]
@@ -267,7 +287,9 @@ def configure_logging(
formatter = "colored"
else:
formatter = "plain"
- processors, named = logging_processors(enable_pretty_log, mask_secrets=not
sending_to_supervisor)
+ processors, named = logging_processors(
+ enable_pretty_log, mask_secrets=not sending_to_supervisor,
colored_console_log=colored_console_log
+ )
timestamper = named["timestamper"]
pre_chain: list[structlog.typing.Processor] = [
diff --git a/task-sdk/tests/task_sdk/log/test_log.py
b/task-sdk/tests/task_sdk/log/test_log.py
index 83b3bbd3170..15db5a801e9 100644
--- a/task-sdk/tests/task_sdk/log/test_log.py
+++ b/task-sdk/tests/task_sdk/log/test_log.py
@@ -147,3 +147,80 @@ def test_logs_are_masked(captured_logs):
"try_number=1, map_index=-1, hostname=None, context_carrier=None)",
"timestamp": "2025-03-25T05:13:27.073918Z",
}
+
+
+def test_logging_processors_with_colors():
+ """Test that logging_processors creates colored console renderer when
colored_console_log=True."""
+ from airflow.sdk.log import logging_processors
+
+ _, named = logging_processors(enable_pretty_log=True,
colored_console_log=True)
+ assert "console" in named
+ console_renderer = named["console"]
+ assert hasattr(console_renderer, "_styles")
+
+
+def test_logging_processors_without_colors():
+ """Test that logging_processors creates non-colored console renderer when
colored_console_log=False."""
+ from airflow.sdk.log import logging_processors
+
+ _, named = logging_processors(enable_pretty_log=True,
colored_console_log=False)
+ assert "console" in named
+ console_renderer = named["console"]
+ assert hasattr(console_renderer, "_styles")
+ assert console_renderer._styles.__name__ == "_PlainStyles"
+
+
+def test_logging_processors_json_format():
+ """Test that logging_processors creates JSON renderer when
enable_pretty_log=False."""
+ from airflow.sdk.log import logging_processors
+
+ _, named = logging_processors(enable_pretty_log=False,
colored_console_log=True)
+ assert "console" not in named
+ assert "json" in named
+
+
+def test_configure_logging_respects_colored_console_log_config():
+ """Test that configure_logging respects the colored_console_log
configuration."""
+ from airflow.sdk.log import configure_logging, reset_logging
+
+ mock_conf = mock.MagicMock()
+ mock_conf.get.return_value = "INFO"
+ mock_conf.getboolean.return_value = False # colored_console_log = False
+
+ with mock.patch("airflow.configuration.conf", mock_conf):
+ reset_logging()
+ configure_logging(enable_pretty_log=True)
+ # Check that getboolean was called with colored_console_log
+ calls = [call for call in mock_conf.getboolean.call_args_list if
call[0][1] == "colored_console_log"]
+ assert len(calls) == 1
+ assert calls[0] == mock.call("logging", "colored_console_log",
fallback=True)
+
+
+def test_configure_logging_explicit_colored_console_log():
+ """Test that configure_logging respects explicit colored_console_log
parameter."""
+ from airflow.sdk.log import configure_logging, reset_logging
+
+ mock_conf = mock.MagicMock()
+ mock_conf.get.return_value = "INFO"
+ mock_conf.getboolean.return_value = True # colored_console_log = True
+
+ with mock.patch("airflow.configuration.conf", mock_conf):
+ reset_logging()
+ # Explicitly disable colors despite config saying True
+ configure_logging(enable_pretty_log=True, colored_console_log=False)
+ mock_conf.getboolean.assert_not_called()
+
+
+def test_configure_logging_no_airflow_config():
+ """Test that configure_logging defaults work correctly."""
+ from airflow.sdk.log import configure_logging, reset_logging
+
+ # This test can be removed or repurposed since we now always import
airflow.configuration
+ mock_conf = mock.MagicMock()
+ mock_conf.get.return_value = "INFO"
+ mock_conf.getboolean.return_value = True # colored_console_log = True by
default
+
+ with mock.patch("airflow.configuration.conf", mock_conf):
+ reset_logging()
+ configure_logging(enable_pretty_log=True)
+ mock_conf.getboolean.assert_called_with("logging",
"colored_console_log", fallback=True)