Currently, each python daemon has to come up with it's own logging
solution.  These logging strategies are not consistent across the
python code or with the C vlog module.  This patch adds a new
logging module which hopes to solve the problem.  This new module
generates log messages in a manner consistent with the C code.
Furthermore, it can easily be extended to support things like rate
limiters in the future.

This patch does not update any python code to use the new module.
---
 python/ovs/automake.mk |    1 +
 python/ovs/vlog.py     |  200 ++++++++++++++++++++++++++++++++++++++++++++++++
 tests/automake.mk      |    6 +-
 tests/test-vlog.py     |   37 +++++++++
 tests/testsuite.at     |    1 +
 tests/vlog.at          |   28 +++++++
 6 files changed, 271 insertions(+), 2 deletions(-)
 create mode 100644 python/ovs/vlog.py
 create mode 100644 tests/test-vlog.py
 create mode 100644 tests/vlog.at

diff --git a/python/ovs/automake.mk b/python/ovs/automake.mk
index a1787ba..2247328 100644
--- a/python/ovs/automake.mk
+++ b/python/ovs/automake.mk
@@ -20,6 +20,7 @@ ovs_pyfiles = \
        python/ovs/socket_util.py \
        python/ovs/stream.py \
        python/ovs/timeval.py \
+       python/ovs/vlog.py \
        python/ovs/util.py
 EXTRA_DIST += $(ovs_pyfiles) python/ovs/dirs.py
 
diff --git a/python/ovs/vlog.py b/python/ovs/vlog.py
new file mode 100644
index 0000000..69a3a5e
--- /dev/null
+++ b/python/ovs/vlog.py
@@ -0,0 +1,200 @@
+
+# Copyright (c) 2011 Nicira Networks
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import datetime
+import logging
+import logging.handlers
+import socket
+import sys
+
+import ovs.dirs
+import ovs.util
+
+FACILITIES = {"console": "off", "file": "info", "syslog": "info"}
+LEVELS = {
+    "dbg": logging.DEBUG,
+    "info": logging.INFO,
+    "warn": logging.WARNING,
+    "err": logging.ERROR,
+    "emer": logging.CRITICAL,
+    "off": logging.CRITICAL
+}
+
+
+def get_level(level_str):
+    return LEVELS.get(level_str.lower())
+
+
+class Vlog:
+    __inited = False
+    __msg_num = 0
+    __mfl = {}  # Module -> facilitiy -> level
+
+    def __init__(self, name):
+        """Creates a new Vlog object representing a module called 'name'.  The
+        created Vlog object will do nothing until the Vlog.init() static method
+        is called.  Once called, no more Vlog objects may be created."""
+
+        assert not Vlog.__inited
+        self.name = name.lower()
+        if name not in Vlog.__mfl:
+            Vlog.__mfl[self.name] = FACILITIES.copy()
+
+    def __log(self, level, message, exc_info=False):
+        if not Vlog.__inited:
+            return
+
+        now = datetime.datetime.now().strftime("%b %d %H:%M:%S")
+        message = ("%s|%s|%s|%s|%s"
+                   % (now, Vlog.__msg_num, self.name, level, message))
+
+        level = LEVELS.get(level.lower(), logging.DEBUG)
+        Vlog.__msg_num += 1
+        for f in FACILITIES:
+            f_level = LEVELS.get(Vlog.__mfl[self.name][f], logging.CRITICAL)
+            if level >= f_level:
+                logging.getLogger(f).log(level, message, exc_info=exc_info)
+
+    def emer(self, message):
+        self.__log("EMER", message)
+
+    def err(self, message, exc_info=False):
+        """Logs 'message' at ERR log level. Set 'exc_info' to include a
+        backtrace."""
+        self.__log("ERR", message, exc_info)
+
+    def warn(self, message):
+        self.__log("WARN", message)
+
+    def info(self, message):
+        self.__log("INFO", message)
+
+    def dbg(self, message):
+        self.__log("DBG", message)
+
+    def exception(self, message):
+        """Logs 'message' at ERR log level.  Includes a backtrace."""
+        self.err(message, exc_info=True)
+
+    @staticmethod
+    def init(log_file=None):
+        """Intializes the Vlog module.  Causes Vlog to write to 'log_file' if
+        not None.  Should be called after all Vlog objects have been created.
+        No logging will occur until this function is called."""
+
+        if Vlog.__inited:
+            return
+
+        Vlog.__inited = True
+        logging.raiseExceptions = False
+        for f in FACILITIES:
+            logger = logging.getLogger(f)
+            logger.setLevel(logging.DEBUG)
+
+            try:
+                if f == "console":
+                    logger.addHandler(logging.StreamHandler(sys.stderr))
+                elif f == "syslog":
+                    logger.addHandler(logging.handlers.SysLogHandler(
+                        address="/dev/log",
+                        facility=logging.handlers.SysLogHandler.LOG_DAEMON))
+                elif f == "file" and log_file:
+                    logger.addHandler(logging.FileHandler(log_file))
+            except (IOError, socket.error):
+                logger.setLevel(logging.CRITICAL)
+
+    @staticmethod
+    def set_level(module, facility, level):
+        """ Sets the log level of the 'module'-'facility' tuple to 'level'.
+        All three arguments are strings which are interpreted the same as
+        arguments to the --verbose flag.  Should be called after all Vlog
+        objects have already been created."""
+
+        module = module.lower()
+        facility = facility.lower()
+        level = level.lower()
+
+        if facility != "any" and facility not in FACILITIES:
+            return
+
+        if module != "any" and module not in Vlog.__mfl:
+            return
+
+        if level not in LEVELS:
+            return
+
+        if module == "any":
+            modules = Vlog.__mfl.keys()
+        else:
+            modules = [module]
+
+        if facility == "any":
+            facilities = FACILITIES.keys()
+        else:
+            facilities = [facility]
+
+        for m in modules:
+            for f in facilities:
+                Vlog.__mfl[m][f] = level
+
+
+def add_args(parser):
+    """Adds vlog related options to 'parser', an ArgumentParser object.  The
+    resulting arguments parsed by 'parser' should be passed to handle_args."""
+
+    group = parser.add_argument_group(title="Logging Options")
+    group.add_argument("--log-file", nargs="?", default="default",
+                       help="Enables logging to a file.  Default log file"
+                       " is used if LOG_FILE is omitted.")
+    group.add_argument("-v", "--verbose", nargs="*", default=None,
+                       help="Sets logging levels, see ovs-vswitchd(8)."
+                       "  Defaults to ANY:ANY:dbg.")
+
+
+def handle_args(args):
+    """ Handles command line arguments ('args') parsed by an ArgumentParser.
+    The ArgumentParser should have been primed by add_args().  Also takes care
+    of initializing the Vlog module."""
+
+    log_file = args.log_file
+    if log_file is not None and log_file == "default":
+        log_file = "%s/%s.log" % (ovs.dirs.LOGDIR, ovs.util.PROGRAM_NAME)
+
+    if args.verbose is None:
+        args.verbose = []
+    elif args.verbose == []:
+        args.verbose = ["any:any:dbg"]
+
+    for verbose in args.verbose:
+        args = verbose.split(':')
+
+        if len(args) >= 3:
+            level = args[2]
+        else:
+            level = "dbg"
+
+        if len(args) >= 2:
+            facility = args[1]
+        else:
+            facility = "any"
+
+        if len(args) >= 1:
+            module = args[0]
+        else:
+            module = "any"
+
+        Vlog.set_level(module, facility, level)
+
+    Vlog.init(log_file)
diff --git a/tests/automake.mk b/tests/automake.mk
index 581bdc8..54c0c99 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -53,7 +53,8 @@ TESTSUITE_AT = \
        tests/ovs-vsctl.at \
        tests/ovs-monitor-ipsec.at \
        tests/ovs-xapi-sync.at \
