[v32,10/12] log: support systemd journal

Message ID 20241108085710.2943741-11-david.marchand@redhat.com (mailing list archive)
State Accepted, archived
Delegated to: David Marchand
Headers
Series Log library enhancements |

Checks

Context Check Description
ci/checkpatch warning coding style issues

Commit Message

David Marchand Nov. 8, 2024, 8:57 a.m. UTC
From: Stephen Hemminger <stephen@networkplumber.org>

If DPDK application is being run as a systemd service, then
it can use the journal protocol which allows putting more information
in the log such as priority and other information.

The use of journal protocol is automatically detected and handled.
Rather than having a dependency on libsystemd, just use the protocol
directly as defined in:
	https://systemd.io/JOURNAL_NATIVE_PROTOCOL/

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Morten Brørup <mb@smartsharesystems.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
---
 doc/guides/rel_notes/release_24_11.rst |   4 +
 lib/log/log.c                          |  31 +++--
 lib/log/log_internal.h                 |   3 +
 lib/log/log_journal.c                  | 153 +++++++++++++++++++++++++
 lib/log/log_private.h                  |  16 +++
 lib/log/meson.build                    |   4 +
 lib/log/version.map                    |   1 +
 7 files changed, 200 insertions(+), 12 deletions(-)
 create mode 100644 lib/log/log_journal.c
  

Patch

diff --git a/doc/guides/rel_notes/release_24_11.rst b/doc/guides/rel_notes/release_24_11.rst
index d1226c78d1..341606f541 100644
--- a/doc/guides/rel_notes/release_24_11.rst
+++ b/doc/guides/rel_notes/release_24_11.rst
@@ -118,6 +118,10 @@  New Features
 
   * The log subsystem is initialized earlier in startup so all messages go through the library.
 
+  * If the application is a systemd service and the log output is being sent to standard error
+    then DPDK will switch to journal native protocol.
+    This allows the more data such as severity to be sent.
+
   * The syslog option has changed.
     By default, messages are no longer sent to syslog unless the ``--syslog`` option is specified.
     Syslog is also supported on FreeBSD (but not on Windows).
