Statistics
| Branch: | Tag: | Revision:

root / lib / utils / log.py @ aa0cc3e5

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 logging
26
import logging.handlers
27

    
28
from ganeti import constants
29

    
30

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

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

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

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

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

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

    
50
    self._reopen = False
51

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

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

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

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

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

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

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

76
    """
77
    self._reopen = True
78

    
79

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

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

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

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

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

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

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

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

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

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

    
125
  return wrapped
126

    
127

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

    
131

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

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

140
  """
141
  parts = []
142

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

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

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

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

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

    
164

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

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

192
  """
193
  formatter = _GetLogFormatter(program, multithreaded, debug, False)
194
  syslog_fmt = _GetLogFormatter(program, multithreaded, debug, True)
195

    
196
  root_logger = logging.getLogger("")
197
  root_logger.setLevel(logging.NOTSET)
198

    
199
  # Remove all previously setup handlers
200
  for handler in root_logger.handlers:
201
    handler.close()
202
    root_logger.removeHandler(handler)
203

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

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

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

    
239
    logfile_handler.setFormatter(formatter)
240
    if debug:
241
      logfile_handler.setLevel(logging.DEBUG)
242
    else:
243
      logfile_handler.setLevel(logging.INFO)
244
    root_logger.addHandler(logfile_handler)