2 # Copyright (c) 2011, 2012, 2013 Nicira, Inc.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
18 import logging.handlers
29 FACILITIES = {"console": "info", "file": "info", "syslog": "info"}
31 "console": "%D{%Y-%m-%dT%H:%M:%SZ}|%05N|%c%T|%p|%m",
32 "file": "%D{%Y-%m-%dT%H:%M:%S.###Z}|%05N|%c%T|%p|%m",
33 "syslog": "ovs|%05N|%c%T|%p|%m",
38 "warn": logging.WARNING,
40 "emer": logging.CRITICAL,
41 "off": logging.CRITICAL
45 def get_level(level_str):
46 return LEVELS.get(level_str.lower())
53 __mfl = {} # Module -> facility -> level
56 __log_patterns = PATTERNS
58 def __init__(self, name):
59 """Creates a new Vlog object representing a module called 'name'. The
60 created Vlog object will do nothing until the Vlog.init() static method
61 is called. Once called, no more Vlog objects may be created."""
63 assert not Vlog.__inited
64 self.name = name.lower()
65 if name not in Vlog.__mfl:
66 Vlog.__mfl[self.name] = FACILITIES.copy()
68 def __log(self, level, message, **kwargs):
72 level_num = LEVELS.get(level.lower(), logging.DEBUG)
73 msg_num = Vlog.__msg_num
76 for f, f_level in Vlog.__mfl[self.name].iteritems():
77 f_level = LEVELS.get(f_level, logging.CRITICAL)
78 if level_num >= f_level:
79 msg = self._build_message(message, f, level, msg_num)
80 logging.getLogger(f).log(level_num, msg, **kwargs)
82 def _build_message(self, message, facility, level, msg_num):
83 pattern = self.__log_patterns[facility]
86 tmp = self._format_time(tmp)
88 matches = re.findall("(%-?[0]?[0-9]?[AcmNnpPrtT])", tmp)
91 tmp = self._format_field(tmp, m, ovs.util.PROGRAM_NAME)
93 tmp = self._format_field(tmp, m, self.name)
95 tmp = self._format_field(tmp, m, message)
97 tmp = self._format_field(tmp, m, str(msg_num))
99 tmp = re.sub(m, "\n", tmp)
101 tmp = self._format_field(tmp, m, level.upper())
103 self._format_field(tmp, m, str(os.getpid()))
105 now = datetime.datetime.utcnow()
106 delta = now - self.__start_time
107 ms = delta.microseconds / 1000
108 tmp = self._format_field(tmp, m, str(ms))
110 subprogram = threading.current_thread().name
111 if subprogram == "MainThread":
113 tmp = self._format_field(tmp, m, subprogram)
115 subprogram = threading.current_thread().name
116 if not subprogram == "MainThread":
117 subprogram = "({})".format(subprogram)
120 tmp = self._format_field(tmp, m, subprogram)
123 def _format_field(self, tmp, match, replace):
124 formatting = re.compile("^%(0)?([1-9])?")
125 matches = formatting.match(match)
126 # Do we need to apply padding?
127 if not matches.group(1) and replace != "":
128 replace = replace.center(len(replace)+2)
129 # Does the field have a minimum width
131 min_width = int(matches.group(2))
132 if len(replace) < min_width:
133 replace = replace.center(min_width)
134 return re.sub(match, replace, tmp)
136 def _format_time(self, tmp):
137 date_regex = re.compile('(%(0?[1-9]?[dD])(\{(.*)\})?)')
138 match = date_regex.search(tmp)
143 # UTC date or Local TZ?
144 if match.group(2) == "d":
145 now = datetime.datetime.now()
146 elif match.group(2) == "D":
147 now = datetime.datetime.utcnow()
149 # Custom format or ISO format?
151 time = datetime.date.strftime(now, match.group(4))
153 i = len(re.search("#+", match.group(4)).group(0))
154 msec = '{0:0>{i}.{i}}'.format(str(now.microsecond / 1000), i=i)
155 time = re.sub('#+', msec, time)
156 except AttributeError:
159 time = datetime.datetime.isoformat(now.replace(microsecond=0))
161 return self._format_field(tmp, match.group(1), time)
163 def emer(self, message, **kwargs):
164 self.__log("EMER", message, **kwargs)
166 def err(self, message, **kwargs):
167 self.__log("ERR", message, **kwargs)
169 def warn(self, message, **kwargs):
170 self.__log("WARN", message, **kwargs)
172 def info(self, message, **kwargs):
173 self.__log("INFO", message, **kwargs)
175 def dbg(self, message, **kwargs):
176 self.__log("DBG", message, **kwargs)
178 def __is_enabled(self, level):
179 level = LEVELS.get(level.lower(), logging.DEBUG)
180 for f, f_level in Vlog.__mfl[self.name].iteritems():
181 f_level = LEVELS.get(f_level, logging.CRITICAL)
186 def emer_is_enabled(self):
187 return self.__is_enabled("EMER")
189 def err_is_enabled(self):
190 return self.__is_enabled("ERR")
192 def warn_is_enabled(self):
193 return self.__is_enabled("WARN")
195 def info_is_enabled(self):
196 return self.__is_enabled("INFO")
198 def dbg_is_enabled(self):
199 return self.__is_enabled("DBG")
201 def exception(self, message):
202 """Logs 'message' at ERR log level. Includes a backtrace when in
203 exception context."""
204 self.err(message, exc_info=True)
207 def init(log_file=None):
208 """Intializes the Vlog module. Causes Vlog to write to 'log_file' if
209 not None. Should be called after all Vlog objects have been created.
210 No logging will occur until this function is called."""
216 Vlog.__start_time = datetime.datetime.utcnow()
217 logging.raiseExceptions = False
218 Vlog.__log_file = log_file
220 logger = logging.getLogger(f)
221 logger.setLevel(logging.DEBUG)
225 logger.addHandler(logging.StreamHandler(sys.stderr))
227 logger.addHandler(logging.handlers.SysLogHandler(
229 facility=logging.handlers.SysLogHandler.LOG_DAEMON))
230 elif f == "file" and Vlog.__log_file:
231 Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
232 logger.addHandler(Vlog.__file_handler)
233 except (IOError, socket.error):
234 logger.setLevel(logging.CRITICAL)
236 ovs.unixctl.command_register("vlog/reopen", "", 0, 0,
237 Vlog._unixctl_vlog_reopen, None)
238 ovs.unixctl.command_register("vlog/set", "spec", 1, sys.maxint,
239 Vlog._unixctl_vlog_set, None)
240 ovs.unixctl.command_register("vlog/list", "", 0, 0,
241 Vlog._unixctl_vlog_list, None)
244 def set_level(module, facility, level):
245 """ Sets the log level of the 'module'-'facility' tuple to 'level'.
246 All three arguments are strings which are interpreted the same as
247 arguments to the --verbose flag. Should be called after all Vlog
248 objects have already been created."""
250 module = module.lower()
251 facility = facility.lower()
252 level = level.lower()
254 if facility != "any" and facility not in FACILITIES:
257 if module != "any" and module not in Vlog.__mfl:
260 if level not in LEVELS:
264 modules = Vlog.__mfl.keys()
268 if facility == "any":
269 facilities = FACILITIES.keys()
271 facilities = [facility]
275 Vlog.__mfl[m][f] = level
278 def set_pattern(facility, pattern):
279 """ Sets the log pattern of the 'facility' to 'pattern' """
280 facility = facility.lower()
281 Vlog.__log_patterns[facility] = pattern
284 def set_levels_from_string(s):
289 words = re.split('[ :]', s)
290 if words[0] == "pattern":
292 if words[1] in FACILITIES and words[2]:
293 segments = [words[i] for i in range(2, len(words))]
294 pattern = "".join(segments)
295 Vlog.set_pattern(words[1], pattern)
298 return "Facility %s does not exist" % words[1]
300 return "Please supply a valid pattern and facility"
302 for word in [w.lower() for w in words]:
305 elif word in FACILITIES:
307 return "cannot specify multiple facilities"
311 return "cannot specify multiple levels"
313 elif word in Vlog.__mfl:
315 return "cannot specify multiple modules"
318 return "no facility, level, or module \"%s\"" % word
320 Vlog.set_level(module or "any", facility or "any", level or "any")
324 lines = [" console syslog file\n",
325 " ------- ------ ------\n"]
326 lines.extend(sorted(["%-16s %4s %4s %4s\n"
328 Vlog.__mfl[m]["console"],
329 Vlog.__mfl[m]["syslog"],
330 Vlog.__mfl[m]["file"]) for m in Vlog.__mfl]))
331 return ''.join(lines)
334 def reopen_log_file():
335 """Closes and then attempts to re-open the current log file. (This is
336 useful just after log rotation, to ensure that the new log file starts
340 logger = logging.getLogger("file")
341 logger.removeHandler(Vlog.__file_handler)
342 Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
343 logger.addHandler(Vlog.__file_handler)
346 def _unixctl_vlog_reopen(conn, unused_argv, unused_aux):
348 Vlog.reopen_log_file()
351 conn.reply("Logging to file not configured")
354 def _unixctl_vlog_set(conn, argv, unused_aux):
356 msg = Vlog.set_levels_from_string(arg)
363 def _unixctl_vlog_list(conn, unused_argv, unused_aux):
364 conn.reply(Vlog.get_levels())
367 def add_args(parser):
368 """Adds vlog related options to 'parser', an ArgumentParser object. The
369 resulting arguments parsed by 'parser' should be passed to handle_args."""
371 group = parser.add_argument_group(title="Logging Options")
372 group.add_argument("--log-file", nargs="?", const="default",
373 help="Enables logging to a file. Default log file"
374 " is used if LOG_FILE is omitted.")
375 group.add_argument("-v", "--verbose", nargs="*",
376 help="Sets logging levels, see ovs-vswitchd(8)."
380 def handle_args(args):
381 """ Handles command line arguments ('args') parsed by an ArgumentParser.
382 The ArgumentParser should have been primed by add_args(). Also takes care
383 of initializing the Vlog module."""
385 log_file = args.log_file
386 if log_file == "default":
387 log_file = "%s/%s.log" % (ovs.dirs.LOGDIR, ovs.util.PROGRAM_NAME)
389 if args.verbose is None:
391 elif args.verbose == []:
392 args.verbose = ["any:any:dbg"]
394 for verbose in args.verbose:
395 msg = Vlog.set_levels_from_string(verbose)
397 ovs.util.ovs_fatal(0, "processing \"%s\": %s" % (verbose, msg))