[tor-commits] [bridgedb/develop] Move log configuration functions to bridgedb.util module.

isis at torproject.org isis at torproject.org
Tue Apr 8 16:09:48 UTC 2014


commit cc83936455cbe4e39016b3aef9d345652a82130d
Author: Isis Lovecruft <isis at torproject.org>
Date:   Mon Apr 7 12:17:49 2014 +0000

    Move log configuration functions to bridgedb.util module.
    
      * MOVE `Main.configureLogging()` → `util.configureLogging()`.
      * CHANGE `configureLogging()` function to use
        `logging.config.dictConfig()` with automatic safelogging filters
        from `bridgedb.safelog` module.
      * ADD `util._getRotatingFileHandlers()` function for chmod/chown'ing
        the rotated log files to be readable only by the running process
        owner.
      * ADD `util.JustifiedLogFormatter` class for justified printing of log
        messages when the config options LOG_TRACE and LOG_THREADS are
        enabled (this extra information is then printed in justified columns
        on the left, leaving the actual log messages left-justified to the
        same width on the right, which in my opinion is much easier to read
        quickly).
---
 lib/bridgedb/Main.py |   39 +---------
 lib/bridgedb/util.py |  192 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 193 insertions(+), 38 deletions(-)

diff --git a/lib/bridgedb/Main.py b/lib/bridgedb/Main.py
index e29e991..90a03ef 100644
--- a/lib/bridgedb/Main.py
+++ b/lib/bridgedb/Main.py
@@ -13,7 +13,6 @@ import signal
 import sys
 import time
 import logging
-import logging.handlers
 import gettext
 
 from twisted.internet import reactor
@@ -29,42 +28,6 @@ import bridgedb.Dist as Dist
 import bridgedb.Time as Time
 import bridgedb.Storage
 
