Last active
December 31, 2022 06:00
-
-
Save stefanalt/dd4e68490d1a4e2a343b0beaa1b0d230 to your computer and use it in GitHub Desktop.
log STDIN to in-memory ring-buffer, print buffer content on pipe close or signal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* rbuflog.c - log STDIN to in-memory ring-buffer, print on pipe close or signal | |
* | |
* See usage() for details; | |
*/ | |
#include <stdio.h> | |
#include <signal.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/types.h> | |
#include <errno.h> | |
#include <time.h> | |
//#include "svnglbrev.h" | |
volatile int sigusr1_flag; | |
int opt_lines; | |
int opt_dots; | |
#define LOG_MAX 2048 | |
#define LOG_LINE_LENGTH 512 | |
int log_pos; // next entry to fill | |
struct log_buff { | |
/* todo timestamp */ | |
char line[LOG_LINE_LENGTH]; | |
} log_buff[LOG_MAX]; | |
void log_print( | |
FILE *fp, | |
int linecnt, | |
const char *reason | |
) | |
{ | |
int i; | |
int pos; | |
time_t daytime; | |
char daytime_frmt[80]; | |
if( linecnt >= LOG_MAX-1 ){ | |
linecnt = LOG_MAX-1; | |
} | |
/* print in output order */ | |
pos = log_pos - linecnt; | |
if( pos < 0 ){ | |
pos = pos + LOG_MAX; | |
} | |
time(&daytime); | |
strftime(daytime_frmt, 80, "%Y-%m-%dT%H:%M:%S", localtime(&daytime)); | |
fprintf(fp, "## rbuflog %s (%s) #########\n", daytime_frmt, reason); | |
for(i=0; i<linecnt; i++){ | |
if( log_buff[pos].line[0] != '\0' ){ | |
fprintf(fp, "%s", log_buff[pos].line); | |
} | |
pos++; | |
if( pos >= LOG_MAX ){ | |
pos = 0; | |
} | |
} | |
fflush(fp); | |
} | |
void sighandler( | |
int signo | |
) | |
{ | |
sigusr1_flag = 1; | |
} | |
/* | |
* Show help | |
* | |
* RETURNS: N/A | |
*/ | |
void usage(void) | |
{ | |
fprintf( | |
stdout, | |
/* 1 2 3 4 5 6 7 */ | |
/* 345678901234567890123456789012345678901234567890123456789012345678901234567*/ | |
"rbuflog - log STDIN to in-memory ring-buffer, print on pipe close or signal\n" | |
"USAGE: rbuflog [options] [<outputfile>]\n" | |
"VERSION: $Id: rbuflog.c 4010 2022-12-30 17:02:57Z as $ " | |
#ifdef __SVNGLBREV__ | |
"$GlbRev: " __SVNGLBREV__ " $" | |
#endif | |
"\n" | |
"BUILT: " __DATE__ " " __TIME__ "\n" | |
"\n" | |
"The program consumes lines from STDIN and stores them to a ring buffer.\n" | |
"When STDIN gets closed (or on receiption of a signal) the requested number\n" | |
"of lines are printed to STDOUT or to <outputfile>.\n" | |
"The ring buffer is currently limited to 2048 lines.\n" | |
"\n" | |
"SIGNALS:\n" | |
"Upon receiving SIGUSR1 the current ring-buffer content is printed as\n" | |
"requested on the command line.\n" | |
"\n" | |
"\n" | |
"OPTIONS: (defaults in square brackets)\n" | |
" -n <lines> Lines to print [10]. Less lines might be printed if the\n" | |
" line length execeeds 512 characters\n" | |
" -d <n> Print a dot to STDERR for every <n> input lines. [0=off].\n" | |
"\n" | |
"EXAMPLE:\n" | |
"some-program | rbuflog -n 20\n" | |
); | |
} | |
int main( | |
int argc, | |
char **argv | |
) | |
{ | |
int opt; | |
long long linecnt; | |
char *filename; | |
FILE *fpout; | |
char *p; | |
struct sigaction act; | |
linecnt = 0; | |
opt_lines = 10; | |
opt_dots = 0; | |
sigusr1_flag = 0; | |
while( (opt=getopt(argc, argv, "hn:d:")) != EOF ){ | |
switch(opt){ | |
case 'n': | |
opt_lines = strtol(optarg, 0, 0); | |
break; | |
case 'd': | |
opt_dots = strtol(optarg, 0, 0); | |
break; | |
case '?': /* unknown option */ | |
case 'h': /* help */ | |
case ':': /* missing parameter */ | |
usage(); | |
exit(1); | |
} | |
} | |
argv += optind; | |
argc -= optind; | |
if( argc == 1 ){ | |
filename = argv[0]; | |
fpout = fopen(filename, "w"); | |
if( fpout == NULL ){ | |
fprintf(stderr, "rbuflog: can not open %s for writing\n", filename); | |
exit(1); | |
} | |
} else { | |
fpout = stdout; | |
} | |
signal(SIGINT, SIG_IGN); | |
/* gnu tee ignores SIGPIPE in case one of the output files is a pipe | |
* that doesn't consume all its input. Good idea... */ | |
signal(SIGPIPE, SIG_IGN); | |
/* We want the USR1 interupt the fgets() so we can print the | |
log buffer even if there are no input lines at that time */ | |
act.sa_handler = sighandler; | |
sigemptyset(&act.sa_mask); | |
act.sa_flags = 0; | |
sigaction(SIGUSR1, &act, 0); | |
log_pos = 0; | |
memset(log_buff, 0x00, sizeof(log_buff)); | |
while( 1 ){ | |
/* read one line or less. If the line is too long for our | |
buffer, it will be continued in the next log buffer */ | |
errno = 0; | |
p = fgets(log_buff[log_pos].line, LOG_LINE_LENGTH, stdin); | |
if( (p == NULL) ){ | |
if( (errno == EINTR) && sigusr1_flag ){ | |
log_print(fpout, opt_lines, "interrupt"); | |
sigusr1_flag = 0; | |
continue; | |
} else { | |
log_print(fpout, opt_lines, "EOF"); | |
break; | |
} | |
} | |
if( sigusr1_flag ){ | |
log_print(fpout, opt_lines, "interrupt"); | |
sigusr1_flag = 0; | |
} | |
linecnt++; | |
if( opt_dots ){ | |
if( (linecnt % opt_dots) == 0 ){ | |
fprintf(stderr, "."); | |
if( ((linecnt / opt_dots) % 80) == 0 ){ | |
fprintf(stderr, "\n"); | |
} | |
} | |
} | |
log_pos++; | |
if( log_pos >= LOG_MAX ){ | |
log_pos = 0; | |
} | |
} | |
exit(0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment