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
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/set", "spec", 1, sys.maxsize,
245 Vlog._unixctl_vlog_set, None)
246 ovs.unixctl.command_register("vlog/list", "", 0, 0,
247 Vlog._unixctl_vlog_list, None)
250 def set_level(module, destination, level):
251 """ Sets the log level of the 'module'-'destination' tuple to 'level'.
252 All three arguments are strings which are interpreted the same as
253 arguments to the --verbose flag. Should be called after all Vlog
254 objects have already been created."""
256 module = module.lower()
257 destination = destination.lower()
258 level = level.lower()
260 if destination != "any" and destination not in DESTINATIONS:
263 if module != "any" and module not in Vlog.__mfl:
266 if level not in LEVELS:
270 modules = list(Vlog.__mfl.keys())
274 if destination == "any":
275 destinations = list(DESTINATIONS.keys())
277 destinations = [destination]
280 for f in destinations:
281 Vlog.__mfl[m][f] = level
284 def set_pattern(destination, pattern):
285 """ Sets the log pattern of the 'destination' to 'pattern' """
286 destination = destination.lower()
287 Vlog.__log_patterns[destination] = pattern
290 def add_syslog_handler(facility=None):
291 global syslog_facility, syslog_handler
293 # If handler is already added and there is no change in 'facility',
294 # there is nothing to do.
295 if (not facility or facility == syslog_facility) and syslog_handler:
298 logger = logging.getLogger('syslog')
299 # If there is no infrastructure to support python syslog, increase
300 # the logging severity level to avoid repeated errors.
301 if not os.path.exists("/dev/log"):
302 logger.setLevel(logging.CRITICAL)
306 logger.removeHandler(syslog_handler)
309 syslog_facility = facility
311 syslog_handler = logging.handlers.SysLogHandler(address="/dev/log",
312 facility=syslog_facility)
313 logger.addHandler(syslog_handler)
317 def set_levels_from_string(s):
322 words = re.split('[ :]', s)
323 if words[0] == "pattern":
325 if words[1] in DESTINATIONS and words[2]:
326 segments = [words[i] for i in range(2, len(words))]
327 pattern = "".join(segments)
328 Vlog.set_pattern(words[1], pattern)
331 return "Destination %s does not exist" % words[1]
333 return "Please supply a valid pattern and destination"
334 elif words[0] == "FACILITY":
335 if words[1] in FACILITIES:
336 Vlog.add_syslog_handler(words[1])
339 return "Facility %s is invalid" % words[1]
341 for word in [w.lower() for w in words]:
344 elif word in DESTINATIONS:
346 return "cannot specify multiple destinations"
350 return "cannot specify multiple levels"
352 elif word in Vlog.__mfl:
354 return "cannot specify multiple modules"
357 return "no destination, level, or module \"%s\"" % word
359 Vlog.set_level(module or "any", destination or "any", level or "any")
363 lines = [" console syslog file\n",
364 " ------- ------ ------\n"]
365 lines.extend(sorted(["%-16s %4s %4s %4s\n"
367 Vlog.__mfl[m]["console"],
368 Vlog.__mfl[m]["syslog"],
369 Vlog.__mfl[m]["file"]) for m in Vlog.__mfl]))
370 return ''.join(lines)
373 def reopen_log_file():
374 """Closes and then attempts to re-open the current log file. (This is
375 useful just after log rotation, to ensure that the new log file starts
379 logger = logging.getLogger("file")
380 logger.removeHandler(Vlog.__file_handler)
381 Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
382 logger.addHandler(Vlog.__file_handler)
385 def _unixctl_vlog_reopen(conn, unused_argv, unused_aux):
387 Vlog.reopen_log_file()
390 conn.reply("Logging to file not configured")
393 def _unixctl_vlog_set(conn, argv, unused_aux):
395 msg = Vlog.set_levels_from_string(arg)
402 def _unixctl_vlog_list(conn, unused_argv, unused_aux):
403 conn.reply(Vlog.get_levels())
406 def add_args(parser):
407 """Adds vlog related options to 'parser', an ArgumentParser object. The
408 resulting arguments parsed by 'parser' should be passed to handle_args."""
410 group = parser.add_argument_group(title="Logging Options")
411 group.add_argument("--log-file", nargs="?", const="default",
412 help="Enables logging to a file. Default log file"
413 " is used if LOG_FILE is omitted.")
414 group.add_argument("-v", "--verbose", nargs="*",
415 help="Sets logging levels, see ovs-vswitchd(8)."
419 def handle_args(args):
420 """ Handles command line arguments ('args') parsed by an ArgumentParser.
421 The ArgumentParser should have been primed by add_args(). Also takes care
422 of initializing the Vlog module."""
424 log_file = args.log_file
425 if log_file == "default":
426 log_file = "%s/%s.log" % (ovs.dirs.LOGDIR, ovs.util.PROGRAM_NAME)
428 if args.verbose is None:
430 elif args.verbose == []:
431 args.verbose = ["any:any:dbg"]
433 for verbose in args.verbose:
434 msg = Vlog.set_levels_from_string(verbose)
436 ovs.util.ovs_fatal(0, "processing \"%s\": %s" % (verbose, msg))