From b132a73f15e432eaf43310fce9196ca0c0651465 Mon Sep 17 00:00:00 2001 From: <> Date: Thu, 2 Jan 2003 05:25:50 +0000 Subject: This commit was manufactured by cvs2svn to create branch 'Release_2_1-maint'. --- Mailman/Logging/.cvsignore | 1 + Mailman/Logging/Logger.py | 103 +++++++++++++++++++++++++++++++++++++++ Mailman/Logging/Makefile.in | 69 ++++++++++++++++++++++++++ Mailman/Logging/MultiLogger.py | 76 +++++++++++++++++++++++++++++ Mailman/Logging/StampedLogger.py | 89 +++++++++++++++++++++++++++++++++ Mailman/Logging/Syslog.py | 69 ++++++++++++++++++++++++++ Mailman/Logging/Utils.py | 52 ++++++++++++++++++++ Mailman/Logging/__init__.py | 15 ++++++ 8 files changed, 474 insertions(+) create mode 100644 Mailman/Logging/.cvsignore create mode 100644 Mailman/Logging/Logger.py create mode 100644 Mailman/Logging/Makefile.in create mode 100644 Mailman/Logging/MultiLogger.py create mode 100644 Mailman/Logging/StampedLogger.py create mode 100644 Mailman/Logging/Syslog.py create mode 100644 Mailman/Logging/Utils.py create mode 100644 Mailman/Logging/__init__.py (limited to 'Mailman/Logging') diff --git a/Mailman/Logging/.cvsignore b/Mailman/Logging/.cvsignore new file mode 100644 index 00000000..f3c7a7c5 --- /dev/null +++ b/Mailman/Logging/.cvsignore @@ -0,0 +1 @@ +Makefile diff --git a/Mailman/Logging/Logger.py b/Mailman/Logging/Logger.py new file mode 100644 index 00000000..0cb7c6af --- /dev/null +++ b/Mailman/Logging/Logger.py @@ -0,0 +1,103 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""File-based logger, writes to named category files in mm_cfg.LOG_DIR.""" + +import sys +import os +import codecs +from types import StringType + +from Mailman import mm_cfg +from Mailman.Logging.Utils import _logexc + +# Set this to the encoding to be used for your log file output. If set to +# None, then it uses your system's default encoding. Otherwise, it must be an +# encoding string appropriate for codecs.open(). +LOG_ENCODING = 'iso-8859-1' + + + +class Logger: + def __init__(self, category, nofail=1, immediate=0): + """nofail says to fallback to sys.__stderr__ if write fails to + category file - a complaint message is emitted, but no exception is + raised. Set nofail=0 if you want to handle the error in your code, + instead. + + immediate=1 says to create the log file on instantiation. + Otherwise, the file is created only when there are writes pending. + """ + self.__filename = os.path.join(mm_cfg.LOG_DIR, category) + self.__fp = None + self.__nofail = nofail + self.__encoding = LOG_ENCODING or sys.getdefaultencoding() + if immediate: + self.__get_f() + + def __del__(self): + self.close() + + def __repr__(self): + return '<%s to %s>' % (self.__class__.__name__, `self.__filename`) + + def __get_f(self): + if self.__fp: + return self.__fp + else: + try: + ou = os.umask(002) + try: + try: + f = codecs.open( + self.__filename, 'a+', self.__encoding, 'replace', + 1) + except LookupError: + f = open(self.__filename, 'a+', 1) + self.__fp = f + finally: + os.umask(ou) + except IOError, e: + if self.__nofail: + _logexc(self, e) + f = self.__fp = sys.__stderr__ + else: + raise + return f + + def flush(self): + f = self.__get_f() + if hasattr(f, 'flush'): + f.flush() + + def write(self, msg): + if isinstance(msg, StringType): + msg = unicode(msg, self.__encoding) + f = self.__get_f() + try: + f.write(msg) + except IOError, msg: + _logexc(self, msg) + + def writelines(self, lines): + for l in lines: + self.write(l) + + def close(self): + if not self.__fp: + return + self.__get_f().close() + self.__fp = None diff --git a/Mailman/Logging/Makefile.in b/Mailman/Logging/Makefile.in new file mode 100644 index 00000000..407f39a9 --- /dev/null +++ b/Mailman/Logging/Makefile.in @@ -0,0 +1,69 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# NOTE: Makefile.in is converted into Makefile by the configure script +# in the parent directory. Once configure has run, you can recreate +# the Makefile by running just config.status. + +# Variables set by configure + +VPATH= @srcdir@ +srcdir= @srcdir@ +bindir= @bindir@ +prefix= @prefix@ +exec_prefix= @exec_prefix@ + +CC= @CC@ +CHMOD= @CHMOD@ +INSTALL= @INSTALL@ + +DEFS= @DEFS@ + +# Customizable but not set by configure + +OPT= @OPT@ +CFLAGS= $(OPT) $(DEFS) +PACKAGEDIR= $(prefix)/Mailman/Logging +SHELL= /bin/sh + +MODULES= *.py + +# Modes for directories and executables created by the install +# process. Default to group-writable directories but +# user-only-writable for executables. +DIRMODE= 775 +EXEMODE= 755 +FILEMODE= 644 +INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) + + +# Rules + +all: + +install: + for f in $(MODULES); \ + do \ + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(PACKAGEDIR); \ + done + +finish: + +clean: + +distclean: + -rm *.pyc + -rm Makefile diff --git a/Mailman/Logging/MultiLogger.py b/Mailman/Logging/MultiLogger.py new file mode 100644 index 00000000..3ff11d27 --- /dev/null +++ b/Mailman/Logging/MultiLogger.py @@ -0,0 +1,76 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""A mutiple sink logger. Any message written goes to all sub-loggers.""" + +import sys +from Mailman.Logging.Utils import _logexc + + + +class MultiLogger: + def __init__(self, *args): + self.__loggers = [] + for logger in args: + self.__loggers.append(logger) + + def add_logger(self, logger): + if logger not in self.__loggers: + self.__loggers.append(logger) + + def del_logger(self, logger): + if logger in self.__loggers: + self.__loggers.remove(logger) + + def write(self, msg): + for logger in self.__loggers: + # you want to be sure that a bug in one logger doesn't prevent + # logging to all the other loggers + try: + logger.write(msg) + except: + _logexc(logger, msg) + + def writelines(self, lines): + for line in lines: + self.write(line) + + def flush(self): + for logger in self.__loggers: + if hasattr(logger, 'flush'): + # you want to be sure that a bug in one logger doesn't prevent + # logging to all the other loggers + try: + logger.flush() + except: + _logexc(logger) + + def close(self): + for logger in self.__loggers: + # you want to be sure that a bug in one logger doesn't prevent + # logging to all the other loggers + try: + if logger <> sys.__stderr__ and logger <> sys.__stdout__: + logger.close() + except: + _logexc(logger) + + def reprime(self): + for logger in self.__loggers: + try: + logger.reprime() + except AttributeError: + pass diff --git a/Mailman/Logging/StampedLogger.py b/Mailman/Logging/StampedLogger.py new file mode 100644 index 00000000..370f1af8 --- /dev/null +++ b/Mailman/Logging/StampedLogger.py @@ -0,0 +1,89 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import os +import time + +from Mailman.Logging.Logger import Logger + + + +class StampedLogger(Logger): + """Record messages in log files, including date stamp and optional label. + + If manual_reprime is on (off by default), then timestamp prefix will + included only on first .write() and on any write immediately following a + call to the .reprime() method. This is useful for when StampedLogger is + substituting for sys.stderr, where you'd like to see the grouping of + multiple writes under a single timestamp (and there is often is one group, + for uncaught exceptions where a script is bombing). + + In any case, the identifying prefix will only follow writes that start on + a new line. + + Nofail (by default) says to fallback to sys.stderr if write fails to + category file. A message is emitted, but the IOError is caught. + Initialize with nofail=0 if you want to handle the error in your code, + instead. + + """ + def __init__(self, category, label=None, manual_reprime=0, nofail=1, + immediate=1): + """If specified, optional label is included after timestamp. + Other options are passed to the Logger class initializer. + """ + self.__label = label + self.__manual_reprime = manual_reprime + self.__primed = 1 + self.__bol = 1 + Logger.__init__(self, category, nofail, immediate) + + def reprime(self): + """Reset so timestamp will be included with next write.""" + self.__primed = 1 + + def write(self, msg): + if not self.__bol: + prefix = "" + else: + if not self.__manual_reprime or self.__primed: + stamp = time.strftime("%b %d %H:%M:%S %Y ", + time.localtime(time.time())) + self.__primed = 0 + else: + stamp = "" + if self.__label is None: + label = "(%d)" % os.getpid() + else: + label = "%s(%d):" % (self.__label, os.getpid()) + prefix = stamp + label + Logger.write(self, "%s %s" % (prefix, msg)) + if msg and msg[-1] == '\n': + self.__bol = 1 + else: + self.__bol = 0 + + def writelines(self, lines): + first = 1 + for l in lines: + if first: + self.write(l) + first = 0 + else: + if l and l[0] not in [' ', '\t', '\n']: + Logger.write(self, ' ' + l) + else: + Logger.write(self, l) diff --git a/Mailman/Logging/Syslog.py b/Mailman/Logging/Syslog.py new file mode 100644 index 00000000..3e8d557d --- /dev/null +++ b/Mailman/Logging/Syslog.py @@ -0,0 +1,69 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""Central logging class for the Mailman system. + +This might eventually be replaced by a syslog based logger, hence the name. +""" + +from Mailman.Logging.StampedLogger import StampedLogger + + + +# Global, shared logger instance. All clients should use this object. +syslog = None + + + +# Don't instantiate except below. +class _Syslog: + def __init__(self): + self._logfiles = {} + + def __del__(self): + self.close() + + def write(self, kind, msg, *args, **kws): + self.write_ex(kind, msg, args, kws) + + # We need this because SMTPDirect tries to pass in a special dict-like + # object, which is not a concrete dictionary. This is not allowed by + # Python's extended call syntax. :( + def write_ex(self, kind, msg, args=None, kws=None): + origmsg = msg + logf = self._logfiles.get(kind) + if not logf: + logf = self._logfiles[kind] = StampedLogger(kind) + try: + if args: + msg %= args + if kws: + msg %= kws + # It's really bad if exceptions in the syslogger cause other crashes + except Exception, e: + msg = 'Bad format "%s": %s: %s' % (origmsg, repr(e), e) + logf.write(msg + '\n') + + # For the ultimate in convenience + __call__ = write + + def close(self): + for kind, logger in self._logfiles.items(): + logger.close() + self._logfiles.clear() + + +syslog = _Syslog() diff --git a/Mailman/Logging/Utils.py b/Mailman/Logging/Utils.py new file mode 100644 index 00000000..ef119fb0 --- /dev/null +++ b/Mailman/Logging/Utils.py @@ -0,0 +1,52 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys +import traceback + + +def _logexc(logger=None, msg=''): + sys.__stderr__.write('Logging error: %s\n' % logger) + traceback.print_exc(file=sys.__stderr__) + sys.__stderr__.write('Original log message:\n%s\n' % msg) + + +def LogStdErr(category, label, manual_reprime=1, tee_to_real_stderr=1): + """Establish a StampedLogger on sys.stderr if possible. + + If tee_to_real_stderr is true, then the real standard error also gets + output, via a MultiLogger. + + Returns the MultiLogger if successful, None otherwise. + """ + from StampedLogger import StampedLogger + from MultiLogger import MultiLogger + try: + logger = StampedLogger(category, + label=label, + manual_reprime=manual_reprime, + nofail=0) + if tee_to_real_stderr: + if hasattr(sys, '__stderr__'): + stderr = sys.__stderr__ + else: + stderr = sys.stderr + logger = MultiLogger(stderr, logger) + sys.stderr = logger + return sys.stderr + except IOError: + return None + diff --git a/Mailman/Logging/__init__.py b/Mailman/Logging/__init__.py new file mode 100644 index 00000000..2cbbabb1 --- /dev/null +++ b/Mailman/Logging/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -- cgit v1.2.3