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