-       tests/interface-reconfigure.at
+       tests/interface-reconfigure.at \
+       tests/vlog.at
 TESTSUITE = $(srcdir)/tests/testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal
 EXTRA_DIST += tests/compare-odp-actions.pl
@@ -338,7 +339,8 @@ EXTRA_DIST += \
        tests/test-jsonrpc.py \
        tests/test-ovsdb.py \
        tests/test-reconnect.py \
-       tests/MockXenAPI.py
+       tests/MockXenAPI.py \
+       tests/test-vlog.py
 
 if HAVE_OPENSSL
 TESTPKI_FILES = \
diff --git a/tests/test-vlog.py b/tests/test-vlog.py
new file mode 100644
index 0000000..99aec16
--- /dev/null
+++ b/tests/test-vlog.py
@@ -0,0 +1,37 @@
+# Copyright (c) 2011 Nicira Networks.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+
+import ovs.vlog
+
+
+def main():
+    modules = [ovs.vlog.Vlog("module_%d" % i) for i in xrange(3)]
+
+    parser = argparse.ArgumentParser(description="Vlog Module Tester")
+    ovs.vlog.add_args(parser)
+    args = parser.parse_args()
+    ovs.vlog.handle_args(args)
+
+    for m in modules:
+        m.emer("emergency")
+        m.err("error")
+        m.warn("warning")
+        m.info("information")
+        m.dbg("debug")
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 8608572..b6fe7c3 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -67,3 +67,4 @@ m4_include([tests/ovs-vsctl.at])
 m4_include([tests/ovs-monitor-ipsec.at])
 m4_include([tests/ovs-xapi-sync.at])
 m4_include([tests/interface-reconfigure.at])
+m4_include([tests/vlog.at])
diff --git a/tests/vlog.at b/tests/vlog.at
new file mode 100644
index 0000000..bd69ea7
--- /dev/null
+++ b/tests/vlog.at
@@ -0,0 +1,28 @@
+AT_BANNER([vlog])
+
+AT_SETUP([vlog - Python])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+AT_CHECK([$PYTHON $srcdir/test-vlog.py --log-file log_file \
+-v ANY:ANY:dbg module_1:ANY:info module_2:ANY:warn ANY:syslog:off \
+2>stderr_log])
+AT_CAPTURE_FILE([log_file])
+AT_CAPTURE_FILE([stderr_log])
+
+AT_CHECK([diff log_file stderr_log])
+
+AT_CHECK([sed -e 's/.* .* ..:..:..|//' stderr_log], [0], [dnl
+0|module_0|EMER|emergency
+1|module_0|ERR|error
+2|module_0|WARN|warning
+3|module_0|INFO|information
+4|module_0|DBG|debug
+5|module_1|EMER|emergency
+6|module_1|ERR|error
+7|module_1|WARN|warning
+8|module_1|INFO|information
+10|module_2|EMER|emergency
+11|module_2|ERR|error
+12|module_2|WARN|warning
+])
+
+AT_CLEANUP
-- 
1.7.6.1

_______________________________________________
dev mailing list
[email protected]
http://openvswitch.org/mailman/listinfo/dev

Reply via email to