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