1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """Utility functions for logging.
22
23 """
24
25 import os.path
26 import logging
27 import logging.handlers
28 from cStringIO import StringIO
29
30 from ganeti import constants
31 from ganeti import compat
32
33
35 """Log handler with ability to reopen log file on request.
36
37 In combination with a SIGHUP handler this class can reopen the log file on
38 user request.
39
40 """
42 """Initializes this class.
43
44 @type filename: string
45 @param filename: Path to logfile
46
47 """
48 logging.handlers.BaseRotatingHandler.__init__(self, filename, "a")
49
50 assert self.encoding is None, "Encoding not supported for logging"
51 assert not hasattr(self, "_reopen"), "Base class has '_reopen' attribute"
52
53 self._reopen = False
54
56 """Determine whether log file should be reopened.
57
58 """
59 return self._reopen or not self.stream
60
62 """Reopens the log file.
63
64 """
65 if self.stream:
66 self.stream.flush()
67 self.stream.close()
68 self.stream = None
69
70
71
72 self.stream = open(self.baseFilename, "a")
73
74
75 self._reopen = False
76
78 """Register a request to reopen the file.
79
80 The file will be reopened before writing the next log record.
81
82 """
83 self._reopen = True
84
85
87 """Create wrapper class writing errors to console.
88
89 This needs to be in a function for unittesting.
90
91 """
92 class wrapped(base):
93 """Log handler that doesn't fallback to stderr.
94
95 When an error occurs while writing on the logfile, logging.FileHandler
96 tries to log on stderr. This doesn't work in Ganeti since stderr is
97 redirected to a logfile. This class avoids failures by reporting errors to
98 /dev/console.
99
100 """
101 def __init__(self, console, *args, **kwargs):
102 """Initializes this class.
103
104 @type console: file-like object or None
105 @param console: Open file-like object for console
106
107 """
108 base.__init__(self, *args, **kwargs)
109 assert not hasattr(self, "_console")
110 self._console = console
111
112 def handleError(self, record):
113 """Handle errors which occur during an emit() call.
114
115 Try to handle errors with FileHandler method, if it fails write to
116 /dev/console.
117
118 """
119 try:
120 base.handleError(record)
121 except Exception:
122 if self._console:
123 try:
124
125 self._console.write("Cannot log message:\n%s\n" %
126 self.format(record))
127 except Exception:
128
129 pass
130
131 return wrapped
132
133
134
135 _LogHandler = _LogErrorsToConsole(_ReopenableLogHandler)
136
137
169
170
172 """Wrapper for reopening all log handler's files in a sequence.
173
174 """
175 for handler in handlers:
176 handler.RequestReopen()
177 logging.info("Received request to reopen log files")
178
179
180 -def SetupLogging(logfile, program, debug=0, stderr_logging=False,
181 multithreaded=False, syslog=constants.SYSLOG_USAGE,
182 console_logging=False, root_logger=None):
183 """Configures the logging module.
184
185 @type logfile: str
186 @param logfile: the filename to which we should log
187 @type program: str
188 @param program: the name under which we should log messages
189 @type debug: integer
190 @param debug: if greater than zero, enable debug messages, otherwise
191 only those at C{INFO} and above level
192 @type stderr_logging: boolean
193 @param stderr_logging: whether we should also log to the standard error
194 @type multithreaded: boolean
195 @param multithreaded: if True, will add the thread name to the log file
196 @type syslog: string
197 @param syslog: one of 'no', 'yes', 'only':
198 - if no, syslog is not used
199 - if yes, syslog is used (in addition to file-logging)
200 - if only, only syslog is used
201 @type console_logging: boolean
202 @param console_logging: if True, will use a FileHandler which falls back to
203 the system console if logging fails
204 @type root_logger: logging.Logger
205 @param root_logger: Root logger to use (for unittests)
206 @raise EnvironmentError: if we can't open the log file and
207 syslog/stderr logging is disabled
208 @rtype: callable
209 @return: Function reopening all open log files when called
210
211 """
212 progname = os.path.basename(program)
213
214 formatter = _GetLogFormatter(progname, multithreaded, debug, False)
215 syslog_fmt = _GetLogFormatter(progname, multithreaded, debug, True)
216
217 reopen_handlers = []
218
219 if root_logger is None:
220 root_logger = logging.getLogger("")
221 root_logger.setLevel(logging.NOTSET)
222
223
224 for handler in root_logger.handlers:
225 handler.close()
226 root_logger.removeHandler(handler)
227
228 if stderr_logging:
229 stderr_handler = logging.StreamHandler()
230 stderr_handler.setFormatter(formatter)
231 if debug:
232 stderr_handler.setLevel(logging.NOTSET)
233 else:
234 stderr_handler.setLevel(logging.CRITICAL)
235 root_logger.addHandler(stderr_handler)
236
237 if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
238 facility = logging.handlers.SysLogHandler.LOG_DAEMON
239 syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
240 facility)
241 syslog_handler.setFormatter(syslog_fmt)
242
243 syslog_handler.setLevel(logging.INFO)
244 root_logger.addHandler(syslog_handler)
245
246 if syslog != constants.SYSLOG_ONLY:
247
248
249
250
251 try:
252 if console_logging:
253 logfile_handler = _LogHandler(open(constants.DEV_CONSOLE, "a"),
254 logfile)
255 else:
256 logfile_handler = _ReopenableLogHandler(logfile)
257
258 logfile_handler.setFormatter(formatter)
259 if debug:
260 logfile_handler.setLevel(logging.DEBUG)
261 else:
262 logfile_handler.setLevel(logging.INFO)
263 root_logger.addHandler(logfile_handler)
264 reopen_handlers.append(logfile_handler)
265 except EnvironmentError:
266 if stderr_logging or syslog == constants.SYSLOG_YES:
267 logging.exception("Failed to enable logging to file '%s'", logfile)
268 else:
269
270 raise
271
272 return compat.partial(_ReopenLogFiles, reopen_handlers)
273
274
318