1 import sys, logging, traceback, inspect, os.path
2 from logging import handlers
3 from manifold.util.singleton import Singleton
4 from manifold.util.options import Options
5 from manifold.util.misc import caller_name, make_list
6 from manifold.util import colors
8 # TODO Log should take separately message strings and arguments to be able to
9 # remember which messages are seen several times, and also to allow for
11 # TODO How to log to stdout without putting None in self.log
14 __metaclass__ = Singleton
18 "rsyslog_enable" : False,
19 "rsyslog_host" : None, #"log.top-hat.info",
20 "rsyslog_port" : None, #28514,
21 "log_file" : "/var/log/manifold.log",
22 "log_level" : "DEBUG",
24 "log_duplicates" : False
29 'DEBUG' : colors.MYGREEN,
30 'INFO' : colors.MYBLUE,
31 'WARNING': colors.MYWARNING,
32 'ERROR' : colors.MYRED,
33 'HEADER' : colors.MYHEADER,
35 'RECORD' : colors.MYBLUE,
40 def color(cls, color):
41 return cls.color_ansi[color] if color else ''
43 # To remove duplicate messages
46 def __init__(self, name='(default)'):
47 self.log = None # logging.getLogger(name)
48 self.files_to_keep = []
54 def init_options(self):
58 "--rsyslog-enable", action = "store_false", dest = "rsyslog_enable",
59 help = "Specify if log have to be written to a rsyslog server.",
60 default = self.DEFAULTS["rsyslog_enable"]
63 "--rsyslog-host", dest = "rsyslog_host",
64 help = "Rsyslog hostname.",
65 default = self.DEFAULTS["rsyslog_host"]
68 "--rsyslog-port", type = "int", dest = "rsyslog_port",
69 help = "Rsyslog port.",
70 default = self.DEFAULTS["rsyslog_port"]
73 "-o", "--log-file", dest = "log_file",
74 help = "Log filename.",
75 default = self.DEFAULTS["log_file"]
78 "-L", "--log-level", dest = "log_level",
79 choices = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
81 default = self.DEFAULTS["log_level"]
84 "-d", "--debug", dest = "debug",
85 help = "Debug paths (a list of coma-separated python path: path.to.module.function).",
86 default = self.DEFAULTS["debug"]
89 "", "--log_duplicates", action = "store_true", dest = "log_duplicates",
90 help = "Remove duplicate messages in logs",
91 default = self.DEFAULTS["log_duplicates"]
94 def init_log(self, options=object()):
95 # Initialize self.log (require self.files_to_keep)
96 if self.log: # for debugging by using stdout, log may be equal to None
97 if Options().rsyslog_host:
98 shandler = self.make_handler_rsyslog(
99 Options().rsyslog_host,
100 Options().rsyslog_port,
103 elif Options().log_file:
104 shandler = self.make_handler_locallog(
109 #------------------------------------------------------------------------
111 #------------------------------------------------------------------------
113 def make_handler_rsyslog(self, rsyslog_host, rsyslog_port, log_level):
115 \brief (Internal usage) Prepare logging via rsyslog
116 \param rsyslog_host The hostname of the rsyslog server
117 \param rsyslog_port The port of the rsyslog server
118 \param log_level Log level
120 # Prepare the handler
121 shandler = handlers.SysLogHandler(
122 (rsyslog_host, rsyslog_port),
123 facility = handlers.SysLogHandler.LOG_DAEMON
126 # The log file must remain open while daemonizing
127 self.prepare_handler(shandler, log_level)
130 def make_handler_locallog(self, log_filename, log_level):
132 \brief (Internal usage) Prepare local logging
133 \param log_filename The file in which we write the logs
134 \param log_level Log level
136 # Create directory in which we store the log file
137 log_dir = os.path.dirname(log_filename)
138 if log_dir and not os.path.exists(log_dir):
142 # XXX here we don't log since log is not initialized yet
143 print "OS error: %s" % why
145 # Prepare the handler
146 shandler = logging.handlers.RotatingFileHandler(
151 # The log file must remain open while daemonizing
152 self.files_to_keep.append(shandler.stream)
153 self.prepare_handler(shandler, log_level)
156 def prepare_handler(self, shandler, log_level):
158 \brief (Internal usage)
159 \param shandler Handler used to log information
160 \param log_level Log level
162 shandler.setLevel(log_level)
163 formatter = logging.Formatter("%(asctime)s: %(name)s: %(levelname)s %(message)s")
164 shandler.setFormatter(formatter)
165 self.log.addHandler(shandler)
166 self.log.setLevel(getattr(logging, log_level, logging.INFO))
168 def get_logger(self):
172 def print_msg(cls, msg, level=None, caller=None):
173 sys.stdout.write(cls.color(level))
177 print "[%30s]" % caller,
179 print cls.color('END')
181 #---------------------------------------------------------------------
182 # Log: logger abstraction
183 #---------------------------------------------------------------------
186 def build_message_string(cls, msg, ctx):
188 msg = [m % ctx for m in msg]
189 if isinstance(msg, (tuple, list)):
190 msg = map(lambda s : "%s" % s, msg)
197 def log_message(cls, level, msg, ctx):
199 \brief Logs an message
200 \param level (string) Log level
201 \param msg (string / list of strings) Message string, or List of message strings
202 \param ctx (dict) Context for the message strings
206 if not Options().log_duplicates:
208 count = cls.seen.get(msg, 0)
209 cls.seen[msg] = count + 1
211 # Unhashable types in msg
215 msg += (" -- REPEATED -- Future similar messages will be silently ignored. Please use the --log_duplicates option to allow for duplicates",)
220 caller = caller_name(skip=3)
221 # Eventually remove "" added to the configuration file
223 paths = tuple(s.strip(' \t\n\r') for s in Options().debug.split(','))
226 if not paths or not caller.startswith(paths):
229 logger = Log().get_logger()
230 msg_str = cls.build_message_string(msg, ctx)
233 logger_fct = getattr(logger, level.lower())
234 logger_fct("%s(): %s" % (inspect.stack()[2][3], msg_str))
236 cls.print_msg(msg_str, level, caller)
240 def critical(cls, *msg, **ctx):
241 if not Options().log_level in ['CRITICAL']:
243 cls.log_message('CRITICAL', msg, ctx)
247 def error(cls, *msg, **ctx):
248 if not Options().log_level in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
250 cls.log_message('ERROR', msg, ctx)
251 logger = Log().get_logger()
252 if not Log().get_logger():
253 traceback.print_exc()
257 def warning(cls, *msg, **ctx):
258 if not Options().log_level in ['DEBUG', 'INFO', 'WARNING']:
260 cls.log_message('WARNING', msg, ctx)
263 def info(cls, *msg, **ctx):
264 if not Options().log_level in ['DEBUG', 'INFO']:
266 cls.log_message('INFO', msg, ctx)
269 def debug(cls, *msg, **ctx):
270 if not Options().log_level in ['DEBUG']:
272 cls.log_message('DEBUG', msg, ctx)
276 cls.print_msg(' '.join(map(lambda x: "%r"%x, make_list(msg))), 'TMP', caller_name())
279 def record(cls, *msg):
280 #cls.print_msg(' '.join(map(lambda x: "%r"%x, make_list(msg))), 'RECORD', caller_name())
284 def deprecated(cls, new):
285 #cls.print_msg("Function %s is deprecated, please use %s" % (caller_name(skip=3), new))