Skip to content

Commit 0794a70

Browse files
github-actions[bot]highlyavailable
authored andcommitted
[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 e25351d) Co-authored-by: Peter Bryant <[email protected]>
1 parent 665b714 commit 0794a70

File tree

2 files changed

+117
-18
lines changed

2 files changed

+117
-18
lines changed

task-sdk/src/airflow/sdk/log.py

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def emit(self, record: logging.LogRecord):
140140

141141

142142
@cache
143-
def logging_processors(enable_pretty_log: bool, mask_secrets: bool = True):
143+
def logging_processors(enable_pretty_log: bool, mask_secrets: bool = True, colored_console_log: bool = True):
144144
if enable_pretty_log:
145145
timestamper = structlog.processors.MaybeTimeStamper(fmt="%Y-%m-%d %H:%M:%S.%f")
146146
else:
@@ -176,20 +176,34 @@ def logging_processors(enable_pretty_log: bool, mask_secrets: bool = True):
176176
)
177177

178178
if enable_pretty_log:
179-
rich_exc_formatter = structlog.dev.RichTracebackFormatter(
180-
# These values are picked somewhat arbitrarily to produce useful-but-compact tracebacks. If
181-
# we ever need to change these then they should be configurable.
182-
extra_lines=0,
183-
max_frames=30,
184-
indent_guides=False,
185-
suppress=suppress,
186-
)
187-
my_styles = structlog.dev.ConsoleRenderer.get_default_level_styles()
188-
my_styles["debug"] = structlog.dev.CYAN
179+
if colored_console_log:
180+
rich_exc_formatter = structlog.dev.RichTracebackFormatter(
181+
# These values are picked somewhat arbitrarily to produce useful-but-compact tracebacks. If
182+
# we ever need to change these then they should be configurable.
183+
extra_lines=0,
184+
max_frames=30,
185+
indent_guides=False,
186+
suppress=suppress,
187+
)
188+
my_styles = structlog.dev.ConsoleRenderer.get_default_level_styles()
189+
my_styles["debug"] = structlog.dev.CYAN
189190

