python: Implement "vlog/reopen" unixctl command in Python vlog.
[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 socket
20 import sys
21
22 import ovs.dirs
23 import ovs.unixctl
24 import ovs.util
25
26 FACILITIES = {"console": "info", "file": "info", "syslog": "info"}
27 LEVELS = {
28     "dbg": logging.DEBUG,
29     "info": logging.INFO,
30     "warn": logging.WARNING,
31     "err": logging.ERROR,
32     "emer": logging.CRITICAL,
33     "off": logging.CRITICAL
34 }
35
36
37 def get_level(level_str):
38     return LEVELS.get(level_str.lower())
39
40
41 class Vlog:
42     __inited = False
43     __msg_num = 0
44     __mfl = {}  # Module -> facility -> level
45     __log_file = None
46     __file_handler = None
47
48     def __init__(self, name):
49         """Creates a new Vlog object representing a module called 'name'.  The
50         created Vlog object will do nothing until the Vlog.init() static method
51         is called.  Once called, no more Vlog objects may be created."""
52
53         assert not Vlog.__inited
54         self.name = name.lower()
55         if name not in Vlog.__mfl:
56             Vlog.__mfl[self.name] = FACILITIES.copy()
57
58     def __log(self, level, message, **kwargs):
59         if not Vlog.__inited:
60             return
61
62         now = datetime.datetime.now().strftime("%b %d %H:%M:%S")
63         message = ("%s|%s|%s|%s|%s"
64                    % (now, Vlog.__msg_num, self.name, level, message))
65
66         level = LEVELS.get(level.lower(), logging.DEBUG)
67         Vlog.__msg_num += 1
68
69         for f, f_level in Vlog.__mfl[self.name].iteritems():
70             f_level = LEVELS.get(f_level, logging.CRITICAL)
71             if level >= f_level:
72                 logging.getLogger(f).log(level, message, **kwargs)
73
74     def emer(self, message, **kwargs):
75         self.__log("EMER", message, **kwargs)
76
77     def err(self, message, **kwargs):
78         self.__log("ERR", message, **kwargs)
79
80     def warn(self, message, **kwargs):
81         self.__log("WARN", message, **kwargs)
82
83     def info(self, message, **kwargs):
84         self.__log("INFO", message, **kwargs)
85
86     def dbg(self, message, **kwargs):
87         self.__log("DBG", message, **kwargs)
88
89     def exception(self, message):
90         """Logs 'message' at ERR log level.  Includes a backtrace when in
91         exception context."""
92         self.err(message, exc_info=True)
93
94     @staticmethod
95     def init(log_file=None):
96         """Intializes the Vlog module.  Causes Vlog to write to 'log_file' if
97         not None.  Should be called after all Vlog objects have been created.
98         No logging will occur until this function is called."""
99
100         if Vlog.__inited:
101             return
102
103         Vlog.__inited = True
104         logging.raiseExceptions = False
105         Vlog.__log_file = log_file
106         for f in FACILITIES:
107             logger = logging.getLogger(f)
108             logger.setLevel(logging.DEBUG)
109
110             try:
111                 if f == "console":
112                     logger.addHandler(logging.StreamHandler(sys.stderr))
113                 elif f == "syslog":
114                     logger.addHandler(logging.handlers.SysLogHandler(
115                         address="/dev/log",
116                         facility=logging.handlers.SysLogHandler.LOG_DAEMON))
117                 elif f == "file" and Vlog.__log_file:
118                     Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
119                     logger.addHandler(Vlog.__file_handler)
120             except (IOError, socket.error):
121                 logger.setLevel(logging.CRITICAL)
122
123         ovs.unixctl.command_register("vlog/reopen", "", 0, 0,
124                                      Vlog._unixctl_vlog_reopen, None)
125
126     @staticmethod
127     def set_level(module, facility, level):
128         """ Sets the log level of the 'module'-'facility' tuple to 'level'.
129         All three arguments are strings which are interpreted the same as
130         arguments to the --verbose flag.  Should be called after all Vlog
131         objects have already been created."""
132
133         module = module.lower()
134         facility = facility.lower()
135         level = level.lower()
136
137         if facility != "any" and facility not in FACILITIES:
138             return
139
140         if module != "any" and module not in Vlog.__mfl:
141             return
142
143         if level not in LEVELS:
144             return
145
146         if module == "any":
147             modules = Vlog.__mfl.keys()
148         else:
149             modules = [module]
150
151         if facility == "any":
152             facilities = FACILITIES.keys()
153         else:
154             facilities = [facility]
155
156         for m in modules:
157             for f in facilities:
158                 Vlog.__mfl[m][f] = level
159
160     @staticmethod
161     def reopen_log_file():
162         """Closes and then attempts to re-open the current log file.  (This is
163         useful just after log rotation, to ensure that the new log file starts
164         being used.)"""
165
166         if Vlog.__log_file:
167             logger = logging.getLogger("file")
168             logger.removeHandler(Vlog.__file_handler)
169             Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
170             logger.addHandler(Vlog.__file_handler)
171
172     @staticmethod
173     def _unixctl_vlog_reopen(conn, unused_argv, unused_aux):
174         if Vlog.__log_file:
175             Vlog.reopen_log_file()
176             conn.reply(None)
177         else:
178             conn.reply("Logging to file not configured")
179
180 def add_args(parser):
181     """Adds vlog related options to 'parser', an ArgumentParser object.  The
182     resulting arguments parsed by 'parser' should be passed to handle_args."""
183
184     group = parser.add_argument_group(title="Logging Options")
185     group.add_argument("--log-file", nargs="?", const="default",
186                        help="Enables logging to a file.  Default log file"
187                        " is used if LOG_FILE is omitted.")
188     group.add_argument("-v", "--verbose", nargs="*",
189                        help="Sets logging levels, see ovs-vswitchd(8)."
190                        "  Defaults to ANY:ANY:dbg.")
191
192
193 def handle_args(args):
194     """ Handles command line arguments ('args') parsed by an ArgumentParser.
195     The ArgumentParser should have been primed by add_args().  Also takes care
196     of initializing the Vlog module."""
197
198     log_file = args.log_file
199     if log_file == "default":
200         log_file = "%s/%s.log" % (ovs.dirs.LOGDIR, ovs.util.PROGRAM_NAME)
201
202     if args.verbose is None:
203         args.verbose = []
204     elif args.verbose == []:
205         args.verbose = ["any:any:dbg"]
206
207     for verbose in args.verbose:
208         args = verbose.split(':')
209
210         if len(args) >= 3:
211             level = args[2]
212         else:
213             level = "dbg"
214
215         if len(args) >= 2:
216             facility = args[1]
217         else:
218             facility = "any"
219
220         if len(args) >= 1:
221             module = args[0]
222         else:
223             module = "any"
224
225         Vlog.set_level(module, facility, level)
226
227     Vlog.init(log_file)