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.
26 import logging
.handlers
28 from ganeti
import constants
31 class _ReopenableLogHandler(logging
.handlers
.BaseRotatingHandler
):
32 """Log handler with ability to reopen log file on request.
34 In combination with a SIGHUP handler this class can reopen the log file on
38 def __init__(self
, filename
):
39 """Initializes this class.
41 @type filename: string
42 @param filename: Path to logfile
45 logging
.handlers
.BaseRotatingHandler
.__init__(self
, filename
, "a")
47 assert self
.encoding
is None, "Encoding not supported for logging"
48 assert not hasattr(self
, "_reopen"), "Base class has '_reopen' attribute"
52 def shouldRollover(self
, _
): # pylint: disable-msg=C0103
53 """Determine whether log file should be reopened.
56 return self
._reopen
or not self
.stream
58 def doRollover(self
): # pylint: disable-msg=C0103
59 """Reopens the log file.
68 # TODO: Handle errors?
69 self
.stream
= open(self
.baseFilename
, "a")
71 def RequestReopen(self
):
72 """Register a request to reopen the file.
74 The file will be reopened before writing the next log record.
80 def _LogErrorsToConsole(base
):
81 """Create wrapper class writing errors to console.
83 This needs to be in a function for unittesting.
86 class wrapped(base
): # pylint: disable-msg=C0103
87 """Log handler that doesn't fallback to stderr.
89 When an error occurs while writing on the logfile, logging.FileHandler
90 tries to log on stderr. This doesn't work in Ganeti since stderr is
91 redirected to a logfile. This class avoids failures by reporting errors to
95 def __init__(self
, console
, *args
, **kwargs
):
96 """Initializes this class.
98 @type console: file-like object or None
99 @param console: Open file-like object for console
102 base
.__init__(self
, *args
, **kwargs
)
103 assert not hasattr(self
, "_console")
104 self
._console
= console
106 def handleError(self
, record
): # pylint: disable-msg=C0103
107 """Handle errors which occur during an emit() call.
109 Try to handle errors with FileHandler method, if it fails write to
114 base
.handleError(record
)
115 except Exception: # pylint: disable-msg=W0703
118 # Ignore warning about "self.format", pylint: disable-msg=E1101
119 self
._console
.write("Cannot log message:\n%s\n" %
121 except Exception: # pylint: disable-msg=W0703
122 # Log handler tried everything it could, now just give up
128 #: Custom log handler for writing to console with a reopenable handler
129 _LogHandler
= _LogErrorsToConsole(_ReopenableLogHandler
)
132 def _GetLogFormatter(program
, multithreaded
, debug
, syslog
):
133 """Build log formatter.
135 @param program: Program name
136 @param multithreaded: Whether to add thread name to log messages
137 @param debug: Whether to enable debug messages
138 @param syslog: Whether the formatter will be used for syslog
144 parts
.append(program
+ "[%(process)d]:")
146 parts
.append("%(asctime)s: " + program
+ " pid=%(process)d")
150 parts
.append(" (%(threadName)s)")
152 parts
.append("/%(threadName)s")
154 # Add debug info for non-syslog loggers
155 if debug
and not syslog
:
156 parts
.append(" %(module)s:%(lineno)s")
158 # Ses, we do want the textual level, as remote syslog will probably lose the
159 # error level, and it's easier to grep for it.
160 parts
.append(" %(levelname)s %(message)s")
162 return logging
.Formatter("".join(parts
))
165 def SetupLogging(logfile
, debug
=0, stderr_logging
=False, program
="",
166 multithreaded
=False, syslog
=constants
.SYSLOG_USAGE
,
167 console_logging
=False):
168 """Configures the logging module.
171 @param logfile: the filename to which we should log
173 @param debug: if greater than zero, enable debug messages, otherwise
174 only those at C{INFO} and above level
175 @type stderr_logging: boolean
176 @param stderr_logging: whether we should also log to the standard error
178 @param program: the name under which we should log messages
179 @type multithreaded: boolean
180 @param multithreaded: if True, will add the thread name to the log file
182 @param syslog: one of 'no', 'yes', 'only':
183 - if no, syslog is not used
184 - if yes, syslog is used (in addition to file-logging)
185 - if only, only syslog is used
186 @type console_logging: boolean
187 @param console_logging: if True, will use a FileHandler which falls back to
188 the system console if logging fails
189 @raise EnvironmentError: if we can't open the log file and
190 syslog/stderr logging is disabled
193 formatter
= _GetLogFormatter(program
, multithreaded
, debug
, False)
194 syslog_fmt
= _GetLogFormatter(program
, multithreaded
, debug
, True)
196 root_logger
= logging
.getLogger("")
197 root_logger
.setLevel(logging
.NOTSET
)
199 # Remove all previously setup handlers
200 for handler
in root_logger
.handlers
:
202 root_logger
.removeHandler(handler
)
205 stderr_handler
= logging
.StreamHandler()
206 stderr_handler
.setFormatter(formatter
)
208 stderr_handler
.setLevel(logging
.NOTSET
)
210 stderr_handler
.setLevel(logging
.CRITICAL
)
211 root_logger
.addHandler(stderr_handler
)
213 if syslog
in (constants
.SYSLOG_YES
, constants
.SYSLOG_ONLY
):
214 facility
= logging
.handlers
.SysLogHandler
.LOG_DAEMON
215 syslog_handler
= logging
.handlers
.SysLogHandler(constants
.SYSLOG_SOCKET
,
217 syslog_handler
.setFormatter(syslog_fmt
)
218 # Never enable debug over syslog
219 syslog_handler
.setLevel(logging
.INFO
)
220 root_logger
.addHandler(syslog_handler
)
222 if syslog
!= constants
.SYSLOG_ONLY
:
223 # this can fail, if the logging directories are not setup or we have
224 # a permisssion problem; in this case, it's best to log but ignore
225 # the error if stderr_logging is True, and if false we re-raise the
226 # exception since otherwise we could run but without any logs at all
229 logfile_handler
= _LogHandler(open(constants
.DEV_CONSOLE
, "a"), logfile
)
231 logfile_handler
= _ReopenableLogHandler(logfile
)
233 logfile_handler
.setFormatter(formatter
)
235 logfile_handler
.setLevel(logging
.DEBUG
)
237 logfile_handler
.setLevel(logging
.INFO
)
238 root_logger
.addHandler(logfile_handler
)
239 except EnvironmentError:
240 if stderr_logging
or syslog
== constants
.SYSLOG_YES
:
241 logging
.exception("Failed to enable logging to file '%s'", logfile
)
243 # we need to re-raise the exception