about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--README11
-rw-r--r--mlog.111
-rw-r--r--mlog.c56
3 files changed, 70 insertions, 8 deletions
diff --git a/README b/README
index 6b6e439..401a32c 100644
--- a/README
+++ b/README
@@ -4,12 +4,13 @@ NAME
      mlog – merge log files by timestamp
 
 SYNOPSIS
-     mlog [-f] [-s] [-u] [files ...]
+     mlog [-f] [-s] [-u] [-n lines] [files ...]
 
 DESCRIPTION
      mlog reads lines from multiple log files and prints them in chronological
-     order.  The files need to start with comparable timestamps.  mlog prints
-     the oldest line first, thus preserving relative order.
+     order.  The lines in the files need to start with lexicographically
+     comparable timestamps.  mlog prints the oldest line first, thus
+     preserving relative order.
 
      mlog behaves like a combination of ‘sort -m’ and ‘tail -f’.
 
@@ -20,6 +21,8 @@ DESCRIPTION
              With two -f flags, seek to the end of files on start and only
              print fresh lines.
 
+     -n N    Only print the last N lines of each file (default: all lines).
+
      -s      Strip a prefix generated by socklog(8) from each line, which have
              a format like ‘facility.priority: timestamp’.
 
@@ -46,4 +49,4 @@ LICENSE
 
      http://creativecommons.org/publicdomain/zero/1.0/
 
-Void Linux                     January 12, 2024                     Void Linux
+Void Linux                     January 14, 2024                     Void Linux
diff --git a/mlog.1 b/mlog.1
index edbd3a9..21d5168 100644
--- a/mlog.1
+++ b/mlog.1
@@ -1,4 +1,4 @@
-.Dd January 12, 2024
+.Dd January 14, 2024
 .Dt MLOG 1
 .Os
 .Sh NAME
@@ -9,11 +9,13 @@
 .Op Fl f
 .Op Fl s
 .Op Fl u
+.Op Fl n Ar lines
 .Op Ar files\ ...
 .Sh DESCRIPTION
 .Nm
 reads lines from multiple log files and prints them in chronological order.
-The files need to start with comparable timestamps.
+The lines in the files need to start with lexicographically comparable
+timestamps.
 .Nm
 prints the oldest line first, thus preserving relative order.
 .Pp
@@ -31,6 +33,11 @@ When the file is truncated or recreated, start from beginning.
 With two
 .Fl f
 flags, seek to the end of files on start and only print fresh lines.
+.It Fl n Ar N
+Only print the last
+.Ar N
+lines of each file
+.Pq default: all lines .
 .It Fl s
 Strip a prefix generated by
 .Xr socklog 8
diff --git a/mlog.c b/mlog.c
index 4167555..b5c06b7 100644
--- a/mlog.c
+++ b/mlog.c
@@ -21,6 +21,7 @@ int ifd;
 int uflag;			/* remove duplicates */
 int fflag;			/* -f follow / -ff tail+follow */
 int sflag;			/* strip socklog prefix */
+int nflag;			/* number of lines at end to print */
 
 struct logfile {
 	char *path;
@@ -262,19 +263,68 @@ nextline(int i)
 	return 0;
 }
 
+void
+tail_line(FILE *file, int n)
+{
+	size_t page = 4096;
+
+	if (fseek(file, -page, SEEK_END) < 0) {
+		if (errno == EINVAL)
+			rewind(file);
+		else
+			return; /* can't seek */
+	}
+
+	int l = -1;		/* line ends with newline */
+	char buf[page];
+
+	while (1) {
+		clearerr(file);
+		long off = ftell(file);
+		ssize_t in = fread(buf, 1, page, file);
+
+		size_t i;
+		for (i = in; l < n && i > 0; i--) {
+			if (buf[i] == '\n')
+				l++;
+		}
+
+		if (l == n) {
+			fseek(file, off + i + 2, SEEK_SET);
+			break;
+		} else if (off == 0) {	/* file too short */
+			rewind(file);
+			break;
+		}
+
+		/* seek back more and count again */
+		if (fseek(file, -2*page, SEEK_CUR) < 0) {
+			if (errno == EINVAL) {
+				rewind(file);
+				break;
+			} else {
+				return;
+			}
+		}
+	}
+
+	clearerr(file);
+}
+
 int
 main(int argc, char *argv[])
 {
 	int opt;
 
-	while ((opt = getopt(argc, argv, "fsu")) != -1) {
+	while ((opt = getopt(argc, argv, "fn:su")) != -1) {
                 switch (opt) {
                 case 'f': fflag++; break;
+                case 'n': nflag = atoi(optarg); break;
                 case 's': sflag++; break;
                 case 'u': uflag++; break;
                 default:
 		usage:
-                        fputs("usage: mlog [-fsu] FILES...\n", stderr);
+                        fputs("usage: mlog [-fsu] [-n LINES] FILES...\n", stderr);
                         exit(2);
                 }
         }
@@ -301,6 +351,8 @@ main(int argc, char *argv[])
 			    strerror(errno));
 		if (fflag > 1 && logs[i].file)
 			fseek(logs[i].file, 0L, SEEK_END);
+		else if (nflag > 0 && logs[i].file)
+			tail_line(logs[i].file, nflag);
 	}
 
 	while (1) {