[RFC PATCH 1/2] Support logging to UDP destinations.

Hao Xiang hao.xiang at linux.dev
Thu Jul 18 17:49:13 UTC 2024


The original implementation of svlogd from smarden.org supports
logging to a UDP destination but it is not yet in the busybox
version. This change implements this feature. The current implementation
has a few limitations.

* Only supports UDP destination as an additional log target. Logs are
still written to files.
* While sending logs to UDP destination, only the first 1000 characters
per line are send in a UDP message. If a log line is very long, UDP
message will be truncated.

Signed-off-by: Hao Xiang <hao.xiang at linux.dev>
---
 runit/svlogd.c | 203 +++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 197 insertions(+), 6 deletions(-)

diff --git a/runit/svlogd.c b/runit/svlogd.c
index f7576f0fa..c808dfc42 100644
--- a/runit/svlogd.c
+++ b/runit/svlogd.c
@@ -153,7 +153,7 @@ log message, you can use a pattern like this instead
 ///////:   "\n""NNUM - min number files to retain" - confusing
 ///////:   "\n""tSEC - rotate file if it get SEC seconds old" - confusing
 //usage:   "\n""!PROG - process rotated log with PROG"
-///////:   "\n""uIPADDR - send log over UDP" - unsupported
+//usage:   "\n""uIPADDR - send log over UDP"
 ///////:   "\n""UIPADDR - send log over UDP and DONT log" - unsupported
 ///////:   "\n""pPFX - prefix each line with PFX" - unsupported
 //usage:   "\n""+,-PATTERN - (de)select line for logging"
@@ -188,6 +188,9 @@ struct logdir {
 	char fnsave[FMT_PTIME];
 	char match;
 	char matcherr;
+	struct sockaddr_in udpaddr;
+	int udpfd;
+	struct msghdr *msg;
 };
 
 
