utils: Move logging-related code into separate file
[ganeti-local] / lib / utils / log.py
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 LogFileHandler(logging.FileHandler):
32   """Log handler that doesn't fallback to stderr.
33
34   When an error occurs while writing on the logfile, logging.FileHandler tries
35   to log on stderr. This doesn't work in ganeti since stderr is redirected to
36   the logfile. This class avoids failures reporting errors to /dev/console.
37
38   """
39   def __init__(self, filename, mode="a", encoding=None):
40     """Open the specified file and use it as the stream for logging.
41
42     Also open /dev/console to report errors while logging.
43
44     """
45     logging.FileHandler.__init__(self, filename, mode, encoding)
46     self.console = open(constants.DEV_CONSOLE, "a")
47
48   def handleError(self, record): # pylint: disable-msg=C0103
49     """Handle errors which occur during an emit() call.
50
51     Try to handle errors with FileHandler method, if it fails write to
52     /dev/console.
53
54     """
55     try:
56       logging.FileHandler.handleError(self, record)
57     except Exception: # pylint: disable-msg=W0703
58       try:
59         self.console.write("Cannot log message:\n%s\n" % self.format(record))
60       except Exception: # pylint: disable-msg=W0703
61         # Log handler tried everything it could, now just give up
62         pass
63
64
65 def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
66                  multithreaded=False, syslog=constants.SYSLOG_USAGE,
67                  console_logging=False):
68   """Configures the logging module.
69
70   @type logfile: str
71   @param logfile: the filename to which we should log
72   @type debug: integer
73   @param debug: if greater than zero, enable debug messages, otherwise
74       only those at C{INFO} and above level
75   @type stderr_logging: boolean
76   @param stderr_logging: whether we should also log to the standard error
77   @type program: str
78   @param program: the name under which we should log messages
79   @type multithreaded: boolean
80   @param multithreaded: if True, will add the thread name to the log file
81   @type syslog: string
82   @param syslog: one of 'no', 'yes', 'only':
83       - if no, syslog is not used
84       - if yes, syslog is used (in addition to file-logging)
85       - if only, only syslog is used
86   @type console_logging: boolean
87   @param console_logging: if True, will use a FileHandler which falls back to
88       the system console if logging fails
89   @raise EnvironmentError: if we can't open the log file and
90       syslog/stderr logging is disabled
91
92   """
93   fmt = "%(asctime)s: " + program + " pid=%(process)d"
94   sft = program + "[%(process)d]:"
95   if multithreaded:
96     fmt += "/%(threadName)s"
97     sft += " (%(threadName)s)"
98   if debug:
99     fmt += " %(module)s:%(lineno)s"
100     # no debug info for syslog loggers
101   fmt += " %(levelname)s %(message)s"
102   # yes, we do want the textual level, as remote syslog will probably
103   # lose the error level, and it's easier to grep for it
104   sft += " %(levelname)s %(message)s"
105   formatter = logging.Formatter(fmt)
106   sys_fmt = logging.Formatter(sft)
107
108   root_logger = logging.getLogger("")
109   root_logger.setLevel(logging.NOTSET)
110
111   # Remove all previously setup handlers
112   for handler in root_logger.handlers:
113     handler.close()
114     root_logger.removeHandler(handler)
115
116   if stderr_logging:
117     stderr_handler = logging.StreamHandler()
118     stderr_handler.setFormatter(formatter)
119     if debug:
120       stderr_handler.setLevel(logging.NOTSET)
121     else:
122       stderr_handler.setLevel(logging.CRITICAL)
123     root_logger.addHandler(stderr_handler)
124
125   if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
126     facility = logging.handlers.SysLogHandler.LOG_DAEMON
127     syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
128                                                     facility)
129     syslog_handler.setFormatter(sys_fmt)
130     # Never enable debug over syslog
131     syslog_handler.setLevel(logging.INFO)
132     root_logger.addHandler(syslog_handler)
133
134   if syslog != constants.SYSLOG_ONLY:
135     # this can fail, if the logging directories are not setup or we have
136     # a permisssion problem; in this case, it's best to log but ignore
137     # the error if stderr_logging is True, and if false we re-raise the
138     # exception since otherwise we could run but without any logs at all
139     try:
140       if console_logging:
141         logfile_handler = LogFileHandler(logfile)
142       else:
143         logfile_handler = logging.FileHandler(logfile)
144       logfile_handler.setFormatter(formatter)
145       if debug:
146         logfile_handler.setLevel(logging.DEBUG)
147       else:
148         logfile_handler.setLevel(logging.INFO)
149       root_logger.addHandler(logfile_handler)
150     except EnvironmentError:
151       if stderr_logging or syslog == constants.SYSLOG_YES:
152         logging.exception("Failed to enable logging to file '%s'", logfile)
153       else:
154         # we need to re-raise the exception
155         raise