Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions kedro/io/data_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
)
from kedro.io.memory_dataset import MemoryDataset, _is_memory_dataset
from kedro.io.shared_memory_dataset import SharedMemoryDataset
from kedro.utils import _format_rich, _has_rich_handler
from kedro.utils import _format_rich, _has_rich_handler, _has_only_rich_handlers

if TYPE_CHECKING:
from multiprocessing.managers import SyncManager
Expand Down Expand Up @@ -266,7 +266,9 @@ def __init__(
datasets, load_versions or {}, save_version
)

self._use_rich_markup = _has_rich_handler()
# Only enable rich markup if ALL handlers are RichHandlers to prevent markup leakage
# This avoids leaking rich markup tags to file handlers when mixed handlers are present
self._use_rich_markup = _has_only_rich_handlers()

for ds_name in list(self._config_resolver.config):
if ds_name in self._datasets:
Expand Down
4 changes: 2 additions & 2 deletions kedro/pipeline/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ def run(self, inputs: dict[str, Any] | None = None) -> dict[str, Any]:
keys are defined by the node outputs.

"""
self._logger.info("Running node: %s", str(self))
self._logger.info("Running node: %s", str(self), extra={"markup": False})

outputs = None

Expand Down Expand Up @@ -446,7 +446,7 @@ def run(self, inputs: dict[str, Any] | None = None) -> dict[str, Any]:
"Node %s failed with error: \n%s",
str(self),
str(exc),
extra={"markup": True},
extra={"markup": False},
)
raise exc

Expand Down
17 changes: 17 additions & 0 deletions kedro/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,23 @@ def _has_rich_handler(logger: logging.Logger | None = None) -> bool:
return any(isinstance(handler, RichHandler) for handler in logger.handlers)


def _has_only_rich_handlers(logger: logging.Logger | None = None) -> bool:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this method is very similar to _has_rich_handlers, could we just use this new method everywhere instead?

"""Returns true if the logger has only RichHandler attached (no other handler types)."""
if not logger:
logger = logging.getLogger() # Use root by default
try:
from rich.logging import RichHandler
except ImportError:
# If Rich is not available, no RichHandlers exist
return False

if not logger.handlers:
# If no handlers, return True (safe default)
return True

return all(isinstance(handler, RichHandler) for handler in logger.handlers)


def _format_rich(value: str, markup: str) -> str:
"""Format string with rich markup"""
return f"[{markup}]{value}[/{markup}]"
Loading