vlog: Ability to override the default log facility.
authorGurucharan Shetty <gshetty@nicira.com>
Mon, 26 Jan 2015 16:04:25 +0000 (08:04 -0800)
committerGurucharan Shetty <gshetty@nicira.com>
Wed, 28 Jan 2015 16:32:37 +0000 (08:32 -0800)
When Open vSwitch is run in hundreds of hypervisors, it is
useful to collect log messages through log collectors. To
collect log messages like this, it is useful to log them
in a particular RFC5424 facility in the local system. The
log collectors can then be used to collect logs anytime
desired.

This commit provides a sysadmin the ability to specify the
facility through which the log messages are logged.

Signed-off-by: Gurucharan Shetty <gshetty@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
include/windows/syslog.h
lib/vlog.c
lib/vlog.man
python/ovs/vlog.py
tests/vlog.at
utilities/ovs-appctl.8.in

index 41267da..3925ed6 100644 (file)
 #define LOG_NDELAY      8       /* don't delay open */
 #define LOG_DAEMON      24      /* system daemons */
 
+#define LOG_KERN      (0<<3)  /* kernel messages */
+#define LOG_USER      (1<<3)  /* user-level messages */
+#define LOG_MAIL      (2<<3)  /* mail system */
+#define LOG_DAEMON    (3<<3)  /* system daemons */
+#define LOG_AUTH      (4<<3)  /* security/authorization messages */
+#define LOG_SYSLOG    (5<<3)  /* messages generated internally by syslogd */
+#define LOG_LPR       (6<<3)  /* line printer subsystem */
+#define LOG_NEWS      (7<<3)  /* network news subsystem */
+#define LOG_UUCP      (8<<3)  /* UUCP subsystem */
+#define LOG_CRON      (9<<3)  /* clock daemon */
+#define LOG_AUTHPRIV  (10<<3) /* security/authorization messages */
+#define LOG_FTP       (11<<3) /* FTP daemon */
+
 #define LOG_LOCAL0      (16<<3) /* reserved for local use */
 #define LOG_LOCAL1      (17<<3) /* reserved for local use */
 #define LOG_LOCAL2      (18<<3) /* reserved for local use */
index c680027..da7a307 100644 (file)
@@ -110,6 +110,41 @@ static bool log_async OVS_GUARDED_BY(log_file_mutex);
 /* Syslog export configuration. */
 static int syslog_fd OVS_GUARDED_BY(pattern_rwlock) = -1;
 
+/* Log facility configuration. */
+static atomic_int log_facility = ATOMIC_VAR_INIT(0);
+
+/* Facility name and its value. */
+struct vlog_facility {
+    char *name;           /* Name. */
+    unsigned int value;   /* Facility associated with 'name'. */
+};
+static struct vlog_facility vlog_facilities[] = {
+    {"kern", LOG_KERN},
+    {"user", LOG_USER},
+    {"mail", LOG_MAIL},
+    {"daemon", LOG_DAEMON},
+    {"auth", LOG_AUTH},
+    {"syslog", LOG_SYSLOG},
+    {"lpr", LOG_LPR},
+    {"news", LOG_NEWS},
+    {"uucp", LOG_UUCP},
+    {"clock", LOG_CRON},
+    {"ftp", LOG_FTP},
+    {"ntp", 12<<3},
+    {"audit", 13<<3},
+    {"alert", 14<<3},
+    {"clock2", 15<<3},
+    {"local0", LOG_LOCAL0},
+    {"local1", LOG_LOCAL1},
+    {"local2", LOG_LOCAL2},
+    {"local3", LOG_LOCAL3},
+    {"local4", LOG_LOCAL4},
+    {"local5", LOG_LOCAL5},
+    {"local6", LOG_LOCAL6},
+    {"local7", LOG_LOCAL7}
+};
+static bool vlog_facility_exists(const char* facility, int *value);
+
 static void format_log_message(const struct vlog_module *, enum vlog_level,
                                const char *pattern,
                                const char *message, va_list, struct ds *)
@@ -419,6 +454,14 @@ vlog_set_levels_from_string(const char *s_)
             goto exit;
         }
         vlog_set_pattern(destination, save_ptr);