@@ -197,6 +200,7 @@ struct globals {
 	int linemax;
 	////int buflen;
 	int linelen;
+	int udplinelen;
 
 	int fdwdir;
 	char **fndir;
@@ -216,6 +220,8 @@ struct globals {
 	const char *replace;
 	int fl_flag_0;
 	unsigned dirn;
+	char *udpline;
+	char *udplineptr;
 
 	sigset_t blocked_sigset;
 };
@@ -225,6 +231,7 @@ struct globals {
 #define linemax        (G.linemax       )
 #define buflen         (G.buflen        )
 #define linelen        (G.linelen       )
+#define udplinelen     (G.udplinelen    )
 #define fndir          (G.fndir         )
 #define fdwdir         (G.fdwdir        )
 #define wstat          (G.wstat         )
@@ -240,6 +247,8 @@ struct globals {
 #define blocked_sigset (G.blocked_sigset)
 #define fl_flag_0      (G.fl_flag_0     )
 #define dirn           (G.dirn          )
+#define udpline        (G.udpline       )
+#define udplineptr     (G.udplineptr    )
 #define line bb_common_bufsiz1
 #define INIT_G() do { \
 	setup_common_bufsiz(); \
@@ -256,6 +265,9 @@ struct globals {
 #define PAUSE "pausing: "
 #define INFO "info: "
 
+#define MAX_PREFIX_LEN 60
+#define MAX_IOV_LEN 2
+
 static void fatalx(const char *m0)
 {
 	bb_error_msg_and_die(FATAL"%s", m0);
@@ -677,6 +689,64 @@ static int buffer_pwrite(int n, char *s, unsigned len)
 	return i;
 }
 
+/* Add a buffer line to the UDP message's IOV. */
+static void udp_add_buf(struct msghdr *msg, char *buf, int len)
+{
+	if (msg->msg_iovlen == MAX_IOV_LEN)
+		return;
+
+	msg->msg_iov[msg->msg_iovlen].iov_base = buf;
+	msg->msg_iov[msg->msg_iovlen].iov_len = len;
+	msg->msg_iovlen++;
+}
+
+/* Copy the message payload into a dedicated buffer */
+/* for UDP message. The memory copy always appends  */
+/* to the end of the buffer until the it's full     */
+static void udp_append_line(char *buf, int len)
+{
+	if (!udplinelen)
+		return;
+
+	if (udplinelen < len) {
+		len = udplinelen;
+	}
+	memcpy(udplineptr, buf, len);
+	udplineptr += len;
+	udplinelen -= len;
+}
+
+static size_t get_msg_len(struct msghdr *msg)
+{
+	size_t len = 0;
+	for (int i = 0; i < msg->msg_iovlen; i++) {
+		len += msg->msg_iov[i].iov_len;
+	}
+	return len;
+}
+
+/* Send the UDP message after IOV is set */
+static void udp_write(int n)
+{
+	struct logdir *ld = &dir[n];
+	char *message;
+	size_t totallen = 0;
+
+	const char *udp_failure = "warning: failure sending through udp: ";
+	const char *alloc_failure = "warning: failure allocating string";
+
+	totallen = get_msg_len(ld->msg);
+
+	if (sendmsg(ld->udpfd, ld->msg, 0) != totallen) {
+		if (asprintf(&message, "%s", udp_failure) != -1) {
+			buffer_pwrite(n, message, strlen(message));
+			free(message);
+		} else {
+			warn(alloc_failure);
+		}
+	}
+}
+
 static void logdir_close(struct logdir *ld)
 {
 	if (ld->fddir == -1)
@@ -700,16 +770,40 @@ static void logdir_close(struct logdir *ld)
 	ld->fdlock = -1;
 	free(ld->processor);
 	ld->processor = NULL;
+	if (ld->udpfd != -1)
+		close(ld->udpfd);
+	ld->udpfd = -1;
+	if (ld->msg != NULL)
+		free(ld->msg);
+	ld->msg = NULL;
+}
+
+static struct msghdr *create_msg(struct sockaddr_in *udpaddr)
+{
+	struct msghdr *msg;
+	msg = xzalloc(sizeof(struct msghdr) + sizeof(struct iovec) * MAX_IOV_LEN);
+	msg->msg_iov = (struct iovec *)(msg + sizeof(struct msghdr));
+	msg->msg_name = udpaddr;
+	msg->msg_namelen = sizeof(*udpaddr);
+
+	return msg;
 }
 
 static NOINLINE unsigned logdir_open(struct logdir *ld, const char *fn)
 {
-	char buf[128];
+	char buf[256];
 	unsigned now;
 	char *new, *s, *np;
 	int i;
 	struct stat st;
 
+	char *start;
+	char cp[32];
+	int max_len = sizeof(cp) - 1;
+	int port;
+	char *sp;
+	int len;
+
 	now = monotonic_sec();
 
 	ld->fddir = open(fn, O_RDONLY|O_NDELAY);
@@ -742,6 +836,10 @@ static NOINLINE unsigned logdir_open(struct logdir *ld, const char *fn)
 	ld->name = (char*)fn;
 	ld->ppid = 0;
 	ld->match = '+';
+	ld->udpfd = -1;
+	ld->udpaddr.sin_family = AF_INET;
+	ld->udpaddr.sin_port = 0;
+	ld->msg = NULL;
 	free(ld->inst); ld->inst = NULL;
 	free(ld->processor); ld->processor = NULL;
 
@@ -809,6 +907,48 @@ static NOINLINE unsigned logdir_open(struct logdir *ld, const char *fn)
 					ld->processor = wstrdup(&s[1]);
 				}
 				break;
+			case 'u':
+				start = &s[1];
+				if (*start == '\0') {
+					warn("No ip address string specified");
+					break;
+				}
+				sp = strchr(start, ':');
+				if (sp) {
+					port = atoi(sp + 1);
+					if (port == 0) {
+						warn2("Can't convert string to port", sp + 1);
+						break;
+					}
+					len = sp - start;
+				} else {
+					/* Default port 514 */
+					port = 514;
+					len = strlen(start);
+				}
+				if (len > max_len)
+					len = max_len;
+				strncpy(cp, start, len);
+				cp[len] = '\0';
+				if (!inet_aton(cp, &ld->udpaddr.sin_addr)) {
+					warn2("Can't convert string to ip address", cp);
+					break;
+				}
+				ld->udpaddr.sin_port = port;
+				ld->udpfd = socket(AF_INET, SOCK_DGRAM, 0);
+				if (ld->udpfd == -1) {
+					warn("Can't open UDP socket");
+					break;
+				}
+				ld->msg = create_msg(&ld->udpaddr);
+				if (!udpline) {
+					udpline = xzalloc(COMMON_BUFSIZE);
+				}
+				/* UDP requires one-line buffering,
+				 * resetting the "find newline" function
+				 * accordingly */
+				memRchr = memchr;
+				break;
 			}
 			s = np;
 		}
@@ -879,6 +1019,13 @@ static void logdirs_reopen(void)
 	int ok = 0;
 
 	tmaxflag = 0;
+
+	if (udpline) {
+		free(udpline);
+		udpline = NULL;
+		udplinelen = 0;
+	}
+
 	for (l = 0; l < dirn; ++l) {
 		logdir_close(&dir[l]);
 		if (logdir_open(&dir[l], fndir[l]))
@@ -1047,6 +1194,23 @@ static void logmatch(struct logdir *ld, char* lineptr, int lineptr_len)
 	}
 }
 
+static char *get_lineptr(unsigned timestamp)
+{
+	char *lineptr = line;
+	if (timestamp)
+		lineptr += 26;
+	return lineptr;
+}
+
+static bool dir_log_match(void)
+{
+	for (int i = 0; i < dirn; ++i) {
+		if (dir[i].match == '+')
+			return TRUE;
+	}
+	return FALSE;
+}
+
 int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int svlogd_main(int argc, char **argv)
 {
@@ -1055,6 +1219,7 @@ int svlogd_main(int argc, char **argv)
 	int i;
 	unsigned opt;
 	unsigned timestamp = 0;
+	bool match;
 
 	INIT_G();
 
@@ -1097,6 +1262,7 @@ int svlogd_main(int argc, char **argv)
 		dir[i].fdcur = -1;
 		////dir[i].btmp = xmalloc(buflen);
 		/*dir[i].ppid = 0;*/
+		dir[i].udpfd = -1;
 	}
 	/* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
 	fndir = argv;
@@ -1136,9 +1302,7 @@ int svlogd_main(int argc, char **argv)
 		int printlen;
 		char ch;
 
-		lineptr = line;
-		if (timestamp)
-			lineptr += 26;
+		lineptr = get_lineptr(timestamp);
 
 		/* lineptr[0..linemax-1] - buffer for stdin */
 		/* (possibly has some unprocessed data from prev loop) */
@@ -1192,6 +1356,8 @@ int svlogd_main(int argc, char **argv)
 		}
 		for (i = 0; i < dirn; ++i) {
 			struct logdir *ld = &dir[i];
+			if (ld->msg)
+				ld->msg->msg_iovlen = 0;
 			if (ld->fddir == -1)
 				continue;
 			if (ld->inst)
@@ -1206,6 +1372,18 @@ int svlogd_main(int argc, char **argv)
 			buffer_pwrite(i, printptr, printlen);
 		}
 
