2 # Copyright (c) 2011, 2012, 2013, 2015, 2016 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
26 from six.moves import range
32 DESTINATIONS = {"console": "info", "file": "info", "syslog": "info"}
34 "console": "%D{%Y-%m-%dT%H:%M:%SZ}|%05N|%c%T|%p|%m",
35 "file": "%D{%Y-%m-%dT%H:%M:%S.###Z}|%05N|%c%T|%p|%m",
36 "syslog": "ovs|%05N|%c%T|%p|%m",
41 "warn": logging.WARNING,
43 "emer": logging.CRITICAL,
44 "off": logging.CRITICAL
46 FACILITIES = ['auth', 'authpriv', 'cron', 'daemon', 'ftp', 'kern', 'lpr',
47 'mail', 'news', 'syslog', 'user', 'uucp', 'local0', 'local1',
48 'local2', 'local3', 'local4', 'local5', 'local6', 'local7']
49 syslog_facility = "daemon"
53 def get_level(level_str):
54 return LEVELS.get(level_str.lower())
61 __mfl = {} # Module -> destination -> level
64 __log_patterns = PATTERNS
66 def __init__(self, name):
67 """Creates a new Vlog object representing a module called 'name'. The
68 created Vlog object will do nothing until the Vlog.init() static method
69 is called. Once called, no more Vlog objects may be created."""
71 assert not Vlog.__inited
72 self.name = name.lower()
73 if name not in Vlog.__mfl:
74 Vlog.__mfl[self.name] = DESTINATIONS.copy()
76 def __log(self, level, message, **kwargs):
80 level_num = LEVELS.get(level.lower(), logging.DEBUG)
81 msg_num = Vlog.__msg_num
84 for f, f_level in six.iteritems(Vlog.__mfl[self.name]):
85 f_level = LEVELS.get(f_level, logging.CRITICAL)
86 if level_num >= f_level:
87 msg = self._build_message(message, f, level, msg_num)
88 logging.getLogger(f).log(level_num, msg, **kwargs)
90 def _build_message(self, message, destination, level, msg_num):
91 pattern = self.__log_patterns[destination]
94 tmp = self._format_time(tmp)
96 matches = re.findall("(%-?[0]?[0-9]?[AcmNnpPrtT])", tmp)
99 tmp = self._format_field(tmp, m, ovs.util.PROGRAM_NAME)
101 tmp = self._format_field(tmp, m, self.name)
103 tmp = self._format_field(tmp, m, message)
105 tmp = self._format_field(tmp, m, str(msg_num))
107 tmp = re.sub(m, "\n", tmp)
109 tmp = self._format_field(tmp, m, level.upper())
111 self._format_field(tmp, m, str(os.getpid()))
113 now = datetime.datetime.utcnow()
114 delta = now - self.__start_time
115 ms = delta.microseconds / 1000
116 tmp = self._format_field(tmp, m, str(ms))
118 subprogram = threading.currentThread().getName()
119 if subprogram == "MainThread":
121 tmp = self._format_field(tmp, m, subprogram)
123 subprogram = threading.currentThread().getName()
124 if not subprogram == "MainThread":
125 subprogram = "({})".format(subprogram)
128 tmp = self._format_field(tmp, m, subprogram)
131 def _format_field(self, tmp, match, replace):
132 formatting = re.compile("^%(0)?([1-9])?")
133 matches = formatting.match(match)
134 # Do we need to apply padding?
135 if not matches.group(1) and replace != "":
136 replace = replace.center(len(replace) + 2)
137 # Does the field have a minimum width
139 min_width = int(matches.group(2))
140 if len(replace) < min_width:
141 replace = replace.center(min_width)
142 return re.sub(match, replace, tmp)
144 def _format_time(self, tmp):
145 date_regex = re.compile('(%(0?[1-9]?[dD])(\{(.*)\})?)')
146 match = date_regex.search(tmp)
151 # UTC date or Local TZ?
152 if match.group(2) == "d":
153 now = datetime.datetime.now()
154 elif match.group(2) == "D":
155 now = datetime.datetime.utcnow()
157 # Custom format or ISO format?
159 time = datetime.date.strftime(now, match.group(4))
161 i = len(re.search("#+", match.group(4)).group(0))
162 msec = '{0:0>{i}.{i}}'.format(str(now.microsecond / 1000), i=i)
163 time = re.sub('#+', msec, time)
164 except AttributeError:
167 time = datetime.datetime.isoformat(now.replace(microsecond=0))
169 return self._format_field(tmp, match.group(1), time)
171 def emer(self, message, **kwargs):
172 self.__log("EMER", message, **kwargs)
174 def err(self, message, **kwargs):
175 self.__log("ERR", message, **kwargs)
177 def warn(self, message, **kwargs):
178 self.__log("WARN", message, **kwargs)
180 def info(self, message, **kwargs):
181 self.__log("INFO", message, **kwargs)
183 def dbg(self, message, **kwargs):
184 self.__log("DBG", message, **kwargs)
186 def __is_enabled(self, level):
187 level = LEVELS.get(level.lower(), logging.DEBUG)
188 for f, f_level in six.iteritems(Vlog.__mfl[self.name]):
189 f_level = LEVELS.get(f_level, logging.CRITICAL)
194 def emer_is_enabled(self):
195 return self.__is_enabled("EMER")
197 def err_is_enabled(self):
198 return self.__is_enabled("ERR")
200 def warn_is_enabled(self):
201 return self.__is_enabled("WARN")
203 def info_is_enabled(self):
204 return self.__is_enabled("INFO")
206 def dbg_is_enabled(self):
207 return self.__is_enabled("DBG")
209 def exception(self, message):
210 """Logs 'message' at ERR log level. Includes a backtrace when in
211 exception context."""
212 self.err(message, exc_info=True)
215 def init(log_file=None):
216 """Intializes the Vlog module. Causes Vlog to write to 'log_file' if
217 not None. Should be called after all Vlog objects have been created.
218 No logging will occur until this function is called."""
224 Vlog.__start_time = datetime.datetime.utcnow()
225 logging.raiseExceptions = False
226 Vlog.__log_file = log_file
227 for f in DESTINATIONS:
228 logger = logging.getLogger(f)
229 logger.setLevel(logging.DEBUG)
233 logger.addHandler(logging.StreamHandler(sys.stderr))
235 Vlog.add_syslog_handler()
236 elif f == "file" and Vlog.__log_file:
237 Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
238 logger.addHandler(Vlog.__file_handler)
239 except (IOError, socket.error):
240 logger.setLevel(logging.CRITICAL)
242 ovs.unixctl.command_register("vlog/reopen", "", 0, 0,
243 Vlog._unixctl_vlog_reopen, None)
244 ovs.unixctl.command_register("vlog/close", "", 0, 0,
245 Vlog._unixctl_vlog_close, None)
246 ovs.unixctl.command_register("vlog/set", "spec", 1, sys.maxsize,
247 Vlog._unixctl_vlog_set, None)
248 ovs.unixctl.command_register("vlog/list", "", 0, 0,
249 Vlog._unixctl_vlog_list, None)
252 def set_level(module, destination, level):
253 """ Sets the log level of the 'module'-'destination' tuple to 'level'.
254 All three arguments are strings which are interpreted the same as
255 arguments to the --verbose flag. Should be called after all Vlog
256 objects have already been created."""
258 module = module.lower()
259 destination = destination.lower()
260 level = level.lower()
262 if destination != "any" and destination not in DESTINATIONS:
265 if module != "any" and module not in Vlog.__mfl:
268 if level not in LEVELS:
272 modules = list(Vlog.__mfl.keys())
276 if destination == "any":
277 destinations = list(DESTINATIONS.keys())
279 destinations = [destination]
282 for f in destinations:
283 Vlog.__mfl[m][f] = level
286 def set_pattern(destination, pattern):
287 """ Sets the log pattern of the 'destination' to 'pattern' """
288 destination = destination.lower()
289 Vlog.__log_patterns[destination] = pattern
292 def add_syslog_handler(facility=None):
293 global syslog_facility, syslog_handler
295 # If handler is already added and there is no change in 'facility',
296 # there is nothing to do.
297 if (not facility or facility == syslog_facility) and syslog_handler:
300 logger = logging.getLogger('syslog')
301 # If there is no infrastructure to support python syslog, increase
302 # the logging severity level to avoid repeated errors.
303 if not os.path.exists("/dev/log"):
304 logger.setLevel(logging.CRITICAL)
308 logger.removeHandler(syslog_handler)
311 syslog_facility = facility
313 syslog_handler = logging.handlers.SysLogHandler(address="/dev/log",
314 facility=syslog_facility)
315 logger.addHandler(syslog_handler)
319 def set_levels_from_string(s):
324 words = re.split('[ :]', s)
325 if words[0] == "pattern":
327 if words[1] in DESTINATIONS and words[2]:
328 segments = [words[i] for i in range(2, len(words))]
329 pattern = "".join(segments)
330 Vlog.set_pattern(words[1], pattern)
333 return "Destination %s does not exist" % words[1]
335 return "Please supply a valid pattern and destination"
336 elif words[0] == "FACILITY":
337 if words[1] in FACILITIES:
338 Vlog.add_syslog_handler(words[1])
341 return "Facility %s is invalid" % words[1]
343 for word in [w.lower() for w in words]:
346 elif word in DESTINATIONS:
348 return "cannot specify multiple destinations"
352 return "cannot specify multiple levels"
354 elif word in Vlog.__mfl:
356 return "cannot specify multiple modules"
359 return "no destination, level, or module \"%s\"" % word
361 Vlog.set_level(module or "any", destination or "any", level or "any")
365 lines = [" console syslog file\n",
366 " ------- ------ ------\n"]
367 lines.extend(sorted(["%-16s %4s %4s %4s\n"
369 Vlog.__mfl[m]["console"],
370 Vlog.__mfl[m]["syslog"],
371 Vlog.__mfl[m]["file"]) for m in Vlog.__mfl]))
372 return ''.join(lines)
375 def reopen_log_file():
376 """Closes and then attempts to re-open the current log file. (This is
377 useful just after log rotation, to ensure that the new log file starts
381 logger = logging.getLogger("file")
382 logger.removeHandler(Vlog.__file_handler)
383 Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
384 logger.addHandler(Vlog.__file_handler)
387 def _unixctl_vlog_reopen(conn, unused_argv, unused_aux):
389 Vlog.reopen_log_file()
392 conn.reply("Logging to file not configured")
395 def _unixctl_vlog_close(conn, unused_argv, unused_aux):
397 logger = logging.getLogger("file")
398 logger.removeHandler(Vlog.__file_handler)
402 def _unixctl_vlog_set(conn, argv, unused_aux):
404 msg = Vlog.set_levels_from_string(arg)
411 def _unixctl_vlog_list(conn, unused_argv, unused_aux):
412 conn.reply(Vlog.get_levels())
415 def add_args(parser):
416 """Adds vlog related options to 'parser', an ArgumentParser object. The
417 resulting arguments parsed by 'parser' should be passed to handle_args."""
419 group = parser.add_argument_group(title="Logging Options")
420 group.add_argument("--log-file", nargs="?", const="default",
421 help="Enables logging to a file. Default log file"
422 " is used if LOG_FILE is omitted.")
423 group.add_argument("-v", "--verbose", nargs="*",
424 help="Sets logging levels, see ovs-vswitchd(8)."
428 def handle_args(args):
429 """ Handles command line arguments ('args') parsed by an ArgumentParser.
430 The ArgumentParser should have been primed by add_args(). Also takes care
431 of initializing the Vlog module."""
433 log_file = args.log_file
434 if log_file == "default":
435 log_file = "%s/%s.log" % (ovs.dirs.LOGDIR, ovs.util.PROGRAM_NAME)
437 if args.verbose is None:
439 elif args.verbose == []:
440 args.verbose = ["any:any:dbg"]
442 for verbose in args.verbose:
443 msg = Vlog.set_levels_from_string(verbose)
445 ovs.util.ovs_fatal(0, "processing \"%s\": %s" % (verbose, msg))