Statistics
| Branch: | Tag: | Revision:

root / lib / utils / log.py @ cfcc79c6

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

    
31

    
32
class _ReopenableLogHandler(logging.handlers.BaseRotatingHandler):
33
  """Log handler with ability to reopen log file on request.
34

35
  In combination with a SIGHUP handler this class can reopen the log file on
36
  user request.
37

38
  """
39
  def __init__(self, filename):
40
    """Initializes this class.
41

42
    @type filename: string
43
    @param filename: Path to logfile
44

45
    """
46
    logging.handlers.BaseRotatingHandler.__init__(self, filename, "a")
47

    
48
    assert self.encoding is None, "Encoding not supported for logging"
49
    assert not hasattr(self, "_reopen"), "Base class has '_reopen' attribute"
50

    
51
    self._reopen = False
52

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

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

    
59
  def doRollover(self): # pylint: disable-msg=C0103
60
    """Reopens the log file.
61

62
    """
63
    if self.stream:
64
      self.stream.flush()
65
      self.stream.close()
66
      self.stream = None
67

    
68
    # Reopen file
69
    # TODO: Handle errors?
70
    self.stream = open(self.baseFilename, "a")
71

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

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

77
    """
78
    self._reopen = True
79

    
80

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

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

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

90
    When an error occurs while writing on the logfile, logging.FileHandler
91
    tries to log on stderr. This doesn't work in Ganeti since stderr is
92
    redirected to a logfile. This class avoids failures by reporting errors to
93
    /dev/console.
94

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

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

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

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

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

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

    
126
  return wrapped
127

    
128

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

    
132

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

136
  @param program: Program name
137
  @param multithreaded: Whether to add thread name to log messages
138
  @param debug: Whether to enable debug messages
139
  @param syslog: Whether the formatter will be used for syslog
140

141
  """
142
  parts = []
143

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

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

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

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

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

    
165

    
166
def SetupLogging(logfile, program, debug=0, stderr_logging=False,
167
                 multithreaded=False, syslog=constants.SYSLOG_USAGE,
168
                 console_logging=False):
169
  """Configures the logging module.
170

171
  @type logfile: str
172
  @param logfile: the filename to which we should log
173
  @type program: str
174
  @param program: the name under which we should log messages
175
  @type debug: integer
176
  @param debug: if greater than zero, enable debug messages, otherwise
177
      only those at C{INFO} and above level
178
  @type stderr_logging: boolean
179
  @param stderr_logging: whether we should also log to the standard error
180
  @type multithreaded: boolean
181
  @param multithreaded: if True, will add the thread name to the log file
182
  @type syslog: string
183
  @param syslog: one of 'no', 'yes', 'only':
184
      - if no, syslog is not used
185
      - if yes, syslog is used (in addition to file-logging)
186
      - if only, only syslog is used
187
  @type console_logging: boolean
188
  @param console_logging: if True, will use a FileHandler which falls back to
189
      the system console if logging fails
190
  @raise EnvironmentError: if we can't open the log file and
191
      syslog/stderr logging is disabled
192

193
  """
194
  progname = os.path.basename(program)
195

    
196
  formatter = _GetLogFormatter(progname, multithreaded, debug, False)
197
  syslog_fmt = _GetLogFormatter(progname, multithreaded, debug, True)
198

    
199
  root_logger = logging.getLogger("")
200
  root_logger.setLevel(logging.NOTSET)
201

    
202
  # Remove all previously setup handlers
203
  for handler in root_logger.handlers:
204
    handler.close()
205
    root_logger.removeHandler(handler)
206

    
207
  if stderr_logging:
208
    stderr_handler = logging.StreamHandler()
209
    stderr_handler.setFormatter(formatter)
210
    if debug:
211
      stderr_handler.setLevel(logging.NOTSET)
212
    else:
213
      stderr_handler.setLevel(logging.CRITICAL)
214
    root_logger.addHandler(stderr_handler)
215

    
216
  if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
217
    facility = logging.handlers.SysLogHandler.LOG_DAEMON
218
    syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
219
                                                    facility)
220
    syslog_handler.setFormatter(syslog_fmt)
221
    # Never enable debug over syslog
222
    syslog_handler.setLevel(logging.INFO)
223
    root_logger.addHandler(syslog_handler)
224

    
225
  if syslog != constants.SYSLOG_ONLY:
226
    # this can fail, if the logging directories are not setup or we have
227
    # a permisssion problem; in this case, it's best to log but ignore
228
    # the error if stderr_logging is True, and if false we re-raise the
229
    # exception since otherwise we could run but without any logs at all
230
    try:
231
      if console_logging:
232
        logfile_handler = _LogHandler(open(constants.DEV_CONSOLE, "a"), logfile)
233
      else:
234
        logfile_handler = _ReopenableLogHandler(logfile)
235
    except EnvironmentError:
236
      if stderr_logging or syslog == constants.SYSLOG_YES:
237
        logging.exception("Failed to enable logging to file '%s'", logfile)
238
      else:
239
        # we need to re-raise the exception
240
        raise
241

    
242
    logfile_handler.setFormatter(formatter)
243
    if debug:
244
      logfile_handler.setLevel(logging.DEBUG)
245
    else:
246
      logfile_handler.setLevel(logging.INFO)
247
    root_logger.addHandler(logfile_handler)