]> xenbits.xensource.com Git - people/liuw/libxenctrl-split/libvirt.git/commitdiff
Add systemd journal support
authorDaniel P. Berrange <berrange@redhat.com>
Tue, 25 Sep 2012 17:31:01 +0000 (18:31 +0100)
committerDaniel P. Berrange <berrange@redhat.com>
Fri, 28 Sep 2012 15:02:56 +0000 (16:02 +0100)
Add support for logging to the systemd journal, using its
simple client library. The benefit over syslog is that it
accepts structured log data, so the journald can store
individual items like code file/line/func separately from
the string message. Tools which require structured log
data can then query the journal to extract exactly what
they desire without resorting to string parsing

While systemd provides a simple client library for logging,
it is more convenient for libvirt to directly write its
own client code. This lets us build up the iovec's on
the stack, avoiding the need to alloc memory when writing
log messages.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
src/libvirt_private.syms
src/util/logging.c
src/util/logging.h
src/util/util.c
src/util/util.h

index dab607af14970ab2429754b1e37f1361ad35a468..eebc52a829154e4bc5275b39636d771fa5890208 100644 (file)
@@ -1233,6 +1233,7 @@ virFileUnlock;
 virFileWaitForDevices;
 virFileWriteStr;
 virFindFileInPath;
+virFormatIntDecimal;
 virGetGroupID;
 virGetGroupName;
 virGetHostname;
index cdd94fd38b178cf38866c724788bc3ad0efa5dd9..0ce18f1c18fa8f254f77c5849952b390c3e7d290 100644 (file)
 #if HAVE_SYSLOG_H
 # include <syslog.h>
 #endif
+#include <sys/socket.h>
+#if HAVE_SYS_UN_H
+# include <sys/un.h>
+#endif
 
 #include "virterror_internal.h"
 #include "logging.h"
@@ -44,6 +48,7 @@
 #include "threads.h"
 #include "virfile.h"
 #include "virtime.h"
+#include "intprops.h"
 
 #define VIR_FROM_THIS VIR_FROM_NONE
 
@@ -146,6 +151,8 @@ virLogOutputString(virLogDestination ldest)
         return "syslog";
     case VIR_LOG_TO_FILE:
         return "file";
+    case VIR_LOG_TO_JOURNALD:
+        return "journald";
     }
     return "unknown";
 }
@@ -1020,6 +1027,177 @@ virLogAddOutputToSyslog(virLogPriority priority,
     }
     return 0;
 }
