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:
296 syslog_facility = facility
298 logger = logging.getLogger('syslog')
300 logger.removeHandler(syslog_handler)
301 syslog_handler = logging.handlers.SysLogHandler(address="/dev/log",
302 facility=syslog_facility)
303 logger.addHandler(syslog_handler)
307 def set_levels_from_string(s):
312 words = re.split('[ :]', s)
313 if words[0] == "pattern":
315 if words[1] in DESTINATIONS and words[2]:
316 segments = [words[i] for i in range(2, len(words))]
317 pattern = "".join(segments)
318 Vlog.set_pattern(words[1], pattern)
321 return "Destination %s does not exist" % words[1]
323 return "Please supply a valid pattern and destination"
324 elif words[0] == "FACILITY":
325 if words[1] in FACILITIES:
326 Vlog.add_syslog_handler(words[1])
329 return "Facility %s is invalid" % words[1]
331 for word in [w.lower() for w in words]:
334 elif word in DESTINATIONS:
336 return "cannot specify multiple destinations"
340 return "cannot specify multiple levels"
342 elif word in Vlog.__mfl:
344 return "cannot specify multiple modules"
347 return "no destination, level, or module \"%s\"" % word
349 Vlog.set_level(module or "any", destination or "any", level or "any")
353 lines = [" console syslog file\n",
354 " ------- ------ ------\n"]
355 lines.extend(sorted(["%-16s %4s %4s %4s\n"
357 Vlog.__mfl[m]["console"],
358 Vlog.__mfl[m]["syslog"],
359 Vlog.__mfl[m]["file"]) for m in Vlog.__mfl]))
360 return ''.join(lines)
363 def reopen_log_file():
364 """Closes and then attempts to re-open the current log file. (This is
365 useful just after log rotation, to ensure that the new log file starts
369 logger = logging.getLogger("file")
370 logger.removeHandler(Vlog.__file_handler)
371 Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
372 logger.addHandler(Vlog.__file_handler)
375 def _unixctl_vlog_reopen(conn, unused_argv, unused_aux):
377 Vlog.reopen_log_file()
380 conn.reply("Logging to file not configured")
383 def _unixctl_vlog_set(conn, argv, unused_aux):
385 msg = Vlog.set_levels_from_string(arg)
392 def _unixctl_vlog_list(conn, unused_argv, unused_aux):
393 conn.reply(Vlog.get_levels())
396 def add_args(parser):
397 """Adds vlog related options to 'parser', an ArgumentParser object. The
398 resulting arguments parsed by 'parser' should be passed to handle_args."""
400 group = parser.add_argument_group(title="Logging Options")
401 group.add_argument("--log-file", nargs="?", const="default",
402 help="Enables logging to a file. Default log file"
403 " is used if LOG_FILE is omitted.")
404 group.add_argument("-v", "--verbose", nargs="*",
405 help="Sets logging levels, see ovs-vswitchd(8)."
409 def handle_args(args):
410 """ Handles command line arguments ('args') parsed by an ArgumentParser.
411 The ArgumentParser should have been primed by add_args(). Also takes care
412 of initializing the Vlog module."""
414 log_file = args.log_file
415 if log_file == "default":
416 log_file = "%s/%s.log" % (ovs.dirs.LOGDIR, ovs.util.PROGRAM_NAME)
418 if args.verbose is None:
420 elif args.verbose == []:
421 args.verbose = ["any:any:dbg"]
423 for verbose in args.verbose:
424 msg = Vlog.set_levels_from_string(verbose)
426 ovs.util.ovs_fatal(0, "processing \"%s\": %s" % (verbose, msg))