diff --git a/autotest/utils_tests.py b/autotest/utils_tests.py index 4d34ff5ed..ac24b1033 100644 --- a/autotest/utils_tests.py +++ b/autotest/utils_tests.py @@ -2099,10 +2099,15 @@ def run_sp_capture_output_test(tmp_path): else: shell = False log_file = os.path.join(tmp_path, "pyemu.log") - pyemu.os_utils.run("echo Hello World", - verbose=False, use_sp=True, - shell=shell, cwd=tmp_path, logfile=True) - + pyemu.os_utils.run( + "echo Hello World", + verbose=False, + use_sp=True, + shell=shell, + cwd=tmp_path, + logfile=log_file, + ) + with open(log_file, 'r') as f: content = f.read() assert "Hello World" in content diff --git a/pyemu/utils/log_utils.py b/pyemu/utils/log_utils.py new file mode 100644 index 000000000..646eb40d1 --- /dev/null +++ b/pyemu/utils/log_utils.py @@ -0,0 +1,71 @@ +"""Utilities to help with logging.""" + +import logging +import sys +from typing import Any, Optional, Union + +FILE_HANDLER = logging.FileHandler("pyemu.log", delay=True) +STREAM_HANDLER = logging.StreamHandler(sys.stdout) + +LOGGER: Optional[logging.Logger] = None + + +def set_logger( + logger: Optional[logging.Logger] = None, + /, + **basic_config: Any, +) -> None: + """Set the global logger to be used by pyemu. + + Args: + logger (logging.Logger): the logger to be used. + **basic_config (Any): keyword arguments to `logging.basicConfig` + """ + global LOGGER + if logger is not None: + if basic_config: + raise ValueError( + "If a logger is passed no extra keyword arguments should be passed as well." + ) + else: + if not basic_config: + raise ValueError( + "If no logger is passed then keyword arguments for logging.basicConfig should be passed." + ) + logging.basicConfig(**basic_config) + logger = logging.getLogger("pyemu") + LOGGER = logger + + +def get_logger( + name: Optional[str] = "pyemu", + verbose: bool = False, + logfile: Union[bool , str] = False, +) -> logging.Logger: + """Get a logger instance. + + Used to either get + Args: + name (`str`): name of the logger (default: "pyemu") + logger (`bool` or `logging.Logger`): either a boolean indicating to write to + "pyemu.log" or a logger to return as is. + + Returns: + logging.Logger object + """ + if LOGGER is not None: + return LOGGER + logger = logging.getLogger(name) + logger.setLevel(logging.INFO) + + for handler in logger.handlers: + logger.removeHandler(handler) + if logfile is True: + logger.addHandler(FILE_HANDLER) + elif isinstance(logfile, str): + logger.addHandler(logging.FileHandler(logfile)) + + if verbose: + logger.addHandler(STREAM_HANDLER) + + return logger diff --git a/pyemu/utils/os_utils.py b/pyemu/utils/os_utils.py index d95ee26ea..ee67187c3 100644 --- a/pyemu/utils/os_utils.py +++ b/pyemu/utils/os_utils.py @@ -11,6 +11,7 @@ import time from datetime import datetime from ..pyemu_warnings import PyemuWarning +from . import log_utils ext = "" bin_path = os.path.join("..", "bin") @@ -173,8 +174,10 @@ def run_sp(cmd_str, cwd=".", verbose=True, logfile=False, **kwargs): # print warning if shell is True if shell: - warnings.warn("shell=True is not recommended and may cause issues, but hey! YOLO", PyemuWarning) - + warnings.warn( + "shell=True is not recommended and may cause issues, but hey! YOLO", + PyemuWarning, + ) bwd = os.getcwd() os.chdir(cwd) @@ -184,26 +187,19 @@ def run_sp(cmd_str, cwd=".", verbose=True, logfile=False, **kwargs): try: cmd_ins = [i for i in cmd_str.split()] - log_stream = open(os.path.join('pyemu.log'), 'w+', newline='') if logfile else None - with sp.Popen(cmd_ins, stdout=sp.PIPE, - stderr=sp.STDOUT, text=True, - shell=shell, bufsize=1) as process: + logger = log_utils.get_logger(verbose=verbose, logfile=logfile) + with sp.Popen( + cmd_ins, stdout=sp.PIPE, stderr=sp.STDOUT, text=True, shell=shell, bufsize=1 + ) as process: for line in process.stdout: - if verbose: - print(line, flush=True, end='') - if logfile: - log_stream.write(line.strip('\n')) - log_stream.flush() - process.wait() # wait for the process to finish + logger.info(line.rstrip("\n")) + process.wait() # wait for the process to finish retval = process.returncode except Exception as e: os.chdir(bwd) raise Exception("run() raised :{0}".format(str(e))) - finally: - if logfile: - log_stream.close() os.chdir(bwd) if "window" in platform.platform().lower():