1 # Copyright (C) 2006-2007 by the Free Software Foundation, Inc.
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 """Logging initialization, using Python's standard logging package.
20 This module cannot be called 'logging' because that would interfere with the
21 import below. Ah, for Python 2.5 and absolute imports.
30 from Mailman
.configuration
import config
34 FMT
= '%(asctime)s (%(process)d) %(message)s'
35 DATEFMT
= '%b %d %H:%M:%S %Y'
38 'bounce', # All bounce processing logs go here
39 'config', # Configuration issues
40 'debug', # Only used for development
41 'error', # All exceptions go to this log
42 'fromusenet', # Information related to the Usenet to Mailman gateway
43 'http', # Internal wsgi-based web interface
44 'locks', # Lock state changes
45 'mischief', # Various types of hostile activity
46 'post', # Information about messages posted to mailing lists
47 'qrunner', # qrunner start/stops
48 'smtp', # Successful SMTP activity
49 'smtp-failure', # Unsuccessful SMTP activity
50 'subscribe', # Information about leaves/joins
51 'vette', # Information related to admindb activity
58 class ReallySafeConfigParser(ConfigParser
.SafeConfigParser
):
59 def getstring(self
, section
, option
, default
=None):
61 return ConfigParser
.SafeConfigParser
.get(self
, section
, option
)
62 except (ConfigParser
.NoSectionError
, ConfigParser
.NoOptionError
):
65 def getboolean(self
, section
, option
, default
=None):
67 return ConfigParser
.SafeConfigParser
.getboolean(
68 self
, section
, option
)
69 except (ConfigParser
.NoSectionError
, ConfigParser
.NoOptionError
):
74 class ReopenableFileHandler(logging
.Handler
):
75 def __init__(self
, filename
):
76 self
._filename
= filename
77 self
._stream
= self
._open
()
78 logging
.Handler
.__init
__(self
)
81 return codecs
.open(self
._filename
, 'a', 'utf-8')
87 def emit(self
, record
):
88 # It's possible for the stream to have been closed by the time we get
89 # here, due to the shut down semantics. This mostly happens in the
90 # test suite, but be defensive anyway.
91 stream
= self
._stream
or sys
.stderr
93 msg
= self
.format(record
)
96 stream
.write(fs
% msg
)
98 stream
.write(fs
% msg
.encode('string-escape'))
101 self
.handleError(record
)
107 logging
.Handler
.close(self
)
111 self
._stream
= self
._open
()
115 def initialize(propagate
=False):
116 # Initialize the root logger, then create a formatter for all the sublogs.
117 logging
.basicConfig(format
=FMT
, datefmt
=DATEFMT
, level
=logging
.INFO
)
118 # If a custom log configuration file was specified, load it now. Note
119 # that we don't use logging.config.fileConfig() because it requires that
120 # all loggers, formatters, and handlers be defined. We want to support
121 # minimal overloading of our logger configurations.
122 cp
= ReallySafeConfigParser()
123 if config
.LOG_CONFIG_FILE
:
124 path
= os
.path
.join(config
.ETC_DIR
, config
.LOG_CONFIG_FILE
)
125 cp
.read(os
.path
.normpath(path
))
126 # Create the subloggers
127 for logger
in LOGGERS
:
128 log
= logging
.getLogger('mailman.' + logger
)
129 # Get settings from log configuration file (or defaults).
130 log_format
= cp
.getstring(logger
, 'format', FMT
)
131 log_datefmt
= cp
.getstring(logger
, 'datefmt', DATEFMT
)
132 # Propagation to the root logger is how we handle logging to stderr
133 # when the qrunners are not run as a subprocess of mailmanctl.
134 log
.propagate
= cp
.getboolean(logger
, 'propagate', propagate
)
135 # Set the logger's level. Note that if the log configuration file
136 # does not set an override, the default level will be INFO except for
137 # the 'debug' logger. It doesn't make much sense for the debug logger
138 # to ignore debug level messages!
139 level_str
= cp
.getstring(logger
, 'level', 'INFO').upper()
140 level_def
= (logging
.DEBUG
if logger
== 'debug' else logging
.INFO
)
141 level_int
= getattr(logging
, level_str
, level_def
)
142 log
.setLevel(level_int
)
143 # Create a formatter for this logger, then a handler, and link the
144 # formatter to the handler.
145 formatter
= logging
.Formatter(fmt
=log_format
, datefmt
=log_datefmt
)
146 path_str
= cp
.getstring(logger
, 'path', logger
)
147 path_abs
= os
.path
.normpath(os
.path
.join(config
.LOG_DIR
, path_str
))
148 handler
= ReopenableFileHandler(path_abs
)
149 _handlers
.append(handler
)
150 handler
.setFormatter(formatter
)
151 log
.addHandler(handler
)
156 for handler
in _handlers
: