58fc8e41e688be2c184661a7690865c1ce253a9a
[ganeti-github.git] / 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