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 DESTINATIONS = {"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
43 FACILITIES = ['auth', 'authpriv', 'cron', 'daemon', 'ftp', 'kern', 'lpr',
44 'mail', 'news', 'syslog', 'user', 'uucp', 'local0', 'local1',
45 'local2', 'local3', 'local4', 'local5', 'local6', 'local7']
46 syslog_facility = "daemon"
50 def get_level(level_str):
51 return LEVELS.get(level_str.lower())
58 __mfl = {} # Module -> destination -> level
61 __log_patterns = PATTERNS
63 def __init__(self, name):
64 """Creates a new Vlog object representing a module called 'name'. The
65 created Vlog object will do nothing until the Vlog.init() static method
66 is called. Once called, no more Vlog objects may be created."""
68 assert not Vlog.__inited
69 self.name = name.lower()
70 if name not in Vlog.__mfl:
71 Vlog.__mfl[self.name] = DESTINATIONS.copy()
73 def __log(self, level, message, **kwargs):
77 level_num = LEVELS.get(level.lower(), logging.DEBUG)
78 msg_num = Vlog.__msg_num
81 for f, f_level in Vlog.__mfl[self.name].iteritems():
82 f_level = LEVELS.get(f_level, logging.CRITICAL)
83 if level_num >= f_level:
84 msg = self._build_message(message, f, level, msg_num)
85 logging.getLogger(f).log(level_num, msg, **kwargs)
87 def _build_message(self, message, destination, level, msg_num):
88 pattern = self.__log_patterns[destination]
91 tmp = self._format_time(tmp)
93 matches = re.findall("(%-?[0]?[0-9]?[AcmNnpPrtT])", tmp)
96 tmp = self._format_field(tmp, m, ovs.util.PROGRAM_NAME)
98 tmp = self._format_field(tmp, m, self.name)
100 tmp = self._format_field(tmp, m, message)
102 tmp = self._format_field(tmp, m, str(msg_num))
104 tmp = re.sub(m, "\n", tmp)
106 tmp = self._format_field(tmp, m, level.upper())
108 self._format_field(tmp, m, str(os.getpid()))
110 now = datetime.datetime.utcnow()
111 delta = now - self.__start_time
112 ms = delta.microseconds / 1000
113 tmp = self._format_field(tmp, m, str(ms))
115 subprogram = threading.currentThread().getName()
116 if subprogram == "MainThread":
118 tmp = self._format_field(tmp, m, subprogram)
120 subprogram = threading.currentThread().getName()
121 if not subprogram == "MainThread":
122 subprogram = "({})".format(subprogram)
125 tmp = self._format_field(tmp, m, subprogram)
128 def _format_field(self, tmp, match, replace):
129 formatting = re.compile("^%(0)?([1-9])?")
130 matches = formatting.match(match)
131 # Do we need to apply padding?
132 if not matches.group(1) and replace != "":
133 replace = replace.center(len(replace) + 2)
134 # Does the field have a minimum width
136 min_width = int(matches.group(2))
137 if len(replace) < min_width:
138 replace = replace.center(min_width)
139 return re.sub(match, replace, tmp)
141 def _format_time(self, tmp):
142 date_regex = re.compile('(%(0?[1-9]?[dD])(\{(.*)\})?)')
143 match = date_regex.search(tmp)
148 # UTC date or Local TZ?
149 if match.group(2) == "d":
150 now = datetime.datetime.now()
151 elif match.group(2) == "D":
152 now = datetime.datetime.utcnow()
154 # Custom format or ISO format?
156 time = datetime.date.strftime(now, match.group(4))
158 i = len(re.search("#+", match.group(4)).group(0))
159 msec = '{0:0>{i}.{i}}'.format(str(now.microsecond / 1000), i=i)
160 time = re.sub('#+', msec, time)
161 except AttributeError:
164 time = datetime.datetime.isoformat(now.replace(microsecond=0))
166 return self._format_field(tmp, match.group(1), time)
168 def emer(self, message, **kwargs):
169 self.__log("EMER", message, **kwargs)
171 def err(self, message, **kwargs):
172 self.__log("ERR", message, **kwargs)
174 def warn(self, message, **kwargs):
175 self.__log("WARN", message, **kwargs)
177 def info(self, message, **kwargs):
178 self.__log("INFO", message, **kwargs)
180 def dbg(self, message, **kwargs):
181 self.__log("DBG", message, **kwargs)
183 def __is_enabled(self, level):
184 level = LEVELS.get(level.lower(), logging.DEBUG)
185 for f, f_level in Vlog.__mfl[self.name].iteritems():
186 f_level = LEVELS.get(f_level, logging.CRITICAL)
191 def emer_is_enabled(self):
192 return self.__is_enabled("EMER")
194 def err_is_enabled(self):
195 return self.__is_enabled("ERR")
197 def warn_is_enabled(self):
198 return self.__is_enabled("WARN")
200 def info_is_enabled(self):
201 return self.__is_enabled("INFO")
203 def dbg_is_enabled(self):
204 return self.__is_enabled("DBG")
206 def exception(self, message):
207 """Logs 'message' at ERR log level. Includes a backtrace when in
208 exception context."""
209 self.err(message, exc_info=True)
212 def init(log_file=None):
213 """Intializes the Vlog module. Causes Vlog to write to 'log_file' if
214 not None. Should be called after all Vlog objects have been created.
215 No logging will occur until this function is called."""
221 Vlog.__start_time = datetime.datetime.utcnow()
222 logging.raiseExceptions = False
223 Vlog.__log_file = log_file
224 for f in DESTINATIONS:
225 logger = logging.getLogger(f)
226 logger.setLevel(logging.DEBUG)
230 logger.addHandler(logging.StreamHandler(sys.stderr))
232 Vlog.add_syslog_handler()
233 elif f == "file" and Vlog.__log_file:
234 Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
235 logger.addHandler(Vlog.__file_handler)
236 except (IOError, socket.error):
237 logger.setLevel(logging.CRITICAL)
239 ovs.unixctl.command_register("vlog/reopen", "", 0, 0,
240 Vlog._unixctl_vlog_reopen, None)
241 ovs.unixctl.command_register("vlog/set", "spec", 1, sys.maxint,
242 Vlog._unixctl_vlog_set, None)
243 ovs.unixctl.command_register("vlog/list", "", 0, 0,
244 Vlog._unixctl_vlog_list, None)
247 def set_level(module, destination, level):
248 """ Sets the log level of the 'module'-'destination' tuple to 'level'.
249 All three arguments are strings which are interpreted the same as
250 arguments to the --verbose flag. Should be called after all Vlog
251 objects have already been created."""
253 module = module.lower()
254 destination = destination.lower()
255 level = level.lower()
257 if destination != "any" and destination not in DESTINATIONS:
260 if module != "any" and module not in Vlog.__mfl:
263 if level not in LEVELS:
267 modules = Vlog.__mfl.keys()
271 if destination == "any":
272 destinations = DESTINATIONS.keys()
274 destinations = [destination]
277 for f in destinations:
278 Vlog.__mfl[m][f] = level
281 def set_pattern(destination, pattern):
282 """ Sets the log pattern of the 'destination' to 'pattern' """
283 destination = destination.lower()
284 Vlog.__log_patterns[destination] = pattern
287 def add_syslog_handler(facility=None):
288 global syslog_facility, syslog_handler
290 # If handler is already added and there is no change in 'facility',
291 # there is nothing to do.
292 if (not facility or facility == syslog_facility) and syslog_handler:
295 logger = logging.getLogger('syslog')
296 # If there is no infrastructure to support python syslog, increase
297 # the logging severity level to avoid repeated errors.
298 if not os.path.exists("/dev/log"):
299 logger.setLevel(logging.CRITICAL)
303 logger.removeHandler(syslog_handler)
306 syslog_facility = facility
308 syslog_handler = logging.handlers.SysLogHandler(address="/dev/log",
309 facility=syslog_facility)
310 logger.addHandler(syslog_handler)
314 def set_levels_from_string(s):
319 words = re.split('[ :]', s)
320 if words[0] == "pattern":
322 if words[1] in DESTINATIONS and words[2]:
323 segments = [words[i] for i in range(2, len(words))]
324 pattern = "".join(segments)
325 Vlog.set_pattern(words[1], pattern)
328 return "Destination %s does not exist" % words[1]
330 return "Please supply a valid pattern and destination"
331 elif words[0] == "FACILITY":
332 if words[1] in FACILITIES:
333 Vlog.add_syslog_handler(words[1])
336 return "Facility %s is invalid" % words[1]
338 for word in [w.lower() for w in words]:
341 elif word in DESTINATIONS:
343 return "cannot specify multiple destinations"
347 return "cannot specify multiple levels"
349 elif word in Vlog.__mfl:
351 return "cannot specify multiple modules"
354 return "no destination, level, or module \"%s\"" % word
356 Vlog.set_level(module or "any", destination or "any", level or "any")
360 lines = [" console syslog file\n",
361 " ------- ------ ------\n"]
362 lines.extend(sorted(["%-16s %4s %4s %4s\n"
364 Vlog.__mfl[m]["console"],
365 Vlog.__mfl[m]["syslog"],
366 Vlog.__mfl[m]["file"]) for m in Vlog.__mfl]))
367 return ''.join(lines)
370 def reopen_log_file():
371 """Closes and then attempts to re-open the current log file. (This is
372 useful just after log rotation, to ensure that the new log file starts
376 logger = logging.getLogger("file")
377 logger.removeHandler(Vlog.__file_handler)
378 Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
379 logger.addHandler(Vlog.__file_handler)
382 def _unixctl_vlog_reopen(conn, unused_argv, unused_aux):
384 Vlog.reopen_log_file()
387 conn.reply("Logging to file not configured")
390 def _unixctl_vlog_set(conn, argv, unused_aux):
392 msg = Vlog.set_levels_from_string(arg)
399 def _unixctl_vlog_list(conn, unused_argv, unused_aux):
400 conn.reply(Vlog.get_levels())
403 def add_args(parser):
404 """Adds vlog related options to 'parser', an ArgumentParser object. The
405 resulting arguments parsed by 'parser' should be passed to handle_args."""
407 group = parser.add_argument_group(title="Logging Options")
408 group.add_argument("--log-file", nargs="?", const="default",
409 help="Enables logging to a file. Default log file"
410 " is used if LOG_FILE is omitted.")
411 group.add_argument("-v", "--verbose", nargs="*",
412 help="Sets logging levels, see ovs-vswitchd(8)."
416 def handle_args(args):
417 """ Handles command line arguments ('args') parsed by an ArgumentParser.
418 The ArgumentParser should have been primed by add_args(). Also takes care
419 of initializing the Vlog module."""
421 log_file = args.log_file
422 if log_file == "default":
423 log_file = "%s/%s.log" % (ovs.dirs.LOGDIR, ovs.util.PROGRAM_NAME)
425 if args.verbose is None:
427 elif args.verbose == []:
428 args.verbose = ["any:any:dbg"]
430 for verbose in args.verbose:
431 msg = Vlog.set_levels_from_string(verbose)
433 ovs.util.ovs_fatal(0, "processing \"%s\": %s" % (verbose, msg))