summaryrefslogtreecommitdiff
path: root/src/mailman/core/logging.py
diff options
context:
space:
mode:
authorBarry Warsaw2009-01-25 13:01:41 -0500
committerBarry Warsaw2009-01-25 13:01:41 -0500
commiteefd06f1b88b8ecbb23a9013cd223b72ca85c20d (patch)
tree72c947fe16fce0e07e996ee74020b26585d7e846 /src/mailman/core/logging.py
parent07871212f74498abd56bef3919bf3e029eb8b930 (diff)
downloadmailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.gz
mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.zst
mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.zip
Diffstat (limited to 'src/mailman/core/logging.py')
-rw-r--r--src/mailman/core/logging.py163
1 files changed, 163 insertions, 0 deletions
diff --git a/src/mailman/core/logging.py b/src/mailman/core/logging.py
new file mode 100644
index 000000000..a18065965
--- /dev/null
+++ b/src/mailman/core/logging.py
@@ -0,0 +1,163 @@
+# Copyright (C) 2006-2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Logging initialization, using Python's standard logging package."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'initialize',
+ 'reopen',
+ ]
+
+
+import os
+import sys
+import codecs
+import logging
+
+from lazr.config import as_boolean, as_log_level
+
+from mailman.config import config
+
+
+_handlers = {}
+
+
+
+# XXX I would love to simplify things and use Python 2.6's WatchedFileHandler,
+# but there are two problems. First, it's more difficult to handle the test
+# suite's need to reopen the file handler to a different path. Does
+# zope.testing's logger support fix this?
+#
+# The other problem is that WatchedFileHandler doesn't really easily support
+# HUPing the process to reopen the log file. Now, maybe that's not a big deal
+# because the standard logging module would already handle things correctly if
+# the file is moved, but still that's not an interface I'm ready to give up on
+# yet. For now, keep our hack.
+
+class ReopenableFileHandler(logging.Handler):
+ """A file handler that supports reopening."""
+
+ def __init__(self, name, filename):
+ self.name = name
+ self._filename = filename
+ self._stream = self._open()
+ logging.Handler.__init__(self)
+
+ def _open(self):
+ return codecs.open(self._filename, 'a', 'utf-8')
+
+ def flush(self):
+ if self._stream:
+ self._stream.flush()
+
+ def emit(self, record):
+ # It's possible for the stream to have been closed by the time we get
+ # here, due to the shut down semantics. This mostly happens in the
+ # test suite, but be defensive anyway.
+ stream = (self._stream if self._stream else sys.stderr)
+ try:
+ msg = self.format(record)
+ try:
+ stream.write('{0}'.format(msg))
+ except UnicodeError:
+ stream.write('{0}'.format(msg.encode('string-escape')))
+ self.flush()
+ except:
+ self.handleError(record)
+
+ def close(self):
+ self.flush()
+ self._stream.close()
+ self._stream = None
+ logging.Handler.close(self)
+
+ def reopen(self, filename=None):
+ """Reopen the output stream.
+
+ :param filename: If given, this reopens the output stream to a new
+ file. This is used in the test suite.
+ :type filename: string
+ """
+ if filename is not None:
+ self._filename = filename
+ self._stream.close()
+ self._stream = self._open()
+
+
+
+def initialize(propagate=None):
+ """Initialize all logs.
+
+ :param propagate: Flag specifying whether logs should propagate their
+ messages to the root logger. If omitted, propagation is determined
+ from the configuration files.
+ :type propagate: bool or None
+ """
+ # First, find the root logger and configure the logging subsystem.
+ # Initialize the root logger, then create a formatter for all the
+ # sublogs. The root logger should log to stderr.
+ logging.basicConfig(format=config.logging.root.format,
+ datefmt=config.logging.root.datefmt,
+ level=as_log_level(config.logging.root.level),
+ stream=sys.stderr)
+ # Create the subloggers.
+ for logger_config in config.logger_configs:
+ sub_name = logger_config.name.split('.')[-1]
+ if sub_name == 'root':
+ continue
+ logger_name = 'mailman.' + sub_name
+ log = logging.getLogger(logger_name)
+ # Get settings from log configuration file (or defaults).
+ log_format = logger_config.format
+ log_datefmt = logger_config.datefmt
+ # Propagation to the root logger is how we handle logging to stderr
+ # when the qrunners are not run as a subprocess of mailmanctl.
+ log.propagate = (as_boolean(logger_config.propagate)
+ if propagate is None else propagate)
+ # Set the logger's level.
+ log.setLevel(as_log_level(logger_config.level))
+ # Create a formatter for this logger, then a handler, and link the
+ # formatter to the handler.
+ formatter = logging.Formatter(fmt=log_format, datefmt=log_datefmt)
+ path_str = logger_config.path
+ path_abs = os.path.normpath(os.path.join(config.LOG_DIR, path_str))
+ handler = ReopenableFileHandler(sub_name, path_abs)
+ _handlers[sub_name] = handler
+ handler.setFormatter(formatter)
+ log.addHandler(handler)
+
+
+
+def reopen():
+ """Re-open all log files."""
+ for handler in _handlers.values():
+ handler.reopen()
+
+
+
+def get_handler(sub_name):
+ """Return the handler associated with a named logger.
+
+ :param sub_name: The logger name, sans the 'mailman.' prefix.
+ :type sub_name: string
+ :return: The file handler associated with the named logger.
+ :rtype: `ReopenableFileHandler`
+ """
+ return _handlers[sub_name]