478f08e86d0b80414099cf485f449b12b9ea3cf2
[sliver-openvswitch.git] / python / ovs / vlog.py
1
2 # Copyright (c) 2011, 2012, 2013 Nicira, Inc.
3 #
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15
16 import datetime
17 import logging
18 import logging.handlers
19 import re
20 import socket
21 import sys
22
23 import ovs.dirs
24 import ovs.unixctl
25 import ovs.util
26
27 FACILITIES = {"console": "info", "file": "info", "syslog": "info"}
28 LEVELS = {
29     "dbg": logging.DEBUG,
30     "info": logging.INFO,
31     "warn": logging.WARNING,
32     "err": logging.ERROR,
33     "emer": logging.CRITICAL,
34     "off": logging.CRITICAL
35 }
36
37
38 def get_level(level_str):
39     return LEVELS.get(level_str.lower())
40
41
42 class Vlog:
43     __inited = False
44     __msg_num = 0
45     __mfl = {}  # Module -> facility -> level
46     __log_file = None
47     __file_handler = None
48
49     def __init__(self, name):
50         """Creates a new Vlog object representing a module called 'name'.  The
51         created Vlog object will do nothing until the Vlog.init() static method
52         is called.  Once called, no more Vlog objects may be created."""
53
54         assert not Vlog.__inited
55         self.name = name.lower()
56         if name not in Vlog.__mfl:
57             Vlog.__mfl[self.name] = FACILITIES.copy()
58
59     def __log(self, level, message, **kwargs):
60         if not Vlog.__inited:
61             return
62
63         dt = datetime.datetime.utcnow();
64         now = dt.strftime("%Y-%m-%dT%H:%M:%S.%%03iZ") % (dt.microsecond/1000)
65         syslog_message = ("%s|%s|%s|%s"
66                            % (Vlog.__msg_num, self.name, level, message))
67
68         level = LEVELS.get(level.lower(), logging.DEBUG)
69         Vlog.__msg_num += 1
70
71         for f, f_level in Vlog.__mfl[self.name].iteritems():
72             f_level = LEVELS.get(f_level, logging.CRITICAL)
73             if level >= f_level:
74                 if f == "syslog":
75                     message = "ovs|" + syslog_message
76                 else:
77                     message = "%s|%s" % (now, syslog_message)
78                 logging.getLogger(f).log(level, message, **kwargs)
79
80     def emer(self, message, **kwargs):
81         self.__log("EMER", message, **kwargs)
82
83     def err(self, message, **kwargs):
84         self.__log("ERR", message, **kwargs)
85
86     def warn(self, message, **kwargs):
87         self.__log("WARN", message, **kwargs)
88
89     def info(self, message, **kwargs):
90         self.__log("INFO", message, **kwargs)
91
92     def dbg(self, message, **kwargs):
93         self.__log("DBG", message, **kwargs)
94
95     def __is_enabled(self, level):
96         level = LEVELS.get(level.lower(), logging.DEBUG)
97         for f, f_level in Vlog.__mfl[self.name].iteritems():
98             f_level = LEVELS.get(f_level, logging.CRITICAL)
99             if level >= f_level:
100                 return True
101         return False
102
103     def emer_is_enabled(self):
104         return self.__is_enabled("EMER")
105
106     def err_is_enabled(self):
107         return self.__is_enabled("ERR")
108
109     def warn_is_enabled(self):
110         return self.__is_enabled("WARN")
111
112     def info_is_enabled(self):
113         return self.__is_enabled("INFO")
114
115     def dbg_is_enabled(self):
116         return self.__is_enabled("DBG")
117
118     def exception(self, message):
119         """Logs 'message' at ERR log level.  Includes a backtrace when in
120         exception context."""
121         self.err(message, exc_info=True)
122
123     @staticmethod
124     def init(log_file=None):
125         """Intializes the Vlog module.  Causes Vlog to write to 'log_file' if
126         not None.  Should be called after all Vlog objects have been created.
127         No logging will occur until this function is called."""
128
129         if Vlog.__inited:
130             return
131
132         Vlog.__inited = True
133         logging.raiseExceptions = False
134         Vlog.__log_file = log_file
135         for f in FACILITIES:
136             logger = logging.getLogger(f)
137             logger.setLevel(logging.DEBUG)
138
139             try:
140                 if f == "console":
141                     logger.addHandler(logging.StreamHandler(sys.stderr))
142                 elif f == "syslog":
143                     logger.addHandler(logging.handlers.SysLogHandler(
144                         address="/dev/log",
145                         facility=logging.handlers.SysLogHandler.LOG_DAEMON))
146                 elif f == "file" and Vlog.__log_file:
147                     Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
148                     logger.addHandler(Vlog.__file_handler)
149             except (IOError, socket.error):
150                 logger.setLevel(logging.CRITICAL)
151
152         ovs.unixctl.command_register("vlog/reopen", "", 0, 0,
153                                      Vlog._unixctl_vlog_reopen, None)
154         ovs.unixctl.command_register("vlog/set", "spec", 1, sys.maxint,
155                                      Vlog._unixctl_vlog_set, None)
156         ovs.unixctl.command_register("vlog/list", "", 0, 0,
157                                      Vlog._unixctl_vlog_list, None)
158
159     @staticmethod
160     def set_level(module, facility, level):
161         """ Sets the log level of the 'module'-'facility' tuple to 'level'.
162         All three arguments are strings which are interpreted the same as
163         arguments to the --verbose flag.  Should be called after all Vlog
164         objects have already been created."""
165
166         module = module.lower()
167         facility = facility.lower()
168         level = level.lower()
169
170         if facility != "any" and facility not in FACILITIES:
171             return
172
173         if module != "any" and module not in Vlog.__mfl:
174             return
175
176         if level not in LEVELS:
177             return
178
179         if module == "any":
180             modules = Vlog.__mfl.keys()
181         else:
182             modules = [module]
183
184         if facility == "any":
185             facilities = FACILITIES.keys()
186         else:
187             facilities = [facility]
188
189         for m in modules:
190             for f in facilities:
191                 Vlog.__mfl[m][f] = level
192
193     @staticmethod
194     def set_levels_from_string(s):
195         module = None
196         level = None
197         facility = None
198
199         for word in [w.lower() for w in re.split('[ :]', s)]:
200             if word == "any":
201                 pass
202             elif word in FACILITIES:
203                 if facility:
204                     return "cannot specify multiple facilities"
205                 facility = word
206             elif word in LEVELS:
207                 if level:
208                     return "cannot specify multiple levels"
209                 level = word
210             elif word in Vlog.__mfl:
211                 if module:
212                     return "cannot specify multiple modules"
213                 module = word
214             else:
215                 return "no facility, level, or module \"%s\"" % word
216
217         Vlog.set_level(module or "any", facility or "any", level or "any")
218
219     @staticmethod
220     def get_levels():
221         lines = ["                 console    syslog    file\n",
222                  "                 -------    ------    ------\n"]
223         lines.extend(sorted(["%-16s  %4s       %4s       %4s\n"
224                              % (m,
225                                 Vlog.__mfl[m]["console"],
226                                 Vlog.__mfl[m]["syslog"],
227                                 Vlog.__mfl[m]["file"]) for m in Vlog.__mfl]))
228         return ''.join(lines)
229
230     @staticmethod
231     def reopen_log_file():
232         """Closes and then attempts to re-open the current log file.  (This is
233         useful just after log rotation, to ensure that the new log file starts
234         being used.)"""
235
236         if Vlog.__log_file:
237             logger = logging.getLogger("file")
238             logger.removeHandler(Vlog.__file_handler)
239             Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
240             logger.addHandler(Vlog.__file_handler)
241
242     @staticmethod
243     def _unixctl_vlog_reopen(conn, unused_argv, unused_aux):
244         if Vlog.__log_file:
245             Vlog.reopen_log_file()
246             conn.reply(None)
247         else:
248             conn.reply("Logging to file not configured")
249
250     @staticmethod
251     def _unixctl_vlog_set(conn, argv, unused_aux):
252         for arg in argv:
253             msg = Vlog.set_levels_from_string(arg)
254             if msg:
255                 conn.reply(msg)
256                 return
257         conn.reply(None)
258
259     @staticmethod
260     def _unixctl_vlog_list(conn, unused_argv, unused_aux):
261         conn.reply(Vlog.get_levels())
262
263 def add_args(parser):
264     """Adds vlog related options to 'parser', an ArgumentParser object.  The
265     resulting arguments parsed by 'parser' should be passed to handle_args."""
266
267     group = parser.add_argument_group(title="Logging Options")
268     group.add_argument("--log-file", nargs="?", const="default",
269                        help="Enables logging to a file.  Default log file"
270                        " is used if LOG_FILE is omitted.")
271     group.add_argument("-v", "--verbose", nargs="*",
272                        help="Sets logging levels, see ovs-vswitchd(8)."
273                        "  Defaults to dbg.")
274
275
276 def handle_args(args):
277     """ Handles command line arguments ('args') parsed by an ArgumentParser.
278     The ArgumentParser should have been primed by add_args().  Also takes care
279     of initializing the Vlog module."""
280
281     log_file = args.log_file
282     if log_file == "default":
283         log_file = "%s/%s.log" % (ovs.dirs.LOGDIR, ovs.util.PROGRAM_NAME)
284
285     if args.verbose is None:
286         args.verbose = []
287     elif args.verbose == []:
288         args.verbose = ["any:any:dbg"]
289
290     for verbose in args.verbose:
291         msg = Vlog.set_levels_from_string(verbose)
292         if msg:
293             ovs.util.ovs_fatal(0, "processing \"%s\": %s" % (verbose, msg))
294
295     Vlog.init(log_file)