-def configureLogging(cfg):
-    """Set up Python's logging subsystem based on the configuratino.
-    """
-
-    # Turn on safe logging by default
-    safelogging = getattr(cfg, 'SAFELOGGING', True)
-
-    level = getattr(cfg, 'LOGLEVEL', 'WARNING')
-    level = getattr(logging, level)
-    logfile = getattr(cfg, 'LOGFILE', "")
-    logfile_count = getattr(cfg, 'LOGFILE_COUNT', 5)
-    logfile_rotate_size = getattr(cfg, 'LOGFILE_ROTATE_SIZE', 10000000)
-    safelog.setSafeLogging(safelogging)
-
-    logging.getLogger().setLevel(level)
-    if logfile:
-        handler = logging.handlers.RotatingFileHandler(logfile, 'a',
-                                                       logfile_rotate_size,
-                                                       logfile_count)
-        formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s',
-                                      "%b %d %H:%M:%S")
-        handler.setFormatter(formatter)
-        logging.getLogger().addHandler(handler)
-
-    logging.info("Logger Started.")
-    logging.info("Level: %s", level)
-    if logfile:
-        logging.info("Log File: %s", os.path.abspath(logfile))
-        logging.info("Log File Count: %d", logfile_count)
-        logging.info("Rotate Logs After Size: %d",  logfile_rotate_size)
-    else:
-        logging.info("Logging to stderr")
-    if safelogging:
-        logging.info("Safe Logging: Enabled")
-    else:
-        logging.warn("Safe Logging: Disabled")
 
 def load(state, splitter, clear=False):
     """Read and parse all descriptors, and load into a bridge splitter.
@@ -461,7 +424,7 @@ def startup(options):
     # called. Otherwise a default handler that logs to the console will be
     # created by the imported module, and all further calls to
     # :func:`logging.basicConfig` will be ignored.
-    configureLogging(config)
+    util.configureLogging(config)
 
     if options['dump-bridges'] or (options.subCommand is not None):
         runSubcommand(options, config)
diff --git a/lib/bridgedb/util.py b/lib/bridgedb/util.py
index ebca7e1..691b9bc 100644
--- a/lib/bridgedb/util.py
+++ b/lib/bridgedb/util.py
@@ -11,5 +11,197 @@
 
 """Common utilities for BridgeDB."""
 
+import logging
+import logging.config
+import logging.handlers
+import os
 
 
+def _getLogHandlers(logToFile=True, logToStderr=True):
+    """Get the appropriate list of log handlers.
+
+    :param bool logToFile: If ``True``, add a logfile handler.
+    :param bool logToStderr: If ``True``, add a stream handler to stderr.
+    :rtype: list
+    :returns: A list containing the appropriate log handler names from the
+        :class:`logging.config.dictConfigClass`.
+    """
+    logHandlers = []
+    if logToFile:
+        logHandlers.append('rotating')
+    if logToStderr:
+        logHandlers.append('console')
+    return logHandlers
+
+def _getRotatingFileHandler(filename, mode='a', filesize=1000000, filecount=0,
+                            encoding='utf-8', uid=None, gid=None):
+    """Get a :class:`logging.RotatingFileHandler` with a logfile which is
+    readable+writable only by the given **uid** and **gid**.
+
+    :param str filename: The full path to the log file.
+    :param str mode: The mode to open **filename** with. (default: ``'a'``)
+    :param int filesize: Rotate logfiles after they have grown to this size in
+        bytes.
+    :param int filecount: The number of logfiles to keep in rotation.
+    :param str encoding: The encoding for the logfile.
+    :param int uid: The owner UID to set on the logfile.
+    :param int gid: The GID to set on the logfile.
+    :rtype: :class:`logging.handlers.RotatingFileHandler`
+    :returns: A logfile handler which will rotate files and chown/chmod newly
+        created files.
+    """
+    # Default to the current process owner's uid and gid:
+    uid = os.getuid() if not uid else uid
+    gid = os.getgid() if not gid else gid
+
+    if not os.path.exists(filename):
+        open(filename, 'a').close()
+    os.chown(filename, uid, gid)
+    os.chmod(filename, os.ST_WRITE | os.ST_APPEND)
+
+    fileHandler = logging.handlers.RotatingFileHandler(filename, mode,
+                                                       maxBytes=filesize,
+                                                       backupCount=filecount,
+                                                       encoding=encoding)
+    return fileHandler
+
+def configureLogging(cfg):
+    """Set up Python's logging subsystem based on the configuration.
+
+    :type cfg: :class:`~bridgedb.persistent.Conf`
+    :param cfg: The current configuration, including any in-memory settings.
+    """
+    from bridgedb import safelog
+
+    # Turn on safe logging by default:
+    safelogging = getattr(cfg, 'SAFELOGGING', True)
+    safelog.setSafeLogging(safelogging)
+
+    level = getattr(cfg, 'LOGLEVEL', 'WARNING')
+    logLevel = getattr(logging, level, 0)
+    logStderr = getattr(cfg, 'LOG_TO_STDERR', False)
+    logfileName = getattr(cfg, 'LOGFILE', "")
+    logfileCount = getattr(cfg, 'LOGFILE_COUNT', 3) - 1
+    logfileRotateSize = getattr(cfg, 'LOGFILE_ROTATE_SIZE', 10000000)
+    logThreads = getattr(cfg, 'LOG_THREADS', False)
+    logTrace = getattr(cfg, 'LOG_TRACE', False)
+    logTimeFormat = getattr(cfg, 'LOG_TIME_FORMAT', "%H:%M:%S")
+
+    logFilters = []
+    if safelogging:
+        logFilters = ['safelogEmail', 'safelogIPv4', 'safelogIPv6']
+
+    logConfig = {
+        'version': 1,
+        'filters': {
+            'safelogEmail': {'()': safelog.SafelogEmailFilter},
+            'safelogIPv4': {'()': safelog.SafelogIPv4Filter},
+            'safelogIPv6': {'()': safelog.SafelogIPv6Filter},
+        },
+        'formatters': {
+            'default': {'()': JustifiedLogFormatter,
+                        # These values below are kwargs passed to
+                        # :class:`JustifiedFormatter`:
+                        'logThreads': logThreads,
+                        'logTrace': logTrace,
+                        'datefmt': logTimeFormat},
+        },
+        'handlers': {
+            'console': {'class': 'logging.StreamHandler',
+                        'level': logLevel,
+                        'formatter': 'default',
+                        'filters': logFilters},
+            'rotating': {'()': _getRotatingFileHandler,
+                         'level': logLevel,
+                         'formatter': 'default',
+                         'filters': logFilters,
+                         # The values below are passed to the handler creator,
+                         # :func:`getChownedFileHandler`, as kwargs:
+                         'filename': logfileName,
+                         'filesize': logfileRotateSize,
+                         'filecount': logfileCount},
+        },
+        'root': {
+            'handlers': _getLogHandlers(logfileName, logStderr),
+            'level': logLevel,
+        },
+    }
+
+    logging.config.dictConfig(logConfig)
+
+    logging.info("Logger Started.")
+    logging.info("Level: %s", logLevel)
+    logging.info("Safe Logging: %sabled" % ("En" if safelogging else "Dis"))
+
+
+class JustifiedLogFormatter(logging.Formatter):
+    """A logging formatter which pretty prints thread and calling function
+    information, in addition to the normal timestamp, log level, and log
+    message.
+
+    :ivar int width: The width of the column for the calling function
+        information, if the latter is to be included.
+    """
+    width = 30
+
+    def __init__(self, logThreads=False, logTrace=False,
+                 datefmt="%H:%M:%s"):
+        """If **logTrace** is ``True``, the line number, module name, and
+        function name where the logger was called will be included in the
+        message, and the width of this information will always equal ``width``.
+
+        :param bool logThreads: If ``True``, include the current thread name
+            and ID in formatted log messages.
+        :param bool logTrace: If ``True``, include information on the calling
+            function in formatted log messages.
+        """
+        super(JustifiedLogFormatter, self).__init__(datefmt=datefmt)
+        self.logThreads = logThreads
+        self.logTrace = logTrace
+
+        _fmt = ["%(asctime)s %(levelname)-7.7s"]
+        if self.logThreads:
+            _fmt.append("[%(threadName)s id:%(thread)d]")
+        _fmt.append("%(callingFunc)s")
+        _fmt.append("%(message)s")
+
+        self._fmt = " ".join(_fmt)
+
+    def _formatCallingFuncName(self, record):
+        """Format the combined module name and function name of the place where
+        the log message/record was recorded, so that the formatted string is
+        left-justified and not longer than the :cvar:`width`.
+
+        :type record: :class:`logging.LogRecord`
+        :param record: A record of an event created by calling a logger.
+        :returns: The :class:`logging.LogRecord` with its ``message``
+            attribute rewritten to contain the module and function name,
+            truncated to ``width``, or padded on the right with spaces as is
+            necessary.
+        """
+        callingFunc = ""
+        if self.logTrace:
+            # The '.' character between the module name and function name
+            # would otherwise be interpreted as a format string specifier, so
+            # we must specify ``chr(46)``:
+            lineno = "L%s:" % record.lineno
+            caller = "%s%-s%s" % (lineno.rjust(6), record.module, chr(46))
+            maxFuncNameWidth = self.width - 2 - len(caller)
+            funcName = record.funcName
+            if len(funcName) > maxFuncNameWidth:
+                funcName = record.funcName[:maxFuncNameWidth]
+            caller += "%s()" % (funcName)
+            callingFunc = caller.ljust(self.width)
+
+        record.callingFunc = callingFunc
+        return record
+
+    def format(self, record):
+        """Reformat this log **record** to neatly print thread and function
+        traces, if configured to do so.
+
+        :type record: :class:`logging.LogRecord`
+        :param record: A record of an event created by calling a logger.
+        """
+        record = self._formatCallingFuncName(record)
+        return super(JustifiedLogFormatter, self).format(record)





More information about the tor-commits mailing list