diff --git a/mpsd-software-environment.py b/mpsd-software-environment.py index d1d3f0136a8979390b503e9cdbefc1c18715ee7f..b984f678a28e6347bcfc6cdda13b4744a0b96deb 100755 --- a/mpsd-software-environment.py +++ b/mpsd-software-environment.py @@ -156,7 +156,7 @@ def set_up_logging(loglevel="warning", filename=None): This function sets up the logging configuration for the script. It configures the log level, log format, and log handlers - for both file and console output. + for both file and console(=shell) output. Parameters @@ -170,34 +170,121 @@ def set_up_logging(loglevel="warning", filename=None): - filename to save logging messages into If loglevel is 'debug', save line numbers in log messages. + + Returns + ------- + None. + + Logger instances are generally not passed around, but retrieved from the + logging module as shown below (they are singletons). + + We provide two loggers: + + 1. log = logging.getLogger('') + + This is the 'root' logger. It uses a RichHandler if rich is available for + output to the shell, otherwise plain text. + + Typical use: + + log.debug("...") + log.info("...") + log.warn("...") + + Equivalent to + + logging.debug("...") + logging.info("...") + + 2. print_log = logging.getlogger('print') + + This uses the logging module to issue the message, but prints without + any further markup (i.e. no date, loglevel, line number, etc). Think + PRINT via the LOGging module. + + We use this as a replacement for the print function (i.e. for messages + that should not be affected by logging levels, and which should always + be printed). + + Typical and intended use: + + print_log.info("Available toolchains are ...") + + The major difference from the normal print command is that the output + will be send to the stdout (as for print) AND the file with name + filename, so that these messages appear in the log file together with + normal log output. + """ + # convert loglevel string into loglevel as number log_level_numeric = getattr(logging, loglevel.upper(), logging.WARNING) - assert log_level_numeric if not isinstance(log_level_numeric, int): raise ValueError("Invalid log level: %s" % loglevel) - handlers = [] - if filename: - handlers.append(logging.FileHandler(filename)) + # set up the main logger ("root" logger) + logger = logging.getLogger("") + # - "logger" logs everything + # - we use loglevel at handler level to write everything to file + # - and filter using log_level_numeric (as the user provides) to + # send logging messages to the console + logger.setLevel(0) + # the handler determines where the logs go: stdout/file if rich_available: - # set up logging as recommended for rich, see # https://rich.readthedocs.io/en/stable/logging.html - handlers.append(rich.logging.RichHandler()) + shell_handler = rich.logging.RichHandler() + # rich handler provides metadata automatically: logging_format = "%(message)s" - else: # rich not available, define our own output + # for shell output, only show time (not date and time) + shell_formatter = logging.Formatter(logging_format, datefmt="[%X]") + else: + shell_handler = logging.StreamHandler() # include line numbers in output if level is DEBUG linenumbers = " %(lineno)4d" if log_level_numeric == logging.DEBUG else "" - handlers.append(logging.StreamHandler()) logging_format = "%(asctime)s %(levelname)7s" + linenumbers + " | %(message)s" + shell_formatter = logging.Formatter(logging_format) - logging.basicConfig( - level=log_level_numeric, - format=logging_format, - datefmt="[%X]", - handlers=handlers, - force=True, - ) + # here we hook everything together + shell_handler.setFormatter(shell_formatter) + # use the log_level_numeric to decide how much logging is sent to shell + shell_handler.setLevel(log_level_numeric) + logger.addHandler(shell_handler) + + # if filename provided, write log messages to that file, too. + if filename: + file_handler = logging.FileHandler(filename) + # if we have a file, we write all information in there. + # We could change the level, for example restrict to only DEBUG and above with + # file_handler.setLevel(logging.DEBUG) + file_logging_format = "%(asctime)s %(levelname)7s %(lineno)4d | %(message)s" + file_formatter = logging.Formatter(file_logging_format, datefmt="[%X]") + file_handler.setFormatter(file_formatter) + logger.addHandler(file_handler) + + # + # new logger for printing + # + print_log = logging.getLogger("print") + print_log.setLevel(logging.INFO) + print_log.propagate = False + # create formatter 'empty' formatter + formatter = logging.Formatter("%(message)s") + + # create, format and add handler for shell output + ch = logging.StreamHandler() + ch.setFormatter(formatter) + print_log.addHandler(ch) + + # if filename provided, write output of print_log to that file, too + if filename: + # create, format and add file handler + fh = logging.FileHandler(filename) + fh.setFormatter(formatter) + print_log.addHandler(fh) + + # + # short message + # logging.debug( f"Logging has been setup, loglevel={loglevel.upper()}" + f"{filename=} {rich_available=}" @@ -651,6 +738,8 @@ def install_environment( "No toolchains requested. Available toolchains for release " f"{mpsd_release} are: \n {available_toolchains}" ) + print_log = logging.getLogger("print") + print_log.info(f"{available_toolchains=}") return for toolchain in toolchains: