3 * (c) Peter Roozemaal, feb 2003
5 * $Id: logrunner.cpp,v 1.6 2007-12-10 16:12:37 arjen Exp $
8 * 2) Add 'logfile' elements to the gnucomo configuration file
9 * defining the name and type of each logfile to scan.
10 * 'filter' elements can be addes to pre-filter lines from the log.
14 * <name>/var/log/httpd/access_log</name>
15 * <type>apache access log</type>
18 * <name>/var/log/messages</name>
19 * <type>system log</type>
20 * <filter>open_scanner</filter>
21 * <filter>session closed</filter>
24 * 3) run executable and feed the output to gcm_input
26 #include <sys/types.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
45 #include "gnucomo_config.h"
47 extern String XML_Entities(String s);
49 static const char* const usage =
50 "Usage: logrunner [<options>] [<hostname>]\n\n"
51 "<hostname> is the gnucomo data collection server\n"
53 " -1: one-shot; stop after a single pass over one of the configured logfiles\n"
54 " -c <name>: specify alternative configuration name, default is \'gnucomo\'\n"
55 " -l <file>: Read input from logfile <file>.\n"
56 " -p <number>: set IP port number to connect to\n"
57 " -s <file>: Use <file> as the status file instead of the default.\n"
58 " -v Verbose output\n"
59 "If no hostname is specified, logrunner sends output to stdout\n";
70 std::list<regex> filter;
75 LogFile(String n, String t, String fh)
85 LogFile(const LogFile &lf)
89 fromhost = lf.fromhost;
93 position = lf.position;
102 void update_status(ino_t i, off_t p)
110 return name + " " + String(inode) + " " + String(position);
113 void add_filter(String filter_expression)
115 filter.push_back(filter_expression);
121 static std::list<LogFile> logs_to_run;
123 String confname = "gnucomo";
124 static char *statusname;
125 static char *newstatusname;
126 static char default_statusname[] = "/var/lib/logrunner.status";
127 static char default_newstatusname[] = "/var/lib/logrunner.status.new";
129 static const char *hostname = NULL;
130 static int port = 2996; /* random magic number */
131 static int out_stream = 1;
133 static int oneshot = 0;
134 static int verbose = 0;
136 static int something_has_changed = 0;
138 static volatile sig_atomic_t sig_seen = 0;
140 static void sighandler(int sig)
145 static void set_signal_handler(void)
147 /* These aren't all; but it's a nice set to start with */
148 signal(SIGHUP, sighandler);
149 signal(SIGINT, sighandler);
150 signal(SIGQUIT, sighandler);
151 signal(SIGABRT, sighandler);
152 signal(SIGPIPE, sighandler);
153 signal(SIGTERM, sighandler);
159 struct hostent* hostptr;
160 struct sockaddr_in addr;
161 unsigned int namelen = sizeof(addr);
165 hostptr = gethostbyname(hostname);
168 fprintf(stderr, "logrunner: FATAL: cannot resolve %s\n", hostname);
171 out_stream = socket(PF_INET, SOCK_STREAM, 0);
172 if ( out_stream < 0 )
174 fprintf(stderr, "logrunner: FATAL: Socket creation failed\n");
177 addr.sin_family = AF_INET;
178 addr.sin_addr.s_addr = *((long*)(hostptr->h_addr));
179 addr.sin_port = htons(port);
180 if ( connect(out_stream, (struct sockaddr*) &addr, namelen) < 0 )
182 fprintf(stderr, "logrunner: FATAL: connect to %s failed\n", hostname);
192 void xsend(const char* str)
194 write(out_stream, str, strlen(str));
197 void xml_header(String type)
199 struct hostent *host;
202 gethostname(buffer, sizeof(buffer));
204 // Try to obtain the official name of the host (FQDN)
206 host = gethostbyname(buffer);
209 strcpy(buffer, host->h_name);
212 xsend("<?xml version='1.0'?>\n");
213 xsend("<gcmt:message xmlns:gcmt=\"http://gnucomo.org/transport/\">\n");
214 xsend("<gcmt:header>\n<gcmt:hostname>");
216 xsend("</gcmt:hostname>\n<gcmt:messagetype>");
218 xsend("</gcmt:messagetype>\n</gcmt:header>\n");
219 xsend("<gcmt:data><gcmt:log>");
222 void xml_footer(void)
224 xsend("</gcmt:log></gcmt:data>\n</gcmt:message>\n");
227 void output(const char* str)
232 void output_error(const char* str)
234 std::cerr << str << " errno=" << errno << "\n";
237 void LogFile::do_file()
239 struct stat statinfo;
243 if ( stat(name, &statinfo) )
245 std::cerr << "!!! logfile: stat failed: " << name << ", errno=" << errno << "\n";
249 if ( statinfo.st_ino != inode )
259 std::cerr << "@@@ logfile: logrotate detected: " << name << "\n";
261 inode = statinfo.st_ino;
267 /* attempt to open the file */
268 fd = open(name, O_RDONLY);
271 std::cerr << "!!! logfile: open failed: " << name << ", " << strerror(errno) << "\n";
274 lseek(fd, position, SEEK_SET);
278 std::cerr << "*** logfile: opened: " << name;
279 std::cerr << "\n*** logfile: resumed read from position ";
280 std::cerr << position << "\n";
281 std::cerr << "This logfile has " << filter.size() << " filters.\n";
288 void LogFile::copy_data()
294 /* read data and dump to output */
295 ndata = read(fd, buffer, sizeof(buffer)-1);
301 // Make a separate <gcmt:raw> element from each line
303 char *line, *nextline;
308 while (nextline < buffer + ndata)
310 while (*nextline != '\n' && nextline < buffer + ndata)
314 if (*nextline == '\n')
316 // Another line found - make the split.
321 // See if we have to select the host and apply filters to this log entry
323 bool filtered_out = false;
325 if (fromhost && (fromhost.in(logline) == -1 || fromhost.in(logline) > 20))
330 std::list<regex>::iterator f = filter.begin();
331 while (f != filter.end())
342 logline = XML_Entities(logline);
344 write(out_stream, "<gcmt:raw>", 10);
345 write(out_stream, logline, ~logline);
346 write(out_stream, "</gcmt:raw>\n", 12);
350 std::cerr << logline << " is filtered out.\n";
358 // We have a buffer full of data but no newline.
359 // Flush the buffer into logline and continue reading.
366 if (line != nextline)
368 // There is still an incomplete line in the buffer.
369 memmove(buffer, line, nextline - line);
371 position += ndata - (nextline - line);
372 ndata -= line - buffer;
373 ndata += read(fd, buffer + (nextline - line), sizeof(buffer) - 1 - (nextline - line));
376 something_has_changed = 1;
380 std::cerr << "!!! logfile: read failed: " << name << ", " << strerror(errno) << "\n";
381 std::cerr << " file descriptor = " << fd << "\n";
385 void write_status_file()
390 std::ofstream statusfile(newstatusname);
391 std::list<LogFile>::iterator lf = logs_to_run.begin();
392 while (lf != logs_to_run.end())
396 std::cerr << "Write status for " << lf->pathname() << "\n";
398 statusfile << lf->status() << "\n";
402 if ( localerror == 0 )
404 if ( rename(newstatusname, statusname) )
406 output_error("!!! dumpstatus: rename failed");
409 something_has_changed = 0;
421 statusfile = fopen(statusname, "r");
422 if ( statusfile == NULL )
424 fprintf(stderr, "logrunner: can\'t open status file \'%s\': ",
429 while ( fgets(buffer, sizeof(buffer), statusfile ) )
431 xp = strchr(buffer, ' ');
435 sscanf(xp+1, "%ld %lu", &ino, &pos);
437 // Search for the logfile in the list of logs to run
439 std::list<LogFile>::iterator lf = logs_to_run.begin();
440 while (lf != logs_to_run.end())
442 if (lf->pathname() == String(buffer))
446 std::cerr << "Read status for " << lf->pathname() << "\n";
448 lf->update_status(ino, pos);
458 void read_config(gnucomo_config cfg)
461 * The configuration for logrunner is stored in the central Gnucomo
462 * configuration file. Multiple 'logfile' elements can be put in this
463 * XML file, one for each logfile to scan.
464 * Each 'logfile' element has at least a 'name' and a 'type' element
465 * that denote the pathname of the logfile and the type of its content.
474 logfilename = cfg.find_parameter("logfile", "name", l);
475 while (logfilename != String(""))
477 logfiletype = cfg.find_parameter("logfile", "type", l);
478 fromhost = cfg.find_parameter("logfile", "fromhost", l);
481 std::cerr << "LogFile " << logfilename << " of type "
482 << logfiletype << " from host " << fromhost << "\n";
485 LogFile lf(logfilename, logfiletype, fromhost);
488 String exp = cfg.find_parameter("logfile", "filter", l, f);
494 exp = cfg.find_parameter("logfile", "filter", l, f);
497 logs_to_run.push_back(lf);
500 logfilename = cfg.find_parameter("logfile", "name", l);
505 void process_options(int argc, char* argv[])
507 const char* const options = "1c:l:p:s:v";
510 statusname = default_statusname;
511 newstatusname = default_newstatusname;
514 opt = getopt(argc, argv, options);
523 confname = strdup(optarg);
526 logfilename = optarg;
532 statusname = strdup(optarg);
533 newstatusname = (char *)malloc(strlen(statusname) + 6);
534 strcpy(newstatusname, statusname);
535 strcat(newstatusname, ".new");
541 fputs(usage, stderr);
544 opt = getopt(argc, argv, options);
546 switch ( argc-optind )
551 hostname = argv[optind];
554 fputs(usage, stderr);
560 LogFile lf(logfilename, String("system log"), String(""));
561 logs_to_run.push_back(lf);
565 int main(int argc, char* argv[])
570 process_options(argc, argv);
573 if (!cfg.read(confname))
575 std::cerr << "Can not read Gnucomo configuration file for " << confname << ".\n";
579 if (logs_to_run.empty())
585 set_signal_handler();
587 while ( sig_seen == 0 )
589 std::list<LogFile>::iterator lf = logs_to_run.begin();
590 while (lf != logs_to_run.end() && !(something_has_changed && oneshot))
596 if ( something_has_changed )
607 fprintf(stderr, "logrunner: stopped by signal %d\n", sig_seen);
608 /* shouldn't we close files and release memory here? */