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
25 from six.moves import range
31 DESTINATIONS = {"console": "info", "file": "info", "syslog": "info"}
33 "console": "%D{%Y-%m-%dT%H:%M:%SZ}|%05N|%c%T|%p|%m",
34 "file": "%D{%Y-%m-%dT%H:%M:%S.###Z}|%05N|%c%T|%p|%m",
35 "syslog": "ovs|%05N|%c%T|%p|%m",
40 "warn": logging.WARNING,
42 "emer": logging.CRITICAL,
43 "off": logging.CRITICAL
45 FACILITIES = ['auth', 'authpriv', 'cron', 'daemon', 'ftp', 'kern', 'lpr',
46 'mail', 'news', 'syslog', 'user', 'uucp', 'local0', 'local1',
47 'local2', 'local3', 'local4', 'local5', 'local6', 'local7']
48 syslog_facility = "daemon"
52 def get_level(level_str):
53 return LEVELS.get(level_str.lower())
60 __mfl = {} # Module -> destination -> level
63 __log_patterns = PATTERNS
65 def __init__(self, name):
66 """Creates a new Vlog object representing a module called 'name'. The
67 created Vlog object will do nothing until the Vlog.init() static method
68 is called. Once called, no more Vlog objects may be created."""
70 assert not Vlog.__inited
71 self.name = name.lower()
72 if name not in Vlog.__mfl:
73 Vlog.__mfl[self.name] = DESTINATIONS.copy()
75 def __log(self, level, message, **kwargs):
79 level_num = LEVELS.get(level.lower(), logging.DEBUG)
80 msg_num = Vlog.__msg_num
83 for f, f_level in Vlog.__mfl[self.name].iteritems():
84 f_level = LEVELS.get(f_level, logging.CRITICAL)
85 if level_num >= f_level:
86 msg = self._build_message(message, f, level, msg_num)
87 logging.getLogger(f).log(level_num, msg, **kwargs)
89 def _build_message(self, message, destination, level, msg_num):
90 pattern = self.__log_patterns[destination]
93 tmp = self._format_time(tmp)
95 matches = re.findall("(%-?[0]?[0-9]?[AcmNnpPrtT])", tmp)
98 tmp = self._format_field(tmp, m, ovs.util.PROGRAM_NAME)
100 tmp = self._format_field(tmp, m, self.name)
102 tmp = self._format_field(tmp, m, message)
104 tmp = self._format_field(tmp, m, str(msg_num))
106 tmp = re.sub(m, "\n", tmp)
108 tmp = self._format_field(tmp, m, level.upper())
110 self._format_field(tmp, m, str(os.getpid()))
112 now = datetime.datetime.utcnow()
113 delta = now - self.__start_time
114 ms = delta.microseconds / 1000
115 tmp = self._format_field(tmp, m, str(ms))
117 subprogram = threading.currentThread().getName()
118 if subprogram == "MainThread":
120 tmp = self._format_field(tmp, m, subprogram)
122 subprogram = threading.currentThread().getName()
123 if not subprogram == "MainThread":
124 subprogram = "({})".format(subprogram)
127 tmp = self._format_field(tmp, m, subprogram)
130 def _format_field(self, tmp, match, replace):
131 formatting = re.compile("^%(0)?([1-9])?")
132 matches = formatting.match(match)
133 # Do we need to apply padding?
134 if not matches.group(1) and replace != "":
135 replace = replace.center(len(replace) + 2)
136 # Does the field have a minimum width
138 min_width = int(matches.group(2))
139 if len(replace) < min_width:
140 replace = replace.center(min_width)
141 return re.sub(match, replace, tmp)
143 def _format_time(self, tmp):
144 date_regex = re.compile('(%(0?[1-9]?[dD])(\{(.*)\})?)')
145 match = date_regex.search(tmp)
150 # UTC date or Local TZ?
151 if match.group(2) == "d":
152 now = datetime.datetime.now()
153 elif match.group(2) == "D":
154 now = datetime.datetime.utcnow()
156 # Custom format or ISO format?
158 time = datetime.date.strftime(now, match.group(4))
160 i = len(re.search("#+", match.group(4)).group(0))
161 msec = '{0:0>{i}.{i}}'.format(str(now.microsecond / 1000), i=i)
162 time = re.sub('#+', msec, time)
163 except AttributeError:
166 time = datetime.datetime.isoformat(now.replace(microsecond=0))
168 return self._format_field(tmp, match.group(1), time)
170 def emer(self, message, **kwargs):
171 self.__log("EMER", message, **kwargs)
173 def err(self, message, **kwargs):
174 self.__log("ERR", message, **kwargs)
176 def warn(self, message, **kwargs):
177 self.__log("WARN", message, **kwargs)
179 def info(self, message, **kwargs):
180 self.__log("INFO", message, **kwargs)
182 def dbg(self, message, **kwargs):
183 self.__log("DBG", message, **kwargs)
185 def __is_enabled(self, level):
186 level = LEVELS.get(level.lower(), logging.DEBUG)
187 for f, f_level in Vlog.__mfl[self.name].iteritems():
188 f_level = LEVELS.get(f_level, logging.CRITICAL)
193 def emer_is_enabled(self):
194 return self.__is_enabled("EMER")
196 def err_is_enabled(self):
197 return self.__is_enabled("ERR")
199 def warn_is_enabled(self):
200 return self.__is_enabled("WARN")
202 def info_is_enabled(self):
203 return self.__is_enabled("INFO")
205 def dbg_is_enabled(self):
206 return self.__is_enabled("DBG")
208 def exception(self, message):
209 """Logs 'message' at ERR log level. Includes a backtrace when in
210 exception context."""
211 self.err(message, exc_info=True)
214 def init(log_file=None):
215 """Intializes the Vlog module. Causes Vlog to write to 'log_file' if
216 not None. Should be called after all Vlog objects have been created.
217 No logging will occur until this function is called."""
223 Vlog.__start_time = datetime.datetime.utcnow()
224 logging.raiseExceptions = False
225 Vlog.__log_file = log_file
226 for f in DESTINATIONS:
227 logger = logging.getLogger(f)
228 logger.setLevel(logging.DEBUG)
232 logger.addHandler(logging.StreamHandler(sys.stderr))
234 Vlog.add_syslog_handler()
235 elif f == "file" and Vlog.__log_file:
236 Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
237 logger.addHandler(Vlog.__file_handler)
238 except (IOError, socket.error):
239 logger.setLevel(logging.CRITICAL)
241 ovs.unixctl.command_register("vlog/reopen", "", 0, 0,
242 Vlog._unixctl_vlog_reopen, None)
243 ovs.unixctl.command_register("vlog/set", "spec", 1, sys.maxint,
244 Vlog._unixctl_vlog_set, None)
245 ovs.unixctl.command_register("vlog/list", "", 0, 0,
246 Vlog._unixctl_vlog_list, None)
249 def set_level(module, destination, level):
250 """ Sets the log level of the 'module'-'destination' tuple to 'level'.
251 All three arguments are strings which are interpreted the same as
252 arguments to the --verbose flag. Should be called after all Vlog
253 objects have already been created."""
255 module = module.lower()
256 destination = destination.lower()
257 level = level.lower()
259 if destination != "any" and destination not in DESTINATIONS:
262 if module != "any" and module not in Vlog.__mfl:
265 if level not in LEVELS:
269 modules = Vlog.__mfl.keys()
273 if destination == "any":
274 destinations = DESTINATIONS.keys()
276 destinations = [destination]
279 for f in destinations:
280 Vlog.__mfl[m][f] = level
283 def set_pattern(destination, pattern):
284 """ Sets the log pattern of the 'destination' to 'pattern' """
285 destination = destination.lower()
286 Vlog.__log_patterns[destination] = pattern
289 def add_syslog_handler(facility=None):
290 global syslog_facility, syslog_handler
292 # If handler is already added and there is no change in 'facility',
293 # there is nothing to do.
294 if (not facility or facility == syslog_facility) and syslog_handler:
297 logger = logging.getLogger('syslog')
298 # If there is no infrastructure to support python syslog, increase
299 # the logging severity level to avoid repeated errors.
300 if not os.path.exists("/dev/log"):
301 logger.setLevel(logging.CRITICAL)
305 logger.removeHandler(syslog_handler)
308 syslog_facility = facility
310 syslog_handler = logging.handlers.SysLogHandler(address="/dev/log",
311 facility=syslog_facility)
312 logger.addHandler(syslog_handler)
316 def set_levels_from_string(s):
321 words = re.split('[ :]', s)
322 if words[0] == "pattern":
324 if words[1] in DESTINATIONS and words[2]:
325 segments = [words[i] for i in range(2, len(words))]
326 pattern = "".join(segments)
327 Vlog.set_pattern(words[1], pattern)
330 return "Destination %s does not exist" % words[1]
332 return "Please supply a valid pattern and destination"
333 elif words[0] == "FACILITY":
334 if words[1] in FACILITIES:
335 Vlog.add_syslog_handler(words[1])
338 return "Facility %s is invalid" % words[1]
340 for word in [w.lower() for w in words]:
343 elif word in DESTINATIONS:
345 return "cannot specify multiple destinations"
349 return "cannot specify multiple levels"
351 elif word in Vlog.__mfl:
353 return "cannot specify multiple modules"
356 return "no destination, level, or module \"%s\"" % word
358 Vlog.set_level(module or "any", destination or "any", level or "any")
362 lines = [" console syslog file\n",
363 " ------- ------ ------\n"]
364 lines.extend(sorted(["%-16s %4s %4s %4s\n"
366 Vlog.__mfl[m]["console"],
367 Vlog.__mfl[m]["syslog"],
368 Vlog.__mfl[m]["file"]) for m in Vlog.__mfl]))
369 return ''.join(lines)
372 def reopen_log_file():
373 """Closes and then attempts to re-open the current log file. (This is
374 useful just after log rotation, to ensure that the new log file starts
378 logger = logging.getLogger("file")
379 logger.removeHandler(Vlog.__file_handler)
380 Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
381 logger.addHandler(Vlog.__file_handler)
384 def _unixctl_vlog_reopen(conn, unused_argv, unused_aux):
386 Vlog.reopen_log_file()
389 conn.reply("Logging to file not configured")
392 def _unixctl_vlog_set(conn, argv, unused_aux):
394 msg = Vlog.set_levels_from_string(arg)
401 def _unixctl_vlog_list(conn, unused_argv, unused_aux):
402 conn.reply(Vlog.get_levels())
405 def add_args(parser):
406 """Adds vlog related options to 'parser', an ArgumentParser object. The
407 resulting arguments parsed by 'parser' should be passed to handle_args."""
409 group = parser.add_argument_group(title="Logging Options")
410 group.add_argument("--log-file", nargs="?", const="default",
411 help="Enables logging to a file. Default log file"
412 " is used if LOG_FILE is omitted.")
413 group.add_argument("-v", "--verbose", nargs="*",
414 help="Sets logging levels, see ovs-vswitchd(8)."
418 def handle_args(args):
419 """ Handles command line arguments ('args') parsed by an ArgumentParser.
420 The ArgumentParser should have been primed by add_args(). Also takes care
421 of initializing the Vlog module."""
423 log_file = args.log_file
424 if log_file == "default":
425 log_file = "%s/%s.log" % (ovs.dirs.LOGDIR, ovs.util.PROGRAM_NAME)
427 if args.verbose is None:
429 elif args.verbose == []:
430 args.verbose = ["any:any:dbg"]
432 for verbose in args.verbose:
433 msg = Vlog.set_levels_from_string(verbose)
435 ovs.util.ovs_fatal(0, "processing \"%s\": %s" % (verbose, msg))