Here is a complete, nicely wrapped solution based on Waterboy's answer and various other sources. It supports logging to both console and log file, allows for different log level settings, provides colorized output and is easily configurable (also available as Gist):
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------- # - # Python dual-logging setup (console and log file), - # supporting different log levels and colorized output - # - # Created by Fonic <https://github.com/fonic> - # Date: 04/05/20 - 02/07/23 - # - # Based on: - # https://stackoverflow.com/a/13733863/1976617 - # https://uran198.github.io/en/python/2016/07/12/colorful-python-logging.html - # https://en.wikipedia.org/wiki/ANSI_escape_code#Colors - # - # ------------------------------------------------------------------------------- # Imports import os import sys import logging # Logging formatter supporting colorized output class LogFormatter(logging.Formatter): COLOR_CODES = { logging.CRITICAL: "\033[1;35m", # bright/bold magenta logging.ERROR: "\033[1;31m", # bright/bold red logging.WARNING: "\033[1;33m", # bright/bold yellow logging.INFO: "\033[0;37m", # white / light gray logging.DEBUG: "\033[1;30m" # bright/bold dark gray } RESET_CODE = "\033[0m" def __init__(self, color, *args, **kwargs): super(LogFormatter, self).__init__(*args, **kwargs) self.color = color def format(self, record, *args, **kwargs): if (self.color == True and record.levelno in self.COLOR_CODES): record.color_on = self.COLOR_CODES[record.levelno] record.color_off = self.RESET_CODE else: record.color_on = "" record.color_off = "" return super(LogFormatter, self).format(record, *args, **kwargs) # Set up logging def set_up_logging(console_log_output, console_log_level, console_log_color, logfile_file, logfile_log_level, logfile_log_color, log_line_template): # Create logger # For simplicity, we use the root logger, i.e. call 'logging.getLogger()' # without name argument. This way we can simply use module methods for # for logging throughout the script. An alternative would be exporting # the logger, i.e. 'global logger; logger = logging.getLogger("<name>")' logger = logging.getLogger() # Set global log level to 'debug' (required for handler levels to work) logger.setLevel(logging.DEBUG) # Create console handler console_log_output = console_log_output.lower() if (console_log_output == "stdout"): console_log_output = sys.stdout elif (console_log_output == "stderr"): console_log_output = sys.stderr else: print("Failed to set console output: invalid output: '%s'" % console_log_output) return False console_handler = logging.StreamHandler(console_log_output) # Set console log level try: console_handler.setLevel(console_log_level.upper()) # only accepts uppercase level names except: print("Failed to set console log level: invalid level: '%s'" % console_log_level) return False # Create and set formatter, add console handler to logger console_formatter = LogFormatter(fmt=log_line_template, color=console_log_color) console_handler.setFormatter(console_formatter) logger.addHandler(console_handler) # Create log file handler try: logfile_handler = logging.FileHandler(logfile_file) except Exception as exception: print("Failed to set up log file: %s" % str(exception)) return False # Set log file log level try: logfile_handler.setLevel(logfile_log_level.upper()) # only accepts uppercase level names except: print("Failed to set log file log level: invalid level: '%s'" % logfile_log_level) return False # Create and set formatter, add log file handler to logger logfile_formatter = LogFormatter(fmt=log_line_template, color=logfile_log_color) logfile_handler.setFormatter(logfile_formatter) logger.addHandler(logfile_handler) # Success return True # Main function def main(): # Set up logging script_name = os.path.splitext(os.path.basename(sys.argv[0]))[0] if (not set_up_logging(console_log_output="stdout", console_log_level="warning", console_log_color=True, logfile_file=script_name + ".log", logfile_log_level="debug", logfile_log_color=False, log_line_template="%(color_on)s[%(created)d] [%(threadName)s] [%(levelname)-8s] %(message)s%(color_off)s")): print("Failed to set up logging, aborting.") return 1 # Log some messages logging.debug("Debug message") logging.info("Info message") logging.warning("Warning message") logging.error("Error message") logging.critical("Critical message") # Call main function if (__name__ == "__main__"): sys.exit(main())
NOTE regarding Microsoft Windows:
For colorized output to work within the classic Command Prompt of Microsoft Windows, some additional code is necessary. This does NOT apply to the newer Terminal app which supports colorized output out of the box.
There are two options:
1) Use Python package colorama (filters output sent to stdout and stderr and translates escape sequences to native Windows API calls; works on Windows XP and later):
import colorama colorama.init()
2) Enable ANSI terminal mode using the following function (enables terminal to interpret escape sequences by setting flag ENABLE_VIRTUAL_TERMINAL_PROCESSING; more info on this here, here, here and here; works on Windows 10 and later):
# Imports import sys import ctypes # Enable ANSI terminal mode for Command Prompt on Microsoft Windows def windows_enable_ansi_terminal_mode(): if (sys.platform != "win32"): return None try: kernel32 = ctypes.windll.kernel32 result = kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) if (result == 0): raise Exception return True except: return False
DEBUGdoes it still log to stdout AND the file? I think it's only if its set toINFO. Correct me if I'm wrong please.