309c28b7865a817031ee39c1ab252864289e6861
4 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Utility functions for logging.
27 import logging
.handlers
29 from ganeti
import constants
30 from ganeti
import compat
33 class _ReopenableLogHandler(logging
.handlers
.BaseRotatingHandler
):
34 """Log handler with ability to reopen log file on request.
36 In combination with a SIGHUP handler this class can reopen the log file on
40 def __init__(self
, filename
):
41 """Initializes this class.
43 @type filename: string
44 @param filename: Path to logfile
47 logging
.handlers
.BaseRotatingHandler
.__init__(self
, filename
, "a")
49 assert self
.encoding
is None, "Encoding not supported for logging"
50 assert not hasattr(self
, "_reopen"), "Base class has '_reopen' attribute"
54 def shouldRollover(self
, _
): # pylint: disable-msg=C0103
55 """Determine whether log file should be reopened.
58 return self
._reopen
or not self
.stream
60 def doRollover(self
): # pylint: disable-msg=C0103
61 """Reopens the log file.
70 # TODO: Handle errors?
71 self
.stream
= open(self
.baseFilename
, "a")
73 def RequestReopen(self
):
74 """Register a request to reopen the file.
76 The file will be reopened before writing the next log record.
82 def _LogErrorsToConsole(base
):
83 """Create wrapper class writing errors to console.
85 This needs to be in a function for unittesting.
88 class wrapped(base
): # pylint: disable-msg=C0103
89 """Log handler that doesn't fallback to stderr.
91 When an error occurs while writing on the logfile, logging.FileHandler
92 tries to log on stderr. This doesn't work in Ganeti since stderr is
93 redirected to a logfile. This class avoids failures by reporting errors to
97 def __init__(self
, console
, *args
, **kwargs
):
98 """Initializes this class.
100 @type console: file-like object or None
101 @param console: Open file-like object for console
104 base
.__init__(self
, *args
, **kwargs
)
105 assert not hasattr(self
, "_console")
106 self
._console
= console
108 def handleError(self
, record
): # pylint: disable-msg=C0103
109 """Handle errors which occur during an emit() call.
111 Try to handle errors with FileHandler method, if it fails write to
116 base
.handleError(record
)
117 except Exception: # pylint: disable-msg=W0703
120 # Ignore warning about "self.format", pylint: disable-msg=E1101
121 self
._console
.write("Cannot log message:\n%s\n" %
123 except Exception: # pylint: disable-msg=W0703
124 # Log handler tried everything it could, now just give up
130 #: Custom log handler for writing to console with a reopenable handler
131 _LogHandler
= _LogErrorsToConsole(_ReopenableLogHandler
)
134 def _GetLogFormatter(program
, multithreaded
, debug
, syslog
):
135 """Build log formatter.
137 @param program: Program name
138 @param multithreaded: Whether to add thread name to log messages
139 @param debug: Whether to enable debug messages
140 @param syslog: Whether the formatter will be used for syslog
146 parts
.append(program
+ "[%(process)d]:")
148 parts
.append("%(asctime)s: " + program
+ " pid=%(process)d")
152 parts
.append(" (%(threadName)s)")
154 parts
.append("/%(threadName)s")
156 # Add debug info for non-syslog loggers
157 if debug
and not syslog
:
158 parts
.append(" %(module)s:%(lineno)s")
160 # Ses, we do want the textual level, as remote syslog will probably lose the
161 # error level, and it's easier to grep for it.
162 parts
.append(" %(levelname)s %(message)s")
164 return logging
.Formatter("".join(parts
))
167 def _ReopenLogFiles(handlers
):
168 """Wrapper for reopening all log handler's files in a sequence.
171 for handler
in handlers
:
172 handler
.RequestReopen()
175 def SetupLogging(logfile
, program
, debug
=0, stderr_logging
=False,
176 multithreaded
=False, syslog
=constants
.SYSLOG_USAGE
,
177 console_logging
=False, root_logger
=None):
178 """Configures the logging module.
181 @param logfile: the filename to which we should log
183 @param program: the name under which we should log messages
185 @param debug: if greater than zero, enable debug messages, otherwise
186 only those at C{INFO} and above level
187 @type stderr_logging: boolean
188 @param stderr_logging: whether we should also log to the standard error
189 @type multithreaded: boolean
190 @param multithreaded: if True, will add the thread name to the log file
192 @param syslog: one of 'no', 'yes', 'only':
193 - if no, syslog is not used
194 - if yes, syslog is used (in addition to file-logging)
195 - if only, only syslog is used
196 @type console_logging: boolean
197 @param console_logging: if True, will use a FileHandler which falls back to
198 the system console if logging fails
199 @type root_logger: logging.Logger
200 @param root_logger: Root logger to use (for unittests)
201 @raise EnvironmentError: if we can't open the log file and
202 syslog/stderr logging is disabled
205 progname
= os
.path
.basename(program
)
207 formatter
= _GetLogFormatter(progname
, multithreaded
, debug
, False)
208 syslog_fmt
= _GetLogFormatter(progname
, multithreaded
, debug
, True)
212 if root_logger
is None:
213 root_logger
= logging
.getLogger("")
214 root_logger
.setLevel(logging
.NOTSET
)
216 # Remove all previously setup handlers
217 for handler
in root_logger
.handlers
:
219 root_logger
.removeHandler(handler
)
222 stderr_handler
= logging
.StreamHandler()
223 stderr_handler
.setFormatter(formatter
)
225 stderr_handler
.setLevel(logging
.NOTSET
)
227 stderr_handler
.setLevel(logging
.CRITICAL
)
228 root_logger
.addHandler(stderr_handler
)
230 if syslog
in (constants
.SYSLOG_YES
, constants
.SYSLOG_ONLY
):
231 facility
= logging
.handlers
.SysLogHandler
.LOG_DAEMON
232 syslog_handler
= logging
.handlers
.SysLogHandler(constants
.SYSLOG_SOCKET
,
234 syslog_handler
.setFormatter(syslog_fmt
)
235 # Never enable debug over syslog
236 syslog_handler
.setLevel(logging
.INFO
)
237 root_logger
.addHandler(syslog_handler
)
239 if syslog
!= constants
.SYSLOG_ONLY
:
240 # this can fail, if the logging directories are not setup or we have
241 # a permisssion problem; in this case, it's best to log but ignore
242 # the error if stderr_logging is True, and if false we re-raise the
243 # exception since otherwise we could run but without any logs at all
246 logfile_handler
= _LogHandler(open(constants
.DEV_CONSOLE
, "a"), logfile
)
248 logfile_handler
= _ReopenableLogHandler(logfile
)
249 except EnvironmentError:
250 if stderr_logging
or syslog
== constants
.SYSLOG_YES
:
251 logging
.exception("Failed to enable logging to file '%s'", logfile
)
253 # we need to re-raise the exception
256 logfile_handler
.setFormatter(formatter
)
258 logfile_handler
.setLevel(logging
.DEBUG
)
260 logfile_handler
.setLevel(logging
.INFO
)
261 root_logger
.addHandler(logfile_handler
)
263 reopen_handlers
.append(logfile_handler
)
265 return compat
.partial(_ReopenLogFiles
, reopen_handlers
)