+
+
+# ifdef __linux__
+#  define IOVEC_SET_STRING(iov, str)         \
+    do {                                     \
+        struct iovec *_i = &(iov);           \
+        _i->iov_base = (char*)str;           \
+        _i->iov_len = strlen(str);           \
+    } while (0)
+
+#  define IOVEC_SET_INT(iov, buf, val)                                  \
+    do {                                                                \
+        struct iovec *_i = &(iov);                                      \
+        _i->iov_base = virFormatIntDecimal(buf, sizeof(buf), val);      \
+        _i->iov_len = strlen(buf);                                      \
+    } while (0)
+
+static int journalfd = -1;
+
+static void
+virLogOutputToJournald(virLogSource source,
+                       virLogPriority priority,
+                       const char *filename,
+                       int linenr,
+                       const char *funcname,
+                       const char *timestamp ATTRIBUTE_UNUSED,
+                       unsigned int flags,
+                       const char *rawstr,
+                       const char *str ATTRIBUTE_UNUSED,
+                       void *data ATTRIBUTE_UNUSED)
+{
+    virCheckFlags(VIR_LOG_STACK_TRACE,);
+    int buffd = -1;
+    size_t niov = 0;
+    struct msghdr mh;
+    struct sockaddr_un sa;
+    union {
+        struct cmsghdr cmsghdr;
+        uint8_t buf[CMSG_SPACE(sizeof(int))];
+    } control;
+    struct cmsghdr *cmsg;
+    /* We use /dev/shm instead of /tmp here, since we want this to
+     * be a tmpfs, and one that is available from early boot on
+     * and where unprivileged users can create files. */
+    char path[] = "/dev/shm/journal.XXXXXX";
+    char priostr[INT_BUFSIZE_BOUND(priority)];
+    char linestr[INT_BUFSIZE_BOUND(linenr)];
+
+    /* First message takes upto 4 iovecs, and each
+     * other field needs 3, assuming they don't have
+     * newlines in them
+     */
+#  define IOV_SIZE (4 + (5 * 3))
+    struct iovec iov[IOV_SIZE];
+
+    if (strchr(rawstr, '\n')) {
+        uint64_t nstr;
+        /* If 'str' containes a newline, then we must
+         * encode the string length, since we can't
+         * rely on the newline for the field separator
+         */
+        IOVEC_SET_STRING(iov[niov++], "MESSAGE\n");
+        nstr = htole64(strlen(rawstr));
+        iov[niov].iov_base = (char*)&nstr;
+        iov[niov].iov_len = sizeof(nstr);
+        niov++;
+    } else {
+        IOVEC_SET_STRING(iov[niov++], "MESSAGE=");
+    }
+    IOVEC_SET_STRING(iov[niov++], rawstr);
+    IOVEC_SET_STRING(iov[niov++], "\n");
+
+    IOVEC_SET_STRING(iov[niov++], "PRIORITY=");
+    IOVEC_SET_INT(iov[niov++], priostr, priority);
+    IOVEC_SET_STRING(iov[niov++], "\n");
+
+    IOVEC_SET_STRING(iov[niov++], "LIBVIRT_SOURCE=");
+    IOVEC_SET_STRING(iov[niov++], virLogSourceTypeToString(source));
+    IOVEC_SET_STRING(iov[niov++], "\n");
+
+    IOVEC_SET_STRING(iov[niov++], "CODE_FILE=");
+    IOVEC_SET_STRING(iov[niov++], filename);
+    IOVEC_SET_STRING(iov[niov++], "\n");
+
+    IOVEC_SET_STRING(iov[niov++], "CODE_LINE=");
+    IOVEC_SET_INT(iov[niov++], linestr, linenr);
+    IOVEC_SET_STRING(iov[niov++], "\n");
+
+    IOVEC_SET_STRING(iov[niov++], "CODE_FUNC=");
+    IOVEC_SET_STRING(iov[niov++], funcname);
+    IOVEC_SET_STRING(iov[niov++], "\n");
+
+    memset(&sa, 0, sizeof(sa));
+    sa.sun_family = AF_UNIX;
+    if (!virStrcpy(sa.sun_path, "/run/systemd/journal/socket", sizeof(sa.sun_path)))
+        return;
+
+    memset(&mh, 0, sizeof(mh));
+    mh.msg_name = &sa;
+    mh.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(sa.sun_path);
+    mh.msg_iov = iov;
+    mh.msg_iovlen = niov;
+
+    if (sendmsg(journalfd, &mh, MSG_NOSIGNAL) >= 0)
+        return;
+
+    if (errno != EMSGSIZE && errno != ENOBUFS)
+        return;
+
+    /* Message was too large, so dump to temporary file
+     * and pass an FD to the journal
+     */
+
+    /* NB: mkostemp is not declared async signal safe by
+     * POSIX, but this is Linux only code and the GLibc
+     * impl is safe enough, only using open() and inline
+     * asm to read a timestamp (falling back to gettimeofday
+     * on some arches
+     */
+    if ((buffd = mkostemp(path, O_CLOEXEC|O_RDWR)) < 0)
+        return;
+
+    if (unlink(path) < 0)
+        goto cleanup;
+
+    if (writev(buffd, iov, niov) < 0)
+        goto cleanup;
+
+    mh.msg_iov = NULL;
+    mh.msg_iovlen = 0;
+
+    memset(&control, 0, sizeof(control));
+    mh.msg_control = &control;
+    mh.msg_controllen = sizeof(control);
+
+    cmsg = CMSG_FIRSTHDR(&mh);
+    cmsg->cmsg_level = SOL_SOCKET;
+    cmsg->cmsg_type = SCM_RIGHTS;
+    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+    memcpy(CMSG_DATA(cmsg), &buffd, sizeof(int));
+
+    mh.msg_controllen = cmsg->cmsg_len;
+
+    sendmsg(journalfd, &mh, MSG_NOSIGNAL);
+
+cleanup:
+    VIR_LOG_CLOSE(buffd);
+}
+
+
+static void virLogCloseJournald(void *data ATTRIBUTE_UNUSED)
+{
+    VIR_LOG_CLOSE(journalfd);
+}
+
+
+static int virLogAddOutputToJournald(int priority)
+{
+    if ((journalfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
+        return -1;
+    if (virSetInherit(journalfd, false) < 0) {
+        VIR_LOG_CLOSE(journalfd);
+        return -1;
+    }
+    if (virLogDefineOutput(virLogOutputToJournald, virLogCloseJournald, NULL,
+                           priority, VIR_LOG_TO_JOURNALD, NULL, 0) < 0) {
+        return -1;
+    }
+    return 0;
+}
+# endif /* __linux__ */
 #endif /* HAVE_SYSLOG_H */
 
 #define IS_SPACE(cur)                                                   \
@@ -1114,6 +1292,14 @@ virLogParseOutputs(const char *outputs)
                 count++;
             VIR_FREE(name);
             VIR_FREE(abspath);
+        } else if (STREQLEN(cur, "journald", 8)) {
+            cur += 8;
+#if HAVE_SYSLOG_H
+# ifdef __linux__
+            if (virLogAddOutputToJournald(prio) == 0)
+                count++;
+# endif /* __linux__ */
+#endif /* HAVE_SYSLOG_H */
         } else {
             goto cleanup;
         }
index a3ea82172f872c711728bec7d34cffde5420c090..4fe0c8eb521b6fafdf01798fef2931b9c9c327d0 100644 (file)
@@ -80,6 +80,7 @@ typedef enum {
     VIR_LOG_TO_STDERR = 1,
     VIR_LOG_TO_SYSLOG,
     VIR_LOG_TO_FILE,
+    VIR_LOG_TO_JOURNALD,
 } virLogDestination;
 
 typedef enum {
index 28f9ae3365c90145d18d6e93bf4022421f2fc728..43fdaf10baf9ff1a202fa45f0ee0bca693acfc61 100644 (file)
@@ -2124,6 +2124,36 @@ virDoubleToStr(char **strp, double number)
     return ret;
 }
 
+
+/**
+ * Format @val as a base-10 decimal number, in the
+ * buffer @buf of size @buflen. To allocate a suitable
+ * sized buffer, the INT_BUFLEN(int) macro should be
+ * used
+ *
+ * Returns pointer to start of the number in @buf
+ */
+char *
+virFormatIntDecimal(char *buf, size_t buflen, int val)
+{
+    char *p = buf + buflen - 1;
+    *p = '\0';
+    if (val >= 0) {
+        do {
+            *--p = '0' + (val % 10);
+            val /= 10;
+        } while (val != 0);
+    } else {
+        do {
+            *--p = '0' - (val % 10);
+            val /= 10;
+        } while (val != 0);
+        *--p = '-';
+    }
+    return p;
+}
+
+
 const char *virEnumToString(const char *const*types,
                             unsigned int ntypes,
                             int type)
index 5ab36ed35c00cdbf2a31708a9bc2d194b3d5d748..4316ab17f1701637c61fa2c56e649bee23887025 100644 (file)
@@ -210,7 +210,10 @@ char *virStrcpy(char *dest, const char *src, size_t destbytes)
 # define virStrcpyStatic(dest, src) virStrcpy((dest), (src), sizeof(dest))
 
 int virDoubleToStr(char **strp, double number)
- ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK;
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK;
+
+char *virFormatIntDecimal(char *buf, size_t buflen, int val)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK;
 
 int virDiskNameToIndex(const char* str);
 char *virIndexToDiskName(int idx, const char *prefix);