Statistics
| Branch: | Tag: | Revision:

root / lib / utils / log.py @ 323f9095

History | View | Annotate | Download (8.4 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

    
29
from ganeti import constants
30
from ganeti import compat
31

    
32

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

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

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

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

    
73
  def RequestReopen(self):
74
    """Register a request to reopen the file.
75

76
    The file will be reopened before writing the next log record.
77

78
    """
79
    self._reopen = True
80

    
81

    
82
def _LogErrorsToConsole(base):
83
  """Create wrapper class writing errors to console.
84

85
  This needs to be in a function for unittesting.
86

87
  """
88
  class wrapped(base): # pylint: disable-msg=C0103
89
    """Log handler that doesn't fallback to stderr.
90

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
94
    /dev/console.
95

96
    """
97
    def __init__(self, console, *args, **kwargs):
98
      """Initializes this class.
99

100
      @type console: file-like object or None
101
      @param console: Open file-like object for console
102

103
      """
104
      base.__init__(self, *args, **kwargs)
105
      assert not hasattr(self, "_console")
106
      self._console = console
107

    
108
    def handleError(self, record): # pylint: disable-msg=C0103
109
      """Handle errors which occur during an emit() call.
110

111
      Try to handle errors with FileHandler method, if it fails write to
112
      /dev/console.
113

114
      """
115
      try:
116
        base.handleError(record)
117
      except Exception: # pylint: disable-msg=W0703
118
        if self._console:
119
          try:
120
            # Ignore warning about "self.format", pylint: disable-msg=E1101
121
            self._console.write("Cannot log message:\n%s\n" %
122
                                self.format(record))
123
          except Exception: # pylint: disable-msg=W0703
124
            # Log handler tried everything it could, now just give up
125
            pass
126

    
127
  return wrapped
128

    
129

    
130
#: Custom log handler for writing to console with a reopenable handler
131
_LogHandler = _LogErrorsToConsole(_ReopenableLogHandler)
132

    
133

    
134
def _GetLogFormatter(program, multithreaded, debug, syslog):
135
  """Build log formatter.
136

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
141

142
  """
143
  parts = []
144

    
145
  if syslog:
146
    parts.append(program + "[%(process)d]:")
147
  else:
148
    parts.append("%(asctime)s: " + program + " pid=%(process)d")
149

    
150
  if multithreaded:
151
    if syslog:
152
      parts.append(" (%(threadName)s)")
153
    else:
154
      parts.append("/%(threadName)s")
155

    
156
  # Add debug info for non-syslog loggers
157
  if debug and not syslog:
158
    parts.append(" %(module)s:%(lineno)s")
159

    
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")
163

    
164
  return logging.Formatter("".join(parts))
165

    
166

    
167
def _ReopenLogFiles(handlers):
168
  """Wrapper for reopening all log handler's files in a sequence.
169

170
  """
171
  for handler in handlers:
172
    handler.RequestReopen()
173
  logging.info("Received request to reopen log files")
174

    
175

    
176
def SetupLogging(logfile, program, debug=0, stderr_logging=False,
177
                 multithreaded=False, syslog=constants.SYSLOG_USAGE,
178
                 console_logging=False, root_logger=None):
179
  """Configures the logging module.
180

181
  @type logfile: str
182
  @param logfile: the filename to which we should log
183
  @type program: str
184
  @param program: the name under which we should log messages
185
  @type debug: integer
186
  @param debug: if greater than zero, enable debug messages, otherwise
187
      only those at C{INFO} and above level
188
  @type stderr_logging: boolean
189
  @param stderr_logging: whether we should also log to the standard error
190
  @type multithreaded: boolean
191
  @param multithreaded: if True, will add the thread name to the log file
192
  @type syslog: string
193
  @param syslog: one of 'no', 'yes', 'only':
194
      - if no, syslog is not used
195
      - if yes, syslog is used (in addition to file-logging)
196
      - if only, only syslog is used
197
  @type console_logging: boolean
198
  @param console_logging: if True, will use a FileHandler which falls back to
199
      the system console if logging fails
200
  @type root_logger: logging.Logger
201
  @param root_logger: Root logger to use (for unittests)
202
  @raise EnvironmentError: if we can't open the log file and
203
      syslog/stderr logging is disabled
204
  @rtype: callable
205
  @return: Function reopening all open log files when called
206

207
  """
208
  progname = os.path.basename(program)
209

    
210
  formatter = _GetLogFormatter(progname, multithreaded, debug, False)
211
  syslog_fmt = _GetLogFormatter(progname, multithreaded, debug, True)
212

    
213
  reopen_handlers = []
214

    
215
  if root_logger is None:
216
    root_logger = logging.getLogger("")
217
  root_logger.setLevel(logging.NOTSET)
218

    
219
  # Remove all previously setup handlers
220
  for handler in root_logger.handlers:
221
    handler.close()
222
    root_logger.removeHandler(handler)
223

    
224
  if stderr_logging:
225
    stderr_handler = logging.StreamHandler()
226
    stderr_handler.setFormatter(formatter)
227
    if debug:
228
      stderr_handler.setLevel(logging.NOTSET)
229
    else:
230
      stderr_handler.setLevel(logging.CRITICAL)
231
    root_logger.addHandler(stderr_handler)
232

    
233
  if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
234
    facility = logging.handlers.SysLogHandler.LOG_DAEMON
235
    syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
236
                                                    facility)
237
    syslog_handler.setFormatter(syslog_fmt)
238
    # Never enable debug over syslog
239
    syslog_handler.setLevel(logging.INFO)
240
    root_logger.addHandler(syslog_handler)
241

    
242
  if syslog != constants.SYSLOG_ONLY:
243
    # this can fail, if the logging directories are not setup or we have
244
    # a permisssion problem; in this case, it's best to log but ignore
245
    # the error if stderr_logging is True, and if false we re-raise the
246
    # exception since otherwise we could run but without any logs at all
247
    try:
248
      if console_logging:
249
        logfile_handler = _LogHandler(open(constants.DEV_CONSOLE, "a"),
250
                                      logfile)
251
      else:
252
        logfile_handler = _ReopenableLogHandler(logfile)
253

    
254
      logfile_handler.setFormatter(formatter)
255
      if debug:
256
        logfile_handler.setLevel(logging.DEBUG)
257
      else:
258
        logfile_handler.setLevel(logging.INFO)
259
      root_logger.addHandler(logfile_handler)
260
      reopen_handlers.append(logfile_handler)
261
    except EnvironmentError:
262
      if stderr_logging or syslog == constants.SYSLOG_YES:
263
        logging.exception("Failed to enable logging to file '%s'", logfile)
264
      else:
265
        # we need to re-raise the exception
266
        raise
267

    
268
  return compat.partial(_ReopenLogFiles, reopen_handlers)