Statistics
| Branch: | Tag: | Revision:

root / lib / utils / log.py @ e1a6abf9

History | View | Annotate | Download (9.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
5
#
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.
10
#
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.
15
#
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
19
# 02110-1301, USA.
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

    
34
class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler):
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
  """
41
  def __init__(self, filename):
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

    
55
  def shouldRollover(self, _): # pylint: disable=C0103
56
    """Determine whether log file should be reopened.
57

58
    """
59
    return self._reopen or not self.stream
60

    
61
  def doRollover(self): # pylint: disable=C0103
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
    # Reopen file
71
    # TODO: Handle errors?
72
    self.stream = open(self.baseFilename, "a")
73

    
74
    # Don't reopen on the next message
75
    self._reopen = False
76

    
77
  def RequestReopen(self):
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

    
86
def _LogErrorsToConsole(base):
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): # pylint: disable=C0103
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): # pylint: disable=C0103
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: # pylint: disable=W0703
122
        if self._console:
123
          try:
124
            # Ignore warning about "self.format", pylint: disable=E1101
125
            self._console.write("Cannot log message:\n%s\n" %
126
                                self.format(record))
127
          except Exception: # pylint: disable=W0703
128
            # Log handler tried everything it could, now just give up
129
            pass
130

    
131
  return wrapped
132

    
133

    
134
#: Custom log handler for writing to console with a reopenable handler
135
_LogHandler = _LogErrorsToConsole(_ReopenableLogHandler)
136

    
137

    
138
def _GetLogFormatter(program, multithreaded, debug, syslog):
139
  """Build log formatter.
140

141
  @param program: Program name
142
  @param multithreaded: Whether to add thread name to log messages
143
  @param debug: Whether to enable debug messages
144
  @param syslog: Whether the formatter will be used for syslog
145

146
  """
147
  parts = []
148

    
149
  if syslog:
150
    parts.append(program + "[%(process)d]:")
151
  else:
152
    parts.append("%(asctime)s: " + program + " pid=%(process)d")
153

    
154
  if multithreaded:
155
    if syslog:
156
      parts.append(" (%(threadName)s)")
157
    else:
158
      parts.append("/%(threadName)s")
159

    
160
  # Add debug info for non-syslog loggers
161
  if debug and not syslog:
162
    parts.append(" %(module)s:%(lineno)s")
163

    
164
  # Ses, we do want the textual level, as remote syslog will probably lose the
165
  # error level, and it's easier to grep for it.
166
  parts.append(" %(levelname)s %(message)s")
167

    
168
  return logging.Formatter("".join(parts))
169

    
170

    
171
def _ReopenLogFiles(handlers):
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
  # Remove all previously setup handlers
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
    # Never enable debug over syslog
243
    syslog_handler.setLevel(logging.INFO)
244
    root_logger.addHandler(syslog_handler)
245

    
246
  if syslog != constants.SYSLOG_ONLY:
247
    # this can fail, if the logging directories are not setup or we have
248
    # a permisssion problem; in this case, it's best to log but ignore
249
    # the error if stderr_logging is True, and if false we re-raise the
250
    # exception since otherwise we could run but without any logs at all
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
        # we need to re-raise the exception
270
        raise
271

    
272
  return compat.partial(_ReopenLogFiles, reopen_handlers)
273

    
274

    
275
def SetupToolLogging(debug, verbose, threadname=False,
276
                     _root_logger=None, _stream=None):
277
  """Configures the logging module for tools.
278

279
  All log messages are sent to stderr.
280

281
  @type debug: boolean
282
  @param debug: Disable log message filtering
283
  @type verbose: boolean
284
  @param verbose: Enable verbose log messages
285
  @type threadname: boolean
286
  @param threadname: Whether to include thread name in output
287

288
  """
289
  if _root_logger is None:
290
    root_logger = logging.getLogger("")
291
  else:
292
    root_logger = _root_logger
293

    
294
  fmt = StringIO()
295
  fmt.write("%(asctime)s:")
296

    
297
  if threadname:
298
    fmt.write(" %(threadName)s")
299

    
300
  if debug or verbose:
301
    fmt.write(" %(levelname)s")
302

    
303
  fmt.write(" %(message)s")
304

    
305
  formatter = logging.Formatter(fmt.getvalue())
306

    
307
  stderr_handler = logging.StreamHandler(_stream)
308
  stderr_handler.setFormatter(formatter)
309
  if debug:
310
    stderr_handler.setLevel(logging.NOTSET)
311
  elif verbose:
312
    stderr_handler.setLevel(logging.INFO)
313
  else:
314
    stderr_handler.setLevel(logging.WARNING)
315

    
316
  root_logger.setLevel(logging.NOTSET)
317
  root_logger.addHandler(stderr_handler)