From 6847561a81a9310c82f19f209008a56073225fcb Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Sun, 14 Jan 2024 21:23:30 +0100 Subject: add -n --- README | 11 +++++++---- mlog.1 | 11 +++++++++-- mlog.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 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) { -- cgit 1.4.1