+    } else if (word && !strcasecmp(word, "FACILITY")) {
+        int value;
+
+        if (!vlog_facility_exists(save_ptr, &value)) {
+            msg = xstrdup("invalid facility");
+            goto exit;
+        }
+        atomic_store_explicit(&log_facility, value, memory_order_relaxed);
     } else {
         struct vlog_module *module = NULL;
         enum vlog_level level = VLL_N_LEVELS;
@@ -507,6 +550,22 @@ vlog_set_syslog_target(const char *target)
     ovs_rwlock_unlock(&pattern_rwlock);
 }
 
+/* Returns 'false' if 'facility' is not a valid string. If 'facility'
+ * is a valid string, sets 'value' with the integer value of 'facility'
+ * and returns 'true'. */
+static bool
+vlog_facility_exists(const char* facility, int *value)
+{
+    size_t i;
+    for (i = 0; i < ARRAY_SIZE(vlog_facilities); i++) {
+        if (!strcasecmp(vlog_facilities[i].name, facility)) {
+            *value = vlog_facilities[i].value;
+            return true;
+        }
+    }
+    return false;
+}
+
 static void
 vlog_unixctl_set(struct unixctl_conn *conn, int argc, const char *argv[],
                  void *aux OVS_UNUSED)
@@ -614,6 +673,7 @@ vlog_init(void)
     if (ovsthread_once_start(&once)) {
         static char *program_name_copy;
         long long int now;
+        int facility;
 
         /* Do initialization work that needs to be done before any logging
          * occurs.  We want to keep this really minimal because any attempt to
@@ -625,7 +685,9 @@ vlog_init(void)
          * a pointer to the private copy to suppress memory leak warnings in
          * case openlog() does make its own copy.) */
         program_name_copy = program_name ? xstrdup(program_name) : NULL;
-        openlog(program_name_copy, LOG_NDELAY, LOG_DAEMON);
+        atomic_read_explicit(&log_facility, &facility, memory_order_relaxed);
+        openlog(program_name_copy, LOG_NDELAY,
+                facility ? facility : LOG_DAEMON);
         ovsthread_once_done(&once);
 
         /* Now do anything that we want to happen only once but doesn't have to
@@ -739,6 +801,7 @@ format_log_message(const struct vlog_module *module, enum vlog_level level,
     char tmp[128];
     va_list args;
     const char *p;
+    int facility;
 
     ds_clear(s);
     for (p = pattern; *p != '\0'; ) {
@@ -773,7 +836,10 @@ format_log_message(const struct vlog_module *module, enum vlog_level level,
             ds_put_cstr(s, program_name);
             break;
         case 'B':
-            ds_put_format(s, "%d", LOG_LOCAL0 + syslog_levels[level]);
+            atomic_read_explicit(&log_facility, &facility,
+                                 memory_order_relaxed);
+            facility = facility ? facility : LOG_LOCAL0;
+            ds_put_format(s, "%d", facility + syslog_levels[level]);
             break;
         case 'c':
             p = fetch_braces(p, "", tmp, sizeof tmp);
@@ -897,12 +963,15 @@ vlog_valist(const struct vlog_module *module, enum vlog_level level,
             int syslog_level = syslog_levels[level];
             char *save_ptr = NULL;
             char *line;
+            int facility;
 
             format_log_message(module, level, destinations[VLF_SYSLOG].pattern,
                                message, args, &s);
             for (line = strtok_r(s.string, "\n", &save_ptr); line;
                  line = strtok_r(NULL, "\n", &save_ptr)) {
-                syslog(syslog_level, "%s", line);
+                atomic_read_explicit(&log_facility, &facility,
+                                     memory_order_relaxed);
+                syslog(syslog_level|facility, "%s", line);
             }
 
             if (syslog_fd >= 0) {
index ddf14d8..5ee34f8 100644 (file)
@@ -54,6 +54,17 @@ Sets the maximum logging verbosity level, equivalent to
 Sets the log pattern for \fIdestination\fR to \fIpattern\fR.  Refer to
 \fBovs\-appctl\fR(8) for a description of the valid syntax for \fIpattern\fR.
 .
+.IP "\fB\-vFACILITY:\fIfacility\fR"
+.IQ "\fB\-\-verbose=FACILITY:\fIfacility\fR"
+Sets the RFC5424 facility of the log message. \fIfacility\fR can be one of
+\fBkern\fR, \fBuser\fR, \fBmail\fR, \fBdaemon\fR, \fBauth\fR, \fBsyslog\fR,
+\fBlpr\fR, \fBnews\fR, \fBuucp\fR, \fBclock\fR, \fBftp\fR, \fBntp\fR,
+\fBaudit\fR, \fBalert\fR, \fBclock2\fR, \fBlocal0\fR, \fBlocal1\fR,
+\fBlocal2\fR, \fBlocal3\fR, \fBlocal4\fR, \fBlocal5\fR, \fBlocal6\fR or
+\fBlocal7\fR. If this option is not specified, \fBdaemon\fR is used as
+the default for the local system syslog and \fBlocal0\fR is used while sending
+a message to the target provided via the \fB\-\-syslog\-target\fR option.
+.
 .TP
 \fB\-\-log\-file\fR[\fB=\fIfile\fR]
 Enables logging to a file.  If \fIfile\fR is specified, then it is
index 105d126..5690924 100644 (file)
@@ -40,6 +40,11 @@ LEVELS = {
     "emer": logging.CRITICAL,
     "off": logging.CRITICAL
 }
+FACILITIES = ['auth', 'authpriv', 'cron', 'daemon', 'ftp', 'kern', 'lpr',
+              'mail', 'news', 'syslog', 'user', 'uucp', 'local0', 'local1',
+              'local2', 'local3', 'local4', 'local5', 'local6', 'local7']
+syslog_facility = "daemon"
+syslog_handler = ''
 
 
 def get_level(level_str):
@@ -224,9 +229,7 @@ class Vlog:
                 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))
+                    Vlog.add_syslog_handler()
                 elif f == "file" and Vlog.__log_file:
                     Vlog.__file_handler = logging.FileHandler(Vlog.__log_file)
                     logger.addHandler(Vlog.__file_handler)
@@ -280,6 +283,26 @@ class Vlog:
         destination = destination.lower()
         Vlog.__log_patterns[destination] = pattern
 
+    @staticmethod
+    def add_syslog_handler(facility=None):
+        global syslog_facility, syslog_handler
+
+        # If handler is already added and there is no change in 'facility',
+        # there is nothing to do.
+        if (not facility or facility == syslog_facility) and syslog_handler:
+            return
+
+        if facility:
+            syslog_facility = facility
+
+        logger = logging.getLogger('syslog')
+        if syslog_handler:
+            logger.removeHandler(syslog_handler)
+        syslog_handler = logging.handlers.SysLogHandler(address="/dev/log",
+                                                    facility=syslog_facility)
+        logger.addHandler(syslog_handler)
+        return
+
     @staticmethod
     def set_levels_from_string(s):
         module = None
@@ -298,6 +321,12 @@ class Vlog:
                     return "Destination %s does not exist" % words[1]
             except IndexError:
                 return "Please supply a valid pattern and destination"
+        elif words[0] == "FACILITY":
+            if words[1] in FACILITIES:
+                Vlog.add_syslog_handler(words[1])
+                return
+            else:
+                return "Facility %s is invalid" % words[1]
 
         for word in [w.lower() for w in words]:
             if word == "any":
index 4a143cd..fdd6732 100644 (file)
@@ -234,3 +234,66 @@ AT_CHECK([APPCTL -t test-unixctl.py vlog/set pattern:file:'I<3OVS|%m'])
 AT_CHECK([APPCTL -t test-unixctl.py log patterntest])
 AT_CHECK([grep -q 'I<3OVS' log])
 AT_CLEANUP
+
+AT_SETUP([vlog - RFC5424 facility])
+OVS_RUNDIR=`pwd`; export OVS_RUNDIR
+OVS_LOGDIR=`pwd`; export OVS_LOGDIR
+OVS_DBDIR=`pwd`; export OVS_DBDIR
+OVS_SYSCONFDIR=`pwd`; export OVS_SYSCONFDIR
+ON_EXIT([kill `cat ovsdb-server.pid`])
+
+dnl Create database.
+touch .conf.db.~lock~
+AT_CHECK([ovsdb-tool create conf.db $abs_top_srcdir/vswitchd/vswitch.ovsschema])
+
+AT_CHECK([ovsdb-server --detach --no-chdir --pidfile \
+          --remote=punix:$OVS_RUNDIR/db.sock -vPATTERN:file:"<%B>1 %A %m" \
+          --log-file], [0], [], [stderr])
+AT_CHECK([ovs-appctl -t ovsdb-server exit])
+
+# A default facility of LOG_LOCAL0 while writing to file.
+AT_CHECK([cat ovsdb-server.log | head -1 | awk '{print $1}'], [0], [<133>1
+])
+rm ovsdb-server.log
+
+AT_CHECK([ovsdb-server --detach --no-chdir --pidfile \
+          --remote=punix:$OVS_RUNDIR/db.sock -vPATTERN:file:"<%B>1 %A %m" \
+          -vFACILITY:daemon --log-file], [0], [], [stderr])
+
+AT_CHECK([cat ovsdb-server.log | head -1 | awk '{print $1}'], [0], [<29>1
+])
+
+AT_CHECK([ovs-appctl -t ovsdb-server vlog/set FACILITY:invalid], [2], [],
+[invalid facility
+ovs-appctl: ovsdb-server: server returned an error
+])
+
+AT_CHECK([ovs-appctl -t ovsdb-server vlog/set FACILITY:local7])
+AT_CHECK([ovs-appctl -t ovsdb-server vlog/set ANY:file:DBG])
+AT_CHECK([ovs-appctl -t ovsdb-server exit])
+
+AT_CHECK([cat ovsdb-server.log | tail -1 | awk '{print $1}'], [0], [<191>1
+])
+AT_CLEANUP
+
+AT_SETUP([vlog - RFC5424 facility - Python])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+OVS_RUNDIR=`pwd`; export OVS_RUNDIR
+OVS_LOGDIR=`pwd`; export OVS_LOGDIR
+OVS_DBDIR=`pwd`; export OVS_DBDIR
+OVS_SYSCONFDIR=`pwd`; export OVS_SYSCONFDIR
+ON_EXIT([kill `cat test-unixctl.py.pid`])
+
+AT_CHECK([$PYTHON $srcdir/test-unixctl.py --log-file=`pwd`/log --pidfile \
+-vFACILITY:invalid --detach], [1], [], [test-unixctl.py: processing "FACILITY:invalid": Facility invalid is invalid
+])
+
+AT_CHECK([$PYTHON $srcdir/test-unixctl.py --log-file=`pwd`/log --pidfile \
+-vFACILITY:daemon --detach])
+
+AT_CHECK([ovs-appctl -t test-unixctl.py vlog/set FACILITY:invalid], [0],
+[Facility invalid is invalid
+])
+
+AT_CHECK([ovs-appctl -t test-unixctl.py vlog/set FACILITY:local0])
+AT_CLEANUP
index 238c74b..9e33f61 100644 (file)
@@ -254,6 +254,14 @@ The default pattern for console and file output is \fB%D{%Y-%m-%dT
 Daemons written in Python (e.g. \fBovs\-xapi\-sync\fR,
 \fBovs\-monitor\-ipsec) do not allow control over the log pattern.
 .
+.IP "\fBvlog/set\fR FACILITY:\fIfacility\fR"
+Sets the RFC5424 facility of the log message. \fIfacility\fR can be one of
+\fBkern\fR, \fBuser\fR, \fBmail\fR, \fBdaemon\fR, \fBauth\fR, \fBsyslog\fR,
+\fBlpr\fR, \fBnews\fR, \fBuucp\fR, \fBclock\fR, \fBftp\fR, \fBntp\fR,
+\fBaudit\fR, \fBalert\fR, \fBclock2\fR, \fBlocal0\fR, \fBlocal1\fR,
+\fBlocal2\fR, \fBlocal3\fR, \fBlocal4\fR, \fBlocal5\fR, \fBlocal6\fR or
+\fBlocal7\fR.
+.
 .IP "\fBvlog/reopen\fR"
 Causes the daemon to close and reopen its log file.  (This
 is useful after rotating log files, to cause a new log file to be