From 1b9b1f870a80c08fcbcdab121a1c89a80b870cb1 Mon Sep 17 00:00:00 2001 From: skshetry <18718008+skshetry@users.noreply.github.com> Date: Fri, 11 Jul 2025 20:17:29 +0545 Subject: [PATCH] try to reset log-level after setting it (#10800) --- dvc/cli/__init__.py | 11 +++--- dvc/logger.py | 16 +++++++- dvc/repo/experiments/executor/base.py | 37 ++++++++++--------- pyproject.toml | 1 - tests/conftest.py | 10 ++--- tests/func/artifacts/test_artifacts.py | 9 ++++- tests/func/test_run_cache.py | 4 +- .../test_studio_live_experiments.py | 2 +- 8 files changed, 55 insertions(+), 35 deletions(-) diff --git a/dvc/cli/__init__.py b/dvc/cli/__init__.py index ffff0eb85..0bffaf18e 100644 --- a/dvc/cli/__init__.py +++ b/dvc/cli/__init__.py @@ -157,6 +157,8 @@ def main(argv=None): # noqa: C901, PLR0912, PLR0915 Returns: int: command's return code. """ + from contextlib import ExitStack + from dvc._debug import debugtools from dvc.config import ConfigError from dvc.exceptions import DvcException, NotDvcRepoError @@ -173,12 +175,11 @@ def main(argv=None): # noqa: C901, PLR0912, PLR0915 logging.disable(logging.INFO) args = None - - outer_log_level = logger.level - level = None + stack = ExitStack() try: args = parse_args(argv) + level = None if args.quiet: level = logging.CRITICAL elif args.verbose == 1: @@ -187,7 +188,7 @@ def main(argv=None): # noqa: C901, PLR0912, PLR0915 level = logging.TRACE # type: ignore[attr-defined] if level is not None: - set_loggers_level(level) + stack.enter_context(set_loggers_level(level)) if level and level <= logging.DEBUG: from platform import platform, python_implementation, python_version @@ -247,7 +248,7 @@ def main(argv=None): # noqa: C901, PLR0912, PLR0915 return ret finally: - logger.setLevel(outer_log_level) + stack.close() from dvc.repo.open_repo import clean_repos diff --git a/dvc/logger.py b/dvc/logger.py index 9175c7ec6..00555c4fb 100644 --- a/dvc/logger.py +++ b/dvc/logger.py @@ -5,6 +5,8 @@ import logging.config import logging.handlers import os import sys +from collections.abc import Iterator +from contextlib import contextmanager from typing import ClassVar import colorama @@ -174,9 +176,19 @@ def _iter_causes(exc): exc = exc.__cause__ -def set_loggers_level(level: int = logging.INFO) -> None: +@contextmanager +def set_loggers_level(level: int = logging.INFO) -> Iterator[None]: + ret: dict[logging.Logger, int] = {} for name in ["dvc", "dvc_objects", "dvc_data"]: - logging.getLogger(name).setLevel(level) + _logger = logging.getLogger(name) + ret[_logger] = _logger.getEffectiveLevel() + _logger.setLevel(level) + + try: + yield + finally: + for _logger, old_lvl in ret.items(): + _logger.setLevel(old_lvl) def setup(level: int = logging.INFO, log_colors: bool = True) -> None: diff --git a/dvc/repo/experiments/executor/base.py b/dvc/repo/experiments/executor/base.py index 3ff373242..6ac531532 100644 --- a/dvc/repo/experiments/executor/base.py +++ b/dvc/repo/experiments/executor/base.py @@ -1,20 +1,21 @@ -import logging import os import pickle import shutil from abc import ABC, abstractmethod from collections.abc import Iterable, Iterator -from contextlib import contextmanager +from contextlib import AbstractContextManager, contextmanager from dataclasses import asdict, dataclass from enum import IntEnum from itertools import chain from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Optional, Union +from funcy import nullcontext from scmrepo.exceptions import SCMError from dvc.env import DVC_EXP_AUTO_PUSH, DVC_EXP_GIT_REMOTE from dvc.exceptions import DvcException from dvc.log import logger +from dvc.logger import set_loggers_level from dvc.repo.experiments.exceptions import ExperimentExistsError from dvc.repo.experiments.refs import EXEC_BASELINE, EXEC_BRANCH, ExpRefInfo from dvc.repo.experiments.utils import to_studio_params @@ -466,8 +467,8 @@ class BaseExecutor(ABC): if queue is not None: queue.put((rev, os.getpid())) - if log_errors and log_level is not None: - cls._set_log_level(log_level) + + log_ctx = cls._set_log_level(log_level) if log_errors else nullcontext() exp_hash: Optional[str] = None exp_ref: Optional[ExpRefInfo] = None @@ -476,14 +477,17 @@ class BaseExecutor(ABC): if info.name: ui.write(f"Reproducing experiment '{info.name}'") - with cls._repro_dvc( - info, - infofile, - log_errors=log_errors, - copy_paths=copy_paths, - message=message, - **kwargs, - ) as dvc: + with ( + log_ctx, + cls._repro_dvc( + info, + infofile, + log_errors=log_errors, + copy_paths=copy_paths, + message=message, + **kwargs, + ) as dvc, + ): args, kwargs = cls._repro_args(dvc) if args: targets: Optional[Union[list, str]] = args[0] @@ -786,13 +790,10 @@ class BaseExecutor(ABC): return orig_rev @staticmethod - def _set_log_level(level): - # When executor.reproduce is run in a multiprocessing child process, - # dvc.cli.main will not be called for that child process so we need to - # setup logging ourselves - dvc_logger = logging.getLogger("dvc") + def _set_log_level(level: Optional[int]) -> AbstractContextManager[None]: if level is not None: - dvc_logger.setLevel(level) + return set_loggers_level(level) + return nullcontext() @staticmethod def _copy_path(src, dst): diff --git a/pyproject.toml b/pyproject.toml index 2cb5898bb..8d13ce7f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,7 +175,6 @@ filterwarnings = [ "ignore:unclosed database.*sqlite3.Connection:ResourceWarning", "ignore:unclosed.*