Statistics
| Branch: | Tag: | Revision:

root / lib / utils / log.py @ d728ac75

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=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=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
    # Don't reopen on the next message
74
    self._reopen = False
75

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

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

    
130
  return wrapped
131

    
132

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

    
136

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

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

145
  """
146
  parts = []
147

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

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

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

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

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

    
169

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

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

    
271
  return compat.partial(_ReopenLogFiles, reopen_handlers)