The python logging module is a very good option for producing output in most code and especially in modules that will distributed.  It is incredibly flexible but that comes with the price of a steep learning curve.  The developers recognized this and included a one-stop method logging.basicConfig() to set up a basic logger that will often suffice for application usage.  However, this method only sets up the root logger and does nothing if a root logger is already configured.  So it is not really appropriate for use in a module.

Following is a little snippet of code that I have in a module called clogging.py (think of custom-logging).  It's inspired by basicConfig() but it requires that you set the name of the logger (something different than root) and includes parameters for the common situation of logging to the screen (stdout) and optionally a file.  The file logger can have a different log level to support more detailed output there.

import logging
import sys

class NullHandler(logging.Handler):
  def emit(self, record):
    pass

def config_logger(name, format='%(message)s', datefmt=None,
         stream=sys.stdout, level=logging.INFO, 
         filename=None, filemode='w', filelevel=None,
         propagate=False):
  """
  Do basic configuration for the logging system. Similar to
  logging.basicConfig but the logger ``name`` is configurable and both a file
  output and a stream output can be created. Returns a logger object.

  The default behaviour is to create a StreamHandler which writes to
  sys.stdout, set a formatter using the "%(message)s" format string, and
  add the handler to the ``name`` logger.

  A number of optional keyword arguments may be specified, which can alter
  the default behaviour.

  :param name: Logger name
  :param format: handler format string
  :param datefmt: handler date/time format specifier
  :param stream: initialize the StreamHandler using ``stream``
          (None disables the stream, default=sys.stdout)
  :param level: logger level (default=INFO).
  :param filename: create FileHandler using ``filename`` (default=None)
  :param filemode: open ``filename`` with specified filemode ('w' or 'a')
  :param filelevel: logger level for file logger (default=``level``)
  :param propagate: propagate message to parent (default=False)

  :returns: logging.Logger object
  """
  # Get a logger for the specified name
  logger = logging.getLogger(name)
  logger.setLevel(level)
  fmt = logging.Formatter(format, datefmt)
  logger.propagate = propagate

  # Remove existing handlers, otherwise multiple handlers can accrue
  for hdlr in logger.handlers:
    logger.removeHandler(hdlr)

  # Add handlers. Add NullHandler if no file or stream output so that
  # modules don't emit a warning about no handler.
  if not (filename or stream):
    logger.addHandler(NullHandler())

  if filename:
    hdlr = logging.FileHandler(filename, filemode)
    if filelevel is None:
      filelevel = level
    hdlr.setLevel(filelevel)
    hdlr.setFormatter(fmt)
    logger.addHandler(hdlr)

  if stream:
    hdlr = logging.StreamHandler(stream)
    hdlr.setLevel(level)
    hdlr.setFormatter(fmt)
    logger.addHandler(hdlr)

  return logger

One issue with logging is the default feature of propagating messages to parent loggers.  Imagine your module defines a logger called "mymodule" and sets the level at INFO.  Then an application using your module defines a root logger and likewise sets the level at INFO.  Because the root logger is the parent of "mymodule", any logging.INFO statements within your module also pass messages through the root logger, potentially generating multiple outputs.  Sometimes this is very useful, but in many simple applications you want to keep the module logging distinct from the application logging.  In the config_logger() routine this is controlled by the propagate parameter.

To use this in a module you could put something like the following at the module level.  This will get executed when the module is imported.

logger = clogging.config_logger('mymodule', level=logging.INFO)

After this users will have the option to control or redirect output from your module with the standard logging interface, e.g.

logging.getLogger('mymodule').setLevel(logging.WARNING)

A friendlier option might be to provide some documented module methods to set the module "verbosity".  This would do essentially the same as above but hide the hairy details of the logging module.



    6           6