From: "Daniel P. Berrange" <berrange(a)redhat.com>
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(a)redhat.com>
---
cfg.mk | 2 +-
src/util/logging.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++
src/util/logging.h | 1 +
3 files changed, 179 insertions(+), 1 deletion(-)
diff --git a/cfg.mk b/cfg.mk
index bbfd4a2..4482d70 100644
--- a/cfg.mk
+++ b/cfg.mk
@@ -771,7 +771,7 @@ exclude_file_name_regexp--sc_prohibit_asprintf = \
^(bootstrap.conf$$|src/util/util\.c$$|examples/domain-events/events-c/event-test\.c$$)
exclude_file_name_regexp--sc_prohibit_close = \
- (\.p[yl]$$|^docs/|^(src/util/virfile\.c|src/libvirt\.c)$$)
+ (\.p[yl]$$|^docs/|^(src/util/virfile\.c|src/libvirt\.c|src/util/logging\.c)$$)
exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \
(^tests/(qemuhelp|nodeinfo)data/|\.(gif|ico|png|diff)$$)
diff --git a/src/util/logging.c b/src/util/logging.c
index fef4577..367c8e1 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,170 @@ virLogAddOutputToSyslog(virLogPriority priority,
}
return 0;
}
+
+
+# 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, fmt, val) \
+ do { \
+ struct iovec *_i = &(iov); \
+ snprintf(buf, sizeof(buf), fmt, val); \
+ _i->iov_base = buf; \
+ _i->iov_len = strlen(buf); \
+ } while (0)
+
+static int journalfd = -1;
+
+static void
+virLogOutputToJournald(virLogSource source,
+ virLogPriority priority,
+ const char *filename,
+ size_t 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(priority)];
+
+ /* 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, "%d", 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, "%zu", 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)) < 0)
+ 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
+ */
+
+ 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:
+ close(buffd);
+}
+
+
+static void virLogCloseJournald(void *data ATTRIBUTE_UNUSED)
+{
+ close(journalfd);
+}
+
+
+static int virLogAddOutputToJournald(int priority)
+{
+ if ((journalfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
+ return -1;
+ if (virSetInherit(journalfd, false) < 0) {
+ close(journalfd);
+ return -1;
+ }
+ if (virLogDefineOutput(virLogOutputToJournald, virLogCloseJournald, NULL,
+ priority, VIR_LOG_TO_JOURNALD, NULL, 0) < 0) {
+ return -1;
+ }
+ return 0;
+}
#endif /* HAVE_SYSLOG_H */
#define IS_SPACE(cur) \
@@ -1114,6 +1285,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 e5918db..d4aa62b 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 {
--
1.7.11.4