diff --git a/MANIFEST.in b/MANIFEST.in index 41b05ab..e8e1f19 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include *.rst include *.txt include coloredlogs.pth +graft docs diff --git a/README.rst b/README.rst index 6f86c98..6b489dc 100644 --- a/README.rst +++ b/README.rst @@ -25,7 +25,6 @@ defined by my verboselogs_ package: if you install both `coloredlogs` and .. contents:: :local: - :depth: 1 Format of log messages ---------------------- @@ -46,6 +45,30 @@ You can customize the log format and styling using environment variables as well as programmatically, please refer to the `online documentation`_ for details. +Enabling millisecond precision +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you're switching from `logging.basicConfig()`_ to `coloredlogs.install()`_ +you may notice that timestamps no longer include milliseconds. This is because +coloredlogs doesn't output milliseconds in timestamps unless you explicitly +tell it to. There are two ways to do that: + +1. The easy way is to pass the `milliseconds` argument to + `coloredlogs.install()`_:: + + coloredlogs.install(milliseconds=True) + +2. Alternatively you can change the log format `to include 'msecs'`_:: + + %(asctime)s,%(msecs)03d %(hostname)s %(name)s[%(process)d] %(levelname)s %(message)s + + Here's what the call to `coloredlogs.install()`_ would then look like:: + + coloredlogs.install(fmt='%(asctime)s,%(msecs)03d %(hostname)s %(name)s[%(process)d] %(levelname)s %(message)s') + + Customizing the log format also enables you to change the delimiter that + separates seconds from milliseconds (the comma above). + Usage ----- @@ -56,7 +79,7 @@ Here's an example of how easy it is to get started: import coloredlogs, logging # Create a logger object. - logger = logging.getLogger('your-module') + logger = logging.getLogger(__name__) # By default the install() function installs a handler on the root logger, # this means that log messages from your code and log messages from the @@ -71,7 +94,7 @@ Here's an example of how easy it is to get started: # Some examples. logger.debug("this is a debugging message") logger.info("this is an informational message") - logger.warn("this is a warning message") + logger.warning("this is a warning message") logger.error("this is an error message") logger.critical("this is a critical message") @@ -147,12 +170,15 @@ This software is licensed under the `MIT license`_. .. _Colorama: https://pypi.python.org/pypi/colorama .. _ColoredCronMailer: https://coloredlogs.readthedocs.io/en/latest/#coloredlogs.converter.ColoredCronMailer .. _ColoredFormatter: https://coloredlogs.readthedocs.io/en/latest/#coloredlogs.ColoredFormatter +.. _coloredlogs.install(): https://coloredlogs.readthedocs.io/en/latest/#coloredlogs.install .. _cron: https://en.wikipedia.org/wiki/Cron .. _GitHub: https://github.com/xolox/python-coloredlogs +.. _logging.basicConfig(): https://docs.python.org/2/library/logging.html#logging.basicConfig .. _logging.Formatter: https://docs.python.org/2/library/logging.html#logging.Formatter .. _logging: https://docs.python.org/2/library/logging.html .. _MIT license: https://en.wikipedia.org/wiki/MIT_License .. _online documentation: https://coloredlogs.readthedocs.io/ .. _peter@peterodding.com: peter@peterodding.com .. _PyPI: https://pypi.python.org/pypi/coloredlogs +.. _to include 'msecs': https://stackoverflow.com/questions/6290739/python-logging-use-milliseconds-in-time-format .. _verboselogs: https://pypi.python.org/pypi/verboselogs diff --git a/coloredlogs/__init__.py b/coloredlogs/__init__.py index 716a26a..6e49c98 100644 --- a/coloredlogs/__init__.py +++ b/coloredlogs/__init__.py @@ -1,7 +1,7 @@ # Colored terminal output for Python's logging module. # # Author: Peter Odding -# Last Change: May 18, 2017 +# Last Change: August 7, 2017 # URL: https://coloredlogs.readthedocs.io """ @@ -199,7 +199,7 @@ NEED_COLORAMA = WINDOWS # Semi-standard module versioning. -__version__ = '7.0' +__version__ = '7.3' DEFAULT_LOG_LEVEL = logging.INFO """The default log level for :mod:`coloredlogs` (:data:`logging.INFO`).""" @@ -237,6 +237,7 @@ info=dict(), notice=dict(color='magenta'), warning=dict(color='yellow'), + success=dict(color='green', bold=CAN_USE_BOLD_FONT), error=dict(color='red'), critical=dict(color='red', bold=CAN_USE_BOLD_FONT)) """Mapping of log level names to default font styles.""" @@ -273,6 +274,9 @@ def install(level=None, **kw): :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` + does by default, :data:`False` to hide milliseconds + (the default is :data:`False`, see `#16`_). :param level_styles: A dictionary with custom level styles (defaults to :data:`DEFAULT_LEVEL_STYLES`). :param field_styles: A dictionary with custom field styles (defaults to @@ -327,6 +331,8 @@ def install(level=None, **kw): 6. The formatter is added to the handler and the handler is added to the logger. + + .. _issue 16: https://github.com/xolox/python-coloredlogs/issues/16 """ logger = kw.get('logger') or logging.getLogger() reconfigure = kw.get('reconfigure', True) @@ -401,6 +407,20 @@ def install(level=None, **kw): # back to the default). if not formatter_options['datefmt']: formatter_options['datefmt'] = os.environ.get('COLOREDLOGS_DATE_FORMAT') or DEFAULT_DATE_FORMAT + # Python's logging module shows milliseconds by default through special + # handling in the logging.Formatter.formatTime() method [1]. Because + # coloredlogs always defines a `datefmt' it bypasses this special + # handling, which is fine because ever since publishing coloredlogs + # I've never needed millisecond precision ;-). However there are users + # of coloredlogs that do want milliseconds to be shown [2] so we + # provide a shortcut to make it easy. + # + # [1] https://stackoverflow.com/questions/6290739/python-logging-use-milliseconds-in-time-format + # [2] https://github.com/xolox/python-coloredlogs/issues/16 + if kw.get('milliseconds') and '%(msecs)' not in formatter_options['fmt']: + formatter_options['fmt'] = formatter_options['fmt'].replace( + '%(asctime)s', '%(asctime)s,%(msecs)03d', + ) # Do we need to make %(hostname) available to the formatter? HostNameFilter.install( handler=handler, @@ -873,14 +893,23 @@ def format(self, record): class. """ style = self.nn.get(self.level_styles, record.levelname) - if style: + # After the introduction of the `Empty' class it was reported in issue + # 33 that format() can be called when `Empty' has already been garbage + # collected. This explains the (otherwise rather out of place) `Empty + # is not None' check in the following `if' statement. The reasoning + # here is that it's much better to log a message without formatting + # then to raise an exception ;-). + # + # For more details refer to issue 33 on GitHub: + # https://github.com/xolox/python-coloredlogs/issues/33 + if style and Empty is not None: # Due to the way that Python's logging module is structured and # documented the only (IMHO) clean way to customize its behavior is # to change incoming LogRecord objects before they get to the base # formatter. However we don't want to break other formatters and # handlers, so we copy the log record. # - # In the past this used copy.copy() but as reported in issue #29 + # In the past this used copy.copy() but as reported in issue 29 # (which is reproducible) this can cause deadlocks. The following # Python voodoo is intended to accomplish the same thing as # copy.copy() without all of the generalization and overhead that diff --git a/coloredlogs/demo.py b/coloredlogs/demo.py index 5085644..c6be1a7 100644 --- a/coloredlogs/demo.py +++ b/coloredlogs/demo.py @@ -1,7 +1,7 @@ # Demonstration of the coloredlogs package. # # Author: Peter Odding -# Last Change: May 17, 2017 +# Last Change: August 7, 2017 # URL: https://coloredlogs.readthedocs.io """A simple demonstration of the `coloredlogs` package.""" @@ -32,7 +32,7 @@ def demonstrate_colored_logging(): # DEBUG logging level but enable the user the customize it. coloredlogs.install(level=os.environ.get('COLOREDLOGS_LOG_LEVEL', 'SPAM')) # Print some examples with different timestamps. - for level in ['spam', 'debug', 'verbose', 'info', 'notice', 'warn', 'error', 'critical']: + for level in ['spam', 'debug', 'verbose', 'info', 'success', 'notice', 'warning', 'error', 'critical']: if hasattr(logger, level): getattr(logger, level)("message with level %r", level) time.sleep(DEMO_DELAY) diff --git a/coloredlogs/tests.py b/coloredlogs/tests.py index 33a339c..29a5ea6 100644 --- a/coloredlogs/tests.py +++ b/coloredlogs/tests.py @@ -1,7 +1,7 @@ # Automated tests for the `coloredlogs' package. # # Author: Peter Odding -# Last Change: May 17, 2017 +# Last Change: August 7, 2017 # URL: https://coloredlogs.readthedocs.io """Automated tests for the `coloredlogs` package.""" @@ -66,6 +66,18 @@ \s (?P .* ) ''', re.VERBOSE) +# Compiled regular expression that matches a single line of output produced by +# the default log format with milliseconds=True. +PATTERN_INCLUDING_MILLISECONDS = re.compile(r''' + (?P \d{4}-\d{2}-\d{2} ) + \s (?P