[PATCH v2] Add systemd journal support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: "Daniel P. Berrange" <berrange@xxxxxxxxxx>

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.

Changed in v2:

 - Add virFormatIntDecimal instead of using snprintf
 - Add comment about mkostemp
 - Fix declaration of linestr var to use size of linenr
 - Wrap in #ifdef __linux__

Signed-off-by: Daniel P. Berrange <berrange@xxxxxxxxxx>
---
 src/libvirt_private.syms |   1 +
 src/util/logging.c       | 184 +++++++++++++++++++++++++++++++++++++++++++++++
 src/util/logging.h       |   1 +
 src/util/util.c          |  30 ++++++++
 src/util/util.h          |   5 +-
 5 files changed, 220 insertions(+), 1 deletion(-)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index dab607a..eebc52a 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1233,6 +1233,7 @@ virFileUnlock;
 virFileWaitForDevices;
 virFileWriteStr;
 virFindFileInPath;
+virFormatIntDecimal;
 virGetGroupID;
 virGetGroupName;
 virGetHostname;
diff --git a/src/util/logging.c b/src/util/logging.c
index cdd94fd..9e44880 100644
--- a/src/util/logging.c
+++ b/src/util/logging.c
@@ -35,6 +35,10 @@
 #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,12 @@ virLogParseOutputs(const char *outputs)
                 count++;
             VIR_FREE(name);
             VIR_FREE(abspath);
+        } else if (STREQLEN(cur, "journald", 8)) {
+            cur += 8;
+#if HAVE_SYSLOG_H
+            if (virLogAddOutputToJournald(prio) == 0)
+                count++;
+#endif /* HAVE_SYSLOG_H */
         } else {
             goto cleanup;
         }
diff --git a/src/util/logging.h b/src/util/logging.h
index a3ea821..4fe0c8e 100644
--- a/src/util/logging.h
+++ b/src/util/logging.h
@@ -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 {
diff --git a/src/util/util.c b/src/util/util.c
index 28f9ae3..43fdaf1 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -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)
diff --git a/src/util/util.h b/src/util/util.h
index 5ab36ed..4316ab1 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -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);
-- 
1.7.11.4

--
libvir-list mailing list
libvir-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/libvir-list


[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]