1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
|
# Copyright (C) 2006-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
"""Logging initialization, using Python's standard logging package.
This module cannot be called 'logging' because that would interfere with the
import below. Ah, for Python 2.5 and absolute imports.
"""
import os
import sys
import codecs
import logging
import ConfigParser
from mailman.configuration import config
FMT = '%(asctime)s (%(process)d) %(message)s'
DATEFMT = '%b %d %H:%M:%S %Y'
LOGGERS = (
'archiver', # All archiver output
'bounce', # All bounce processing logs go here
'config', # Configuration issues
'debug', # Only used for development
'error', # All exceptions go to this log
'fromusenet', # Information related to the Usenet to Mailman gateway
'http', # Internal wsgi-based web interface
'locks', # Lock state changes
'mischief', # Various types of hostile activity
'post', # Information about messages posted to mailing lists
'qrunner', # qrunner start/stops
'smtp', # Successful SMTP activity
'smtp-failure', # Unsuccessful SMTP activity
'subscribe', # Information about leaves/joins
'vette', # Information related to admindb activity
)
_handlers = []
class ReallySafeConfigParser(ConfigParser.SafeConfigParser):
"""Like `SafeConfigParser` but catches `NoOptionError`."""
def getstring(self, section, option, default=None):
try:
return ConfigParser.SafeConfigParser.get(self, section, option)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
# Try again with the '*' section.
try:
return ConfigParser.SafeConfigParser.get(self, '*', option)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
return default
def getboolean(self, section, option, default=None):
try:
return ConfigParser.SafeConfigParser.getboolean(
self, section, option)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
# Try again with the '*' section.
try:
return ConfigParser.SafeConfigParser.getboolean(
self, '*', option)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
return default
def getlevel(self, section, option, default=None):
try:
level_str = ConfigParser.SafeConfigParser.get(
self, section, option)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
# Try again with the '*' section.
try:
level_str = ConfigParser.SafeConfigParser.get(
self, '*', option)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
level_str = default
# Convert the level string into an integer.
level_str = level_str.upper()
level_def = (logging.DEBUG if section == 'debug' else logging.INFO)
return getattr(logging, level_str, level_def)
class ReopenableFileHandler(logging.Handler):
def __init__(self, filename):
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 or sys.stderr
try:
msg = self.format(record)
fs = '%s\n'
try:
stream.write(fs % msg)
except UnicodeError:
stream.write(fs % 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):
self._stream.close()
self._stream = self._open()
def initialize(propagate=False):
# Initialize the root logger, then create a formatter for all the sublogs.
logging.basicConfig(format=FMT, datefmt=DATEFMT, level=logging.INFO)
# If a custom log configuration file was specified, load it now. Note
# that we don't use logging.config.fileConfig() because it requires that
# all loggers, formatters, and handlers be defined. We want to support
# minimal overloading of our logger configurations.
cp = ReallySafeConfigParser()
if config.LOG_CONFIG_FILE:
path = os.path.join(config.ETC_DIR, config.LOG_CONFIG_FILE)
cp.read(os.path.normpath(path))
# Create the subloggers
for logger in LOGGERS:
log = logging.getLogger('mailman.' + logger)
# Get settings from log configuration file (or defaults).
log_format = cp.getstring(logger, 'format', FMT)
log_datefmt = cp.getstring(logger, 'datefmt', 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 = cp.getboolean(logger, 'propagate', propagate)
# Set the logger's level. Note that if the log configuration file
# does not set an override, the default level will be INFO except for
# the 'debug' logger. It doesn't make much sense for the debug logger
# to ignore debug level messages!
level_int = cp.getlevel(logger, 'level', 'INFO')
log.setLevel(level_int)
# 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 = cp.getstring(logger, 'path', logger)
path_abs = os.path.normpath(os.path.join(config.LOG_DIR, path_str))
handler = ReopenableFileHandler(path_abs)
_handlers.append(handler)
handler.setFormatter(formatter)
log.addHandler(handler)
def reopen():
for handler in _handlers:
handler.reopen()
|