f7ace66fdb0f42d210f9dcb0cceab87875622be8
[sliver-openvswitch.git] / python / ovs / vlog.py
1
2 # Copyright (c) 2011, 2012 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         now = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
64         message = ("%s|%s|%s|%s|%s"
65                    % (now, Vlog.__msg_num, self.name, level, message))
66
67         level = LEVELS.get(level.lower(), logging.DEBUG)
68         Vlog.__msg_num += 1
69
70         for f, f_level in Vlog.__mfl[self.name].iteritems():
71             f_level = LEVELS.get(f_level, logging.CRITICAL)
72             if level >= f_level:
73                 logging.getLogger(f).log(level, message, **kwargs)
74
75     def emer(self, message, **kwargs):
76         self.__log("EMER", message, **kwargs)
77
78     def err(self, message, **kwargs):
79         self.__log("ERR", message, **kwargs)
80
81     def warn(self, message, **kwargs):
82         self.__log("WARN", message, **kwargs)
83
84     def info(self, message, **kwargs):
85         self.__log("INFO", message, **kwargs)
86
87     def dbg(self, message, **kwargs):
88         self.__log("DBG", message, **kwargs)
89
90     def exception(self, message):
91         """Logs 'message' at ERR log level.  Includes a backtrace when in
92         exception context."""
93         self.err(message, exc_info=True)
94
95     @staticmethod
96     def init(log_file=None):
97         """Intializes the Vlog module.  Causes Vlog to write to 'log_file' if
98         not None.  Should be called after all Vlog objects have been created.
99         No logging will occur until this function is called."""
100
101         if Vlog.__inited:
102             return
103
104         Vlog.__inited = True
105         logging.raiseExceptions = False
106         Vlog.__log_file = log_file
107         for f in FACILITIES:
108             logger = logging.getLogger(f)
109             logger.setLevel(logging.DEBUG)
110
111             try:
112                 if f == "console":
113                     logger.addHandler(logging.StreamHandler(sys.stderr))
114                 elif f == "syslog":
115                     logger.addHandler(logging.handlers.SysLogHandler(
116                         address="/dev/log",
117                         facility=logging.handlers.SysLogHandler.LOG_DAEMON))
118                 elif f == "file" and Vlog.__log_file:
119                     Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
120                     logger.addHandler(Vlog.__file_handler)
121             except (IOError, socket.error):
122                 logger.setLevel(logging.CRITICAL)
123
124         ovs.unixctl.command_register("vlog/reopen", "", 0, 0,
125                                      Vlog._unixctl_vlog_reopen, None)
126         ovs.unixctl.command_register("vlog/set", "spec", 1, sys.maxint,
127                                      Vlog._unixctl_vlog_set, None)
128         ovs.unixctl.command_register("vlog/list", "", 0, 0,
129                                      Vlog._unixctl_vlog_list, None)
130
131     @staticmethod
132     def set_level(module, facility, level):
133         """ Sets the log level of the 'module'-'facility' tuple to 'level'.
134         All three arguments are strings which are interpreted the same as
135         arguments to the --verbose flag.  Should be called after all Vlog
136         objects have already been created."""
137
138         module = module.lower()
139         facility = facility.lower()
140         level = level.lower()
141
142         if facility != "any" and facility not in FACILITIES:
143             return
144
145         if module != "any" and module not in Vlog.__mfl:
146             return
147
148         if level not in LEVELS:
149             return
150
151         if module == "any":
152             modules = Vlog.__mfl.keys()
153         else:
154             modules = [module]
155
156         if facility == "any":
157             facilities = FACILITIES.keys()
158         else:
159             facilities = [facility]
160
161         for m in modules:
162             for f in facilities:
163                 Vlog.__mfl[m][f] = level
164
165     @staticmethod
166     def set_levels_from_string(s):
167         module = None
168         level = None
169         facility = None
170
171         for word in [w.lower() for w in re.split('[ :]', s)]:
172             if word == "any":
173                 pass
174             elif word in FACILITIES:
175                 if facility:
176                     return "cannot specify multiple facilities"
177                 facility = word
178             elif word in LEVELS:
179                 if level:
180                     return "cannot specify multiple levels"
181                 level = word
182             elif word in Vlog.__mfl:
183                 if module:
184                     return "cannot specify multiple modules"
185                 module = word
186             else:
187                 return "no facility, level, or module \"%s\"" % word
188
189         Vlog.set_level(module or "any", facility or "any", level or "any")
190
191     @staticmethod
192     def get_levels():
193         lines = ["                 console    syslog    file\n",
194                  "                 -------    ------    ------\n"]
195         lines.extend(sorted(["%-16s  %4s       %4s       %4s\n"
196                              % (m,
197                                 Vlog.__mfl[m]["console"],
198                                 Vlog.__mfl[m]["syslog"],
199                                 Vlog.__mfl[m]["file"]) for m in Vlog.__mfl]))
200         return ''.join(lines)
201
202     @staticmethod
203     def reopen_log_file():
204         """Closes and then attempts to re-open the current log file.  (This is
205         useful just after log rotation, to ensure that the new log file starts
206         being used.)"""
207
208         if Vlog.__log_file:
209             logger = logging.getLogger("file")
210             logger.removeHandler(Vlog.__file_handler)
211             Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
212             logger.addHandler(Vlog.__file_handler)
213
214     @staticmethod
215     def _unixctl_vlog_reopen(conn, unused_argv, unused_aux):
216         if Vlog.__log_file:
217             Vlog.reopen_log_file()
218             conn.reply(None)
219         else:
220             conn.reply("Logging to file not configured")
221
222     @staticmethod
223     def _unixctl_vlog_set(conn, argv, unused_aux):
224         for arg in argv:
225             msg = Vlog.set_levels_from_string(arg)
226             if msg:
227                 conn.reply(msg)
228                 return
229         conn.reply(None)
230
231     @staticmethod
232     def _unixctl_vlog_list(conn, unused_argv, unused_aux):
233         conn.reply(Vlog.get_levels())
234
235 def add_args(parser):
236     """Adds vlog related options to 'parser', an ArgumentParser object.  The
237     resulting arguments parsed by 'parser' should be passed to handle_args."""
238
239     group = parser.add_argument_group(title="Logging Options")
240     group.add_argument("--log-file", nargs="?", const="default",
241                        help="Enables logging to a file.  Default log file"
242                        " is used if LOG_FILE is omitted.")
243     group.add_argument("-v", "--verbose", nargs="*",
244                        help="Sets logging levels, see ovs-vswitchd(8)."
245                        "  Defaults to dbg.")
246
247
248 def handle_args(args):
249     """ Handles command line arguments ('args') parsed by an ArgumentParser.
250     The ArgumentParser should have been primed by add_args().  Also takes care
251     of initializing the Vlog module."""
252
253     log_file = args.log_file
254     if log_file == "default":
255         log_file = "%s/%s.log" % (ovs.dirs.LOGDIR, ovs.util.PROGRAM_NAME)
256
257     if args.verbose is None:
258         args.verbose = []
259     elif args.verbose == []:
260         args.verbose = ["any:any:dbg"]
261
262     for verbose in args.verbose:
263         msg = Vlog.set_levels_from_string(verbose)
264         if msg:
265             ovs.util.ovs_fatal(0, "processing \"%s\": %s" % (verbose, msg))
266
267     Vlog.init(log_file)