+		/* Reset the UDP message buffer */
+		if (udpline) {
+			udplineptr = udpline;
+			udplinelen = COMMON_BUFSIZE;
+		}
+
+		match = dir_log_match();
+
+		/* Copy print buffer to UDP message */
+		if (udpline && match)
+			udp_append_line(printptr, printlen);
+
 		/* If we didn't see '\n' (long input line), */
 		/* read/write repeatedly until we see it */
 		while (ch != '\n') {
@@ -1235,6 +1413,19 @@ int svlogd_main(int argc, char **argv)
 					continue;
 				buffer_pwrite(i, lineptr, linelen);
 			}
+			/* Copy print buffer to UDP message */
+			if (udpline && match)
+				udp_append_line(lineptr, linelen);
+		}
+
+		for (i = 0; i < dirn; i++) {
+			if (dir[i].match != '+')
+				continue;
+			if (dir[i].udpfd != -1) {
+				/* Add log payload to UDP message */
+				udp_add_buf(dir[i].msg, udpline, udplineptr - udpline);
+				udp_write(i);
+			}
 		}
 
 		stdin_cnt -= linelen;
@@ -1246,7 +1437,7 @@ int svlogd_main(int argc, char **argv)
 			if (np)
 				goto print_to_nl;
 			/* Move unprocessed data to the front of line */
-			memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
+			memmove(get_lineptr(timestamp), lineptr, stdin_cnt);
 		}
 		fflush_all();////
 	}
-- 
2.45.2



More information about the busybox mailing list