diff --git a/lib/log/log.c b/lib/log/log.c
index 854d77887b..0974adc81f 100644
--- a/lib/log/log.c
+++ b/lib/log/log.c
@@ -505,18 +505,25 @@  rte_log(uint32_t level, uint32_t logtype, const char *format, ...)
 void
 eal_log_init(const char *id)
 {
-	FILE *logf = NULL;
-
-	if (log_syslog_enabled())
-		logf = log_syslog_open(id);
-
-	if (logf)
-		rte_openlog_stream(logf);
-
-	if (log_timestamp_enabled())
-		rte_logs.print_func = log_print_with_timestamp;
-	else
-		rte_logs.print_func = vfprintf;
+	/* If user has already set a log stream, then use it. */
+	if (rte_logs.file == NULL) {
+		FILE *logf = NULL;
+
+		/* if stderr is associated with systemd environment */
+		if (log_journal_enabled())
+			logf = log_journal_open(id);
+		/* If --syslog option was passed */
+		else if (log_syslog_enabled())
+			logf = log_syslog_open(id);
+
+		/* if either syslog or journal is used, then no special handling */
+		if (logf)
+			rte_openlog_stream(logf);
+		else if (log_timestamp_enabled())
+			rte_logs.print_func = log_print_with_timestamp;
+		else
+			rte_logs.print_func = vfprintf;
+	}
 
 #if RTE_LOG_DP_LEVEL >= RTE_LOG_DEBUG
 	RTE_LOG(NOTICE, EAL,
diff --git a/lib/log/log_internal.h b/lib/log/log_internal.h
index 7c7d44eed2..82fdc21ac2 100644
--- a/lib/log/log_internal.h
+++ b/lib/log/log_internal.h
@@ -29,6 +29,9 @@  int eal_log_save_pattern(const char *pattern, uint32_t level);
 __rte_internal
 int eal_log_syslog(const char *name);
 
+__rte_internal
+int eal_log_journal(const char *opt);
+
 /*
  * Convert log level to string.
  */
diff --git a/lib/log/log_journal.c b/lib/log/log_journal.c
new file mode 100644
index 0000000000..e9b3aa5640
--- /dev/null
+++ b/lib/log/log_journal.c
@@ -0,0 +1,153 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <rte_log.h>
+
+#include "log_private.h"
+
+/*
+ * Send structured message using journal protocol
+ * See: https://systemd.io/JOURNAL_NATIVE_PROTOCOL/
+ *
+ * Uses writev() to ensure that whole log message is in one datagram
+ */
+static int
+journal_send(int fd, const char *buf, size_t len)
+{
+	struct iovec iov[4];
+	unsigned int n = 0;
+	int priority = rte_log_cur_msg_loglevel() - 1;
+	char msg[] = "MESSAGE=";
+	char newline = '\n';
+	char pbuf[32];	/* "PRIORITY=N\n" */
+
+	iov[n].iov_base = msg;
+	iov[n++].iov_len = strlen(msg);
+
+	iov[n].iov_base = (char *)(uintptr_t)buf;
+	iov[n++].iov_len = len;
+
+	/* if message doesn't end with newline, one will be applied. */
+	if (buf[len - 1] != '\n') {
+		iov[n].iov_base = &newline;
+		iov[n++].iov_len = 1;
+	}
+
+	/* priority value between 0 ("emerg") and 7 ("debug") */
+	iov[n].iov_base = pbuf;
+	iov[n++].iov_len = snprintf(pbuf, sizeof(pbuf),
+				    "PRIORITY=%d\n", priority);
+	return writev(fd, iov, n);
+}
+
+
+/* wrapper for log stream to put messages into journal */
+static ssize_t
+journal_log_write(void *c, const char *buf, size_t size)
+{
+	int fd = (uintptr_t)c;
+
+	return journal_send(fd, buf, size);
+}
+
+static int
+journal_log_close(void *c)
+{
+	int fd = (uintptr_t)c;
+
+	close(fd);
+	return 0;
+}
+
+static cookie_io_functions_t journal_log_func = {
+	.write = journal_log_write,
+	.close = journal_log_close,
+};
+
+/*
+ * Check if stderr is going to system journal.
+ * This is the documented way to handle systemd journal
+ *
+ * See: https://systemd.io/JOURNAL_NATIVE_PROTOCOL/
+ */
+bool
+log_journal_enabled(void)
+{
+	char *jenv, *endp = NULL;
+	struct stat st;
+	unsigned long dev, ino;
+
+	jenv = getenv("JOURNAL_STREAM");
+	if (jenv == NULL)
+		return false;
+
+	if (fstat(STDERR_FILENO, &st) < 0)
+		return false;
+
+	/* systemd sets colon-separated list of device and inode number */
+	dev = strtoul(jenv, &endp, 10);
+	if (endp == NULL || *endp != ':')
+		return false;	/* missing colon */
+
+	ino = strtoul(endp + 1, NULL, 10);
+
+	return dev == st.st_dev && ino == st.st_ino;
+}
+
+/* Connect to systemd's journal service */
+FILE *
+log_journal_open(const char *id)
+{
+	char syslog_id[PATH_MAX];
+	FILE *log_stream;
+	int len;
+	struct sockaddr_un sun = {
+		.sun_family = AF_UNIX,
+		.sun_path = "/run/systemd/journal/socket",
+	};
+	int jfd = -1;
+
+	len = snprintf(syslog_id, sizeof(syslog_id),
+		       "SYSLOG_IDENTIFIER=%s\nSYSLOG_PID=%u", id, getpid());
+
+	/* Detect truncation of message and fallback to no journal */
+	if (len >= (int)sizeof(syslog_id))
+		return NULL;
+
+	jfd = socket(AF_UNIX, SOCK_DGRAM, 0);
+	if (jfd < 0) {
+		perror("socket");
+		goto error;
+	}
+
+	if (connect(jfd, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
+		perror("connect");
+		goto error;
+	}
+
+	/* Send identifier as first message */
+	if (write(jfd, syslog_id, len) != len) {
+		perror("write");
+		goto error;
+	}
+
+	/* redirect other log messages to journal */
+	log_stream = fopencookie((void *)(uintptr_t)jfd, "w", journal_log_func);
+	if (log_stream != NULL)
+		return log_stream;
+
+error:
+	close(jfd);
+	return NULL;
+}
diff --git a/lib/log/log_private.h b/lib/log/log_private.h
index 5da9c9b438..1b66057fd6 100644
--- a/lib/log/log_private.h
+++ b/lib/log/log_private.h
@@ -24,6 +24,22 @@  bool log_syslog_enabled(void);
 FILE *log_syslog_open(const char *id);
 #endif
 
+#ifdef RTE_EXEC_ENV_LINUX
+bool log_journal_enabled(void);
+FILE *log_journal_open(const char *id);
+#else
+static inline bool
+log_journal_enabled(void)
+{
+	return false;
+}
+static inline FILE *
+log_journal_open(const char *id __rte_unused)
+{
+	return NULL;
+}
+#endif /* !RTE_EXEC_ENV_LINUX */
+
 bool log_timestamp_enabled(void);
 ssize_t log_timestamp(char *tsbuf, size_t tsbuflen);
 
diff --git a/lib/log/meson.build b/lib/log/meson.build
index 4ac232786e..86e4452b19 100644
--- a/lib/log/meson.build
+++ b/lib/log/meson.build
@@ -11,4 +11,8 @@  if not is_windows
     sources += files('log_syslog.c')
 endif
 
+if is_linux
+    sources += files('log_journal.c')
+endif
+
 headers = files('rte_log.h')
diff --git a/lib/log/version.map b/lib/log/version.map
index 8be6907840..800d3943bc 100644
--- a/lib/log/version.map
+++ b/lib/log/version.map
@@ -26,6 +26,7 @@  INTERNAL {
 	global:
 
 	eal_log_init;
+	eal_log_journal; # WINDOWS_NO_EXPORT
 	eal_log_level2str;
 	eal_log_save_pattern;
 	eal_log_save_regexp;