import sys
import logging
import subprocess
[docs]
def check_call(args, logger=None, log_command=True, timeout=None, **kwargs):
    """
    Call the given subprocess with logging to the given logger.
    Parameters
    ----------
    args : list or str
        A list or string of argument to the subprocess.  If ``args`` is a
        string, you must pass ``shell=True`` as one of the ``kwargs``.
    logger : logging.Logger, optional
        The logger to write output to
    log_command : bool, optional
        Whether to write the command that is running ot the logger
    timeout : int, optional
        A timeout in seconds for the call
    **kwargs : dict
        Keyword arguments to pass to subprocess.Popen
    Raises
    ------
    subprocess.CalledProcessError
        If the given subprocess exists with nonzero status
    """
    if isinstance(args, str):
        print_args = args
    else:
        print_args = ' '.join(args)
    # make a logger if there isn't already one
    with LoggingContext(print_args, logger=logger) as logger:
        if log_command:
            logger.info(f'Running: {print_args}')
        process = subprocess.Popen(args, stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE, **kwargs)
        stdout, stderr = process.communicate(timeout=timeout)
        if stdout:
            stdout = stdout.decode('utf-8')
            for line in stdout.split('\n'):
                logger.info(line)
        if stderr:
            stderr = stderr.decode('utf-8')
            for line in stderr.split('\n'):
                logger.error(line)
        if process.returncode != 0:
            raise subprocess.CalledProcessError(process.returncode,
                                                print_args) 
[docs]
class LoggingContext(object):
    """
    A context manager for creating a logger or using an existing logger
    Attributes
    ----------
    logger : logging.Logger
        A logger that sends output to a log file or stdout/stderr
    """
[docs]
    def __init__(self, name, logger=None, log_filename=None):
        """
        If ``logger`` is ``None``, create a new logger either to a log file
        or stdout/stderr.  If ``logger`` is anything else, just set the logger
        attribute
        Parameters
        ----------
        name : str
            A unique name for the logger (e.g. ``__name__`` of the calling
            module)
        logger : logging.Logger, optional
           An existing logger that sends output to a log file or stdout/stderr
           to be used in this context
        log_filename : str, optional
            The name of a file where output should be written.  If none is
            supplied, output goes to stdout/stderr
        """
        self.logger = logger
        self.name = name
        self.log_filename = log_filename
        self.handler = None
        self.old_stdout = None
        self.old_stderr = None
        self.existing_logger = logger is not None 
    def __enter__(self):
        if not self.existing_logger:
            if self.log_filename is not None:
                # redirect output to a log file
                logger = logging.getLogger(self.name)
                handler = logging.FileHandler(self.log_filename)
            else:
                logger = logging.getLogger(self.name)
                handler = logging.StreamHandler(sys.stdout)
            formatter = MpasFormatter()
            handler.setFormatter(formatter)
            logger.addHandler(handler)
            logger.setLevel(logging.INFO)
            logger.propagate = False
            self.logger = logger
            self.handler = handler
            if self.log_filename is not None:
                self.old_stdout = sys.stdout
                self.old_stderr = sys.stderr
                sys.stdout = StreamToLogger(logger, logging.INFO)
                sys.stderr = StreamToLogger(logger, logging.ERROR)
        return self.logger
    def __exit__(self, exc_type, exc_val, exc_tb):
        if not self.existing_logger:
            if self.old_stdout is not None:
                self.handler.close()
                # restore stdout and stderr
                sys.stdout = self.old_stdout
                sys.stderr = self.old_stderr
            # remove the handlers from the logger (probably only necessary if
            # writeLogFile==False)
            self.logger.handlers = []
        self.stdout = self.original_stdout = sys.stdout
        self.stderr = self.original_stderr = sys.stderr 
class MpasFormatter(logging.Formatter):
    """
    A custom formatter for logging
    Modified from:
    https://stackoverflow.com/a/8349076/7728169
    https://stackoverflow.com/a/14859558/7728169
    """
    # Authors
    # -------
    # Xylar Asay-Davis
    # printing error messages without a prefix because they are sometimes
    # errors and sometimes only warnings sent to stderr
    dbg_fmt = "DEBUG: %(module)s: %(lineno)d: %(msg)s"
    info_fmt = "%(msg)s"
    err_fmt = info_fmt
    def __init__(self, fmt=info_fmt):
        logging.Formatter.__init__(self, fmt)
    def format(self, record):
        # Save the original format configured by the user
        # when the logger formatter was instantiated
        format_orig = self._fmt
        # Replace the original format with one customized by logging level
        if record.levelno == logging.DEBUG:
            self._fmt = MpasFormatter.dbg_fmt
        elif record.levelno == logging.INFO:
            self._fmt = MpasFormatter.info_fmt
        elif record.levelno == logging.ERROR:
            self._fmt = MpasFormatter.err_fmt
        # Call the original formatter class to do the grunt work
        result = logging.Formatter.format(self, record)
        # Restore the original format configured by the user
        self._fmt = format_orig
        return result
class StreamToLogger(object):
    """
    Modified based on code by:
    https://www.electricmonk.nl/log/2011/08/14/redirect-stdout-and-stderr-to-a-logger-in-python/
    Copyright (C) 2011 Ferry Boender
    License: GPL, see https://www.electricmonk.nl/log/posting-license/
    Fake file-like stream object that redirects writes to a logger instance.
    """
    def __init__(self, logger, log_level=logging.INFO):
        self.logger = logger
        self.log_level = log_level
        self.linebuf = ''
    def write(self, buf):
        for line in buf.rstrip().splitlines():
            self.logger.log(self.log_level, str(line.rstrip()))
    def flush(self):
        pass