190-
console = structlog.dev.ConsoleRenderer(
191-
exception_formatter=rich_exc_formatter, level_styles=my_styles
192-
)
191+
console = structlog.dev.ConsoleRenderer(
192+
exception_formatter=rich_exc_formatter, level_styles=my_styles
193+
)
194+
else:
195+
# Create a console renderer without colors - use the same RichTracebackFormatter
196+
# but rely on ConsoleRenderer(colors=False) to disable colors
197+
rich_exc_formatter = structlog.dev.RichTracebackFormatter(
198+
extra_lines=0,
199+
max_frames=30,
200+
indent_guides=False,
201+
suppress=suppress,
202+
)
203+
console = structlog.dev.ConsoleRenderer(
204+
colors=False,
205+
exception_formatter=rich_exc_formatter,
206+
)
193207
processors.append(console)
194208
return processors, {
195209
"timestamper": timestamper,
@@ -252,22 +266,30 @@ def configure_logging(
252266
output: BinaryIO | TextIO | None = None,
253267
cache_logger_on_first_use: bool = True,
254268
sending_to_supervisor: bool = False,
269+
colored_console_log: bool | None = None,
255270
):
256271
"""Set up struct logging and stdlib logging config."""
257272
if log_level == "DEFAULT":
258273
log_level = "INFO"
259-
if "airflow.configuration" in sys.modules:
260-
from airflow.configuration import conf
274+
from airflow.configuration import conf
275+
276+
log_level = conf.get("logging", "logging_level", fallback="INFO")
261277

262-
log_level = conf.get("logging", "logging_level", fallback="INFO")
278+
# If colored_console_log is not explicitly set, read from configuration
279+
if colored_console_log is None:
280+
from airflow.configuration import conf
281+
282+
colored_console_log = conf.getboolean("logging", "colored_console_log", fallback=True)
263283

264284
lvl = structlog.stdlib.NAME_TO_LEVEL[log_level.lower()]
265285

266286
if enable_pretty_log:
267287
formatter = "colored"
268288
else:
269289
formatter = "plain"
270-
processors, named = logging_processors(enable_pretty_log, mask_secrets=not sending_to_supervisor)
290+
processors, named = logging_processors(
291+
enable_pretty_log, mask_secrets=not sending_to_supervisor, colored_console_log=colored_console_log
292+
)
271293
timestamper = named["timestamper"]
272294

273295
pre_chain: list[structlog.typing.Processor] = [

task-sdk/tests/task_sdk/log/test_log.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,80 @@ def test_logs_are_masked(captured_logs):
147147
"try_number=1, map_index=-1, hostname=None, context_carrier=None)",
148148
"timestamp": "2025-03-25T05:13:27.073918Z",
149149
}
150+
151+
152+
def test_logging_processors_with_colors():
153+
"""Test that logging_processors creates colored console renderer when colored_console_log=True."""
154+
from airflow.sdk.log import logging_processors
155+
156+
_, named = logging_processors(enable_pretty_log=True, colored_console_log=True)
157+
assert "console" in named
158+
console_renderer = named["console"]
159+
assert hasattr(console_renderer, "_styles")
160+
161+
162+
def test_logging_processors_without_colors():
163+
"""Test that logging_processors creates non-colored console renderer when colored_console_log=False."""
164+
from airflow.sdk.log import logging_processors
165+
166+
_, named = logging_processors(enable_pretty_log=True, colored_console_log=False)
167+
assert "console" in named
168+
console_renderer = named["console"]
169+
assert hasattr(console_renderer, "_styles")
170+
assert console_renderer._styles.__name__ == "_PlainStyles"
171+
172+
173+
def test_logging_processors_json_format():
174+
"""Test that logging_processors creates JSON renderer when enable_pretty_log=False."""
175+
from airflow.sdk.log import logging_processors
176+
177+
_, named = logging_processors(enable_pretty_log=False, colored_console_log=True)
178+
assert "console" not in named
179+
assert "json" in named
180+
181+
182+
def test_configure_logging_respects_colored_console_log_config():
183+
"""Test that configure_logging respects the colored_console_log configuration."""
184+
from airflow.sdk.log import configure_logging, reset_logging
185+
186+
mock_conf = mock.MagicMock()
187+
mock_conf.get.return_value = "INFO"
188+
mock_conf.getboolean.return_value = False # colored_console_log = False
189+
190+
with mock.patch("airflow.configuration.conf", mock_conf):
191+
reset_logging()
192+
configure_logging(enable_pretty_log=True)
193+
# Check that getboolean was called with colored_console_log
194+
calls = [call for call in mock_conf.getboolean.call_args_list if call[0][1] == "colored_console_log"]
195+
assert len(calls) == 1
196+
assert calls[0] == mock.call("logging", "colored_console_log", fallback=True)
197+
198+
199+
def test_configure_logging_explicit_colored_console_log():
200+
"""Test that configure_logging respects explicit colored_console_log parameter."""
201+
from airflow.sdk.log import configure_logging, reset_logging
202+
203+
mock_conf = mock.MagicMock()
204+
mock_conf.get.return_value = "INFO"
205+
mock_conf.getboolean.return_value = True # colored_console_log = True
206+
207+
with mock.patch("airflow.configuration.conf", mock_conf):
208+
reset_logging()
209+
# Explicitly disable colors despite config saying True
210+
configure_logging(enable_pretty_log=True, colored_console_log=False)
211+
mock_conf.getboolean.assert_not_called()
212+
213+
214+
def test_configure_logging_no_airflow_config():
215+
"""Test that configure_logging defaults work correctly."""
216+
from airflow.sdk.log import configure_logging, reset_logging
217+
218+
# This test can be removed or repurposed since we now always import airflow.configuration
219+
mock_conf = mock.MagicMock()
220+
mock_conf.get.return_value = "INFO"
221+
mock_conf.getboolean.return_value = True # colored_console_log = True by default
222+
223+
with mock.patch("airflow.configuration.conf", mock_conf):
224+
reset_logging()
225+
configure_logging(enable_pretty_log=True)
226+
mock_conf.getboolean.assert_called_with("logging", "colored_console_log", fallback=True)

0 commit comments

Comments
 (0)