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: