diff --git a/.gitignore b/.gitignore index b2f753e..dd17746 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,71 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +#dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Pycharm +.idea/* + + .cache .eggs *.egg-info/ .*.swp *.pyc + diff --git a/coloredlogs/__init__.py b/coloredlogs/__init__.py index 6e49c98..cee8ac5 100644 --- a/coloredlogs/__init__.py +++ b/coloredlogs/__init__.py @@ -272,6 +272,8 @@ def install(level=None, **kw): :param fmt: Set the logging format (a string like those accepted by :class:`~logging.Formatter`, defaults to :data:`DEFAULT_LOG_FORMAT`). + :param overridefmt: A dictionary with custom level logging formats + :defaults to data:`DEFAULT_LOG_FORMAT`). :param datefmt: Set the date/time format (a string, defaults to :data:`DEFAULT_DATE_FORMAT`). :param milliseconds: :data:`True` to show milliseconds like :mod:`logging` @@ -396,7 +398,8 @@ def install(level=None, **kw): handler.setLevel(level) # Prepare the arguments to the formatter. The caller is # allowed to customize `fmt' and/or `datefmt' as desired. - formatter_options = dict(fmt=kw.get('fmt'), datefmt=kw.get('datefmt')) + formatter_options = dict(fmt=kw.get('fmt'), datefmt=kw.get('datefmt'), + overridefmt=kw.get('overridefmt')) # Come up with a default log format? if not formatter_options['fmt']: # Use the log format defined by the environment variable @@ -447,7 +450,10 @@ def install(level=None, **kw): if value is not None: formatter_options[name] = value # Create a (possibly colored) formatter. + if not use_colors: + formatter_options.pop('overridefmt', None) formatter_type = ColoredFormatter if use_colors else logging.Formatter + handler.setFormatter(formatter_type(**formatter_options)) # Adjust the level of the selected logger. adjust_level(logger, level) @@ -809,7 +815,9 @@ class ColoredFormatter(logging.Formatter): when you call :func:`coloredlogs.install()`. """ - def __init__(self, fmt=None, datefmt=None, level_styles=None, field_styles=None): + formatters = {} + + def __init__(self, fmt=None, datefmt=None, level_styles=None, field_styles=None, overridefmt=None): """ Initialize a :class:`ColoredFormatter` object. @@ -830,12 +838,28 @@ def __init__(self, fmt=None, datefmt=None, level_styles=None, field_styles=None) # that Sphinx doesn't embed the default values in the generated # documentation (because the result is awkward to read). fmt = fmt or DEFAULT_LOG_FORMAT + datefmt = datefmt or DEFAULT_DATE_FORMAT # Initialize instance attributes. self.level_styles = self.nn.normalize_keys(DEFAULT_LEVEL_STYLES if level_styles is None else level_styles) self.field_styles = self.nn.normalize_keys(DEFAULT_FIELD_STYLES if field_styles is None else field_styles) # Rewrite the format string to inject ANSI escape sequences and # initialize the superclass with the rewritten format string. + if overridefmt is not None and isinstance(overridefmt, dict): + + for level in ['INFO', 'WARNING', 'DEBUG', + 'CRITICAL', 'ERROR', 'VERBOSE', 'FATAL']: + try: + if overridefmt.get(level) is not None: + _fmt = overridefmt[level].get('fmt', fmt) + _datefmt = overridefmt[level].get('datefmt', datefmt) + self.formatters[level] = logging.Formatter( + self.colorize_format(_fmt), + _datefmt + ) + except Exception: + self.formatters.pop(level, None) + logging.Formatter.__init__(self, self.colorize_format(fmt), datefmt) def colorize_format(self, fmt): @@ -923,7 +947,10 @@ def format(self, record): copy.msg = ansi_wrap(coerce_string(record.msg), **style) record = copy # Delegate the remaining formatting to the base formatter. - return logging.Formatter.format(self, record) + if self.formatters.get(record.levelname): + return self.formatters.get(record.levelname).format(record) + else: + return logging.Formatter.format(self, record) if sys.version_info[:2] <= (2, 6): diff --git a/coloredlogs/cli.py b/coloredlogs/cli.py index 701e1f7..7898ed3 100644 --- a/coloredlogs/cli.py +++ b/coloredlogs/cli.py @@ -49,6 +49,7 @@ # Modules included in our package. from coloredlogs.converter import capture, convert from coloredlogs.demo import demonstrate_colored_logging +from coloredlogs.demo import demonstrate_colored_logging_with_different_formatters # Initialize a logger for this module. logger = logging.getLogger(__name__) @@ -60,7 +61,7 @@ def main(): try: # Parse the command line arguments. options, arguments = getopt.getopt(sys.argv[1:], 'cdh', [ - 'convert', 'to-html', 'demo', 'help', + 'convert', 'to-html', 'demo', 'demo-with-formatter', 'help', ]) # Map command line options to actions. for option, value in options: @@ -69,6 +70,8 @@ def main(): arguments = [] elif option in ('-d', '--demo'): actions.append(demonstrate_colored_logging) + elif option in ('-f', '--demo-with-formatter'): + actions.append(demonstrate_colored_logging_with_different_formatters) elif option in ('-h', '--help'): usage(__doc__) return diff --git a/coloredlogs/demo.py b/coloredlogs/demo.py index c6be1a7..d93b808 100644 --- a/coloredlogs/demo.py +++ b/coloredlogs/demo.py @@ -45,3 +45,60 @@ class RandomException(Exception): logger.exception(e) time.sleep(DEMO_DELAY) logger.info("Done, exiting ..") + + +def demonstrate_colored_logging_with_different_formatters(): + """Interactively demonstrate the :mod:`coloredlogs` package with different formatters""" + # Initialize colored output to the terminal, default to the + # DEBUG logging level but enable the user the customize it. + + FORMATS = { + "INFO": {'fmt': "%(asctime)s - %(levelname)s - " + "%(module)s - %(message)s"}, + "DEBUG": {'fmt': "%(asctime)s - %(levelname)s - " + "%(module)s::%(funcName)s @ %(lineno)d - %(message)s"}, + "WARNING": {'fmt': "%(asctime)s - %(message)s"} + # "WARNING": {} + } + FIELD_STYLES = dict( + asctime=dict(color='green'), + hostname=dict(color='magenta'), + levelname=dict(color='blue', bold=False), + programname=dict(color='cyan'), + name=dict(color='blue'), + module=dict(color='cyan'), + lineno=dict(color='magenta') + ) + + LEVEL_STYLES = { + 'DEBUG': {"color": "blue"}, + 'INFO': {"color": "green"}, + 'WARNING': {"color": "yellow"}, + 'ERROR': {"color": "red"}, + 'CRITICAL': {"color": 'red'}, + 'FATAL': {"color": 'red'} + } + + coloredlogs.install(level=os.environ.get('COLOREDLOGS_LOG_LEVEL', 'DEBUG'), + field_styles=FIELD_STYLES, + level_styles=LEVEL_STYLES, + overridefmt=FORMATS) + # Print some examples with different timestamps. + for level in ['spam', 'debug', 'verbose', 'info', 'notice', 'warn', + 'error', 'critical']: + if hasattr(logger, level): + getattr(logger, level)("message with level %r", level) + time.sleep(DEMO_DELAY) + # Show how exceptions are logged. + try: + class RandomException(Exception): + pass + raise RandomException("Something went horribly wrong!") + except Exception as e: + logger.exception(e) + time.sleep(DEMO_DELAY) + logger.info("Done, exiting ..") + + +demonstrate_colored_logging() +demonstrate_colored_logging_with_different_formatters() diff --git a/coloredlogs/tests.py b/coloredlogs/tests.py index 29a5ea6..e4ceefe 100644 --- a/coloredlogs/tests.py +++ b/coloredlogs/tests.py @@ -408,6 +408,15 @@ def test_cli_demo(self): for name in 'debug', 'info', 'warning', 'error', 'critical': assert name.upper() in output + def test_cli_demo_with_formatters(self): + """Test the command line colored logging demonstration.""" + with CaptureOutput() as capturer: + main('coloredlogs', '--demo-with-formatter') + output = capturer.get_text() + # Make sure the output contains all of the expected logging level names. + for name in 'debug', 'info', 'error', 'critical': + assert name.upper() in output + def test_cli_conversion(self): """Test the command line HTML conversion.""" output = main('coloredlogs', '--convert', 'coloredlogs', '--demo', capture=True) diff --git a/dist/coloredlogs-5.0.1.internal.tar.gz b/dist/coloredlogs-5.0.1.internal.tar.gz new file mode 100644 index 0000000..942cd2c Binary files /dev/null and b/dist/coloredlogs-5.0.1.internal.tar.gz differ