[libvirt] [PATCH 7/9] Add systemd journal support

Eric Blake eblake at redhat.com
Thu Sep 27 20:51:35 UTC 2012


On 09/27/2012 10:44 AM, Daniel P. Berrange wrote:
> From: "Daniel P. Berrange" <berrange at 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 at redhat.com>
> ---
>  cfg.mk             |   2 +-
>  src/util/logging.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  src/util/logging.h |   1 +
>  3 files changed, 179 insertions(+), 1 deletion(-)

I'll need a v2 on this one.

Fails to compile:

util/logging.c: In function 'virLogOutputToJournald':
util/logging.c:1124:84: error: ordered comparison of pointer with
integer zero [-Werror=extra]
util/logging.c:1124:18: error: ignoring return value of 'virStrcpy',
declared with attribute warn_unused_result [-Werror=unused-result]
cc1: all warnings being treated as errors

> 
> 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)$$)

That's a bit of a heavy hammer, just to avoid a recursively-logged
close().  I'd rather omit this and use VIR_LOG_CLOSE().

> +
> +
> +# 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);   \

Technically, snprintf is not async-signal safe, which kind of goes
against the idea of using it in our logging functions.  I'd much rather
see us do a hand-rolled conversion of int to %d or %zu (which appear to
be the only formats you ever pass here).

> +        _i->iov_base = buf;                     \
> +        _i->iov_len = strlen(buf);              \

[Pedantic - even strlen() is not async-signal safe, but that appears to
be a bug in POSIX and I have no qualms about using it here; on the other
hand, since snprintf() can malloc() (gasp!) POSIX really is correct in
listing it as non-safe, and we really shouldn't be using it in this context]

> +    } 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,);

This looks odd, until you remember it is a macro, and doing the correct
thing.

> +    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

s/upto/up to/

> +     * 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')) {

[Pedantic - strchr() is another one of those functions surprisingly
omitted from the async-safe list, but I have no qualms in using it]

> +        uint64_t nstr;
> +        /* If 'str' containes a newline, then we must

s/containes/contains/

> +         * 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));

htole64() is a glibc extension which does not exist on mingw, and has
not (yet) been ported to gnulib.  Then again, this code is inside #ifdef
HAVE_SYSLOG_H, so you can probably get away with it (although I'm not
sure whether all platforms that provide <syslog.h> happen to also
provide htole64(), I at least know that it will exclude mingw).

> +        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);

You know, my earlier comments about linenr always fitting in 'int' mean
you could use %u instead of %zu here, making my goal of replacing
snprintf with a hand-rolled async-safe converter a bit easier.

> +    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)

Is mkostemp async-signal safe?  But if it isn't, I don't know how else
to generate a safe file name.  Maybe we create ourselves a safe
temporary directory at process start where we don't care about the async
safety issues, and then in this function, we track a static counter that
we increment each time we create a new file within that directory.

> +        return;
> +
> +    if (unlink(path) < 0)
> +        goto cleanup;
> +
> +    if (writev(buffd, iov, niov) < 0)

Again, mingw and gnulib lacks writev(), but this function isn't compiled
on mingw, so we're safe for now.

> +        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);

VIR_LOG_CLOSE() works just fine here (but not VIR_FORCE_CLOSE, as that
is an accident waiting to trigger recursive logging)

> +}
> +
> +
> +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) {

Why not use SOCK_DGRAM | SOCK_CLOEXEC in the socket() call?

> +        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

Do we really want to silently parse and ignore 'journald' on systems
that lack 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 {
> 

Here's a minimum of what I propose squashing in:

diff --git i/cfg.mk w/cfg.mk
index 4482d70..bbfd4a2 100644
--- i/cfg.mk
+++ w/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|src/util/logging\.c)$$)
+  (\.p[yl]$$|^docs/|^(src/util/virfile\.c|src/libvirt\.c)$$)

 exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \
   (^tests/(qemuhelp|nodeinfo)data/|\.(gif|ico|png|diff)$$)
diff --git i/src/util/logging.c w/src/util/logging.c
index 367c8e1..32ade71 100644
--- i/src/util/logging.c
+++ w/src/util/logging.c
@@ -1121,7 +1121,7 @@ virLogOutputToJournald(virLogSource source,

     memset(&sa, 0, sizeof(sa));
     sa.sun_family = AF_UNIX;
-    if (virStrcpy(sa.sun_path, "/run/systemd/journal/socket",
sizeof(sa.sun_path)) < 0)
+    if (virStrcpyStatic(sa.sun_path, "/run/systemd/journal/socket") ==
NULL)
         return;

     memset(&mh, 0, sizeof(mh));
@@ -1167,13 +1167,13 @@ virLogOutputToJournald(virLogSource source,
     sendmsg(journalfd, &mh, MSG_NOSIGNAL);

 cleanup:
-    close(buffd);
+    VIR_LOG_CLOSE(buffd);
 }


 static void virLogCloseJournald(void *data ATTRIBUTE_UNUSED)
 {
-    close(journalfd);
+    VIR_LOG_CLOSE(journalfd);
 }


@@ -1182,7 +1182,7 @@ static int virLogAddOutputToJournald(int priority)
     if ((journalfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
         return -1;
     if (virSetInherit(journalfd, false) < 0) {
-        close(journalfd);
+        VIR_LOG_CLOSE(journalfd);
         return -1;
     }
     if (virLogDefineOutput(virLogOutputToJournald, virLogCloseJournald,
NULL,


-- 
Eric Blake   eblake at redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 617 bytes
Desc: OpenPGP digital signature
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20120927/92bafdf7/attachment-0001.sig>


More information about the libvir-list mailing list