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