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>
43 #include <AXE/String.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 = "/var/lib/logrunner.status";
125 static char* newstatusname = "/var/lib/logrunner.status.new";
127 static const char *hostname = NULL;
128 static int port = 2996; /* random magic number */
129 static int out_stream = 1;
131 static int oneshot = 0;
132 static int verbose = 0;
134 static int something_has_changed = 0;
136 static volatile sig_atomic_t sig_seen = 0;
138 static void sighandler(int sig)
143 static void set_signal_handler(void)
145 /* These aren't all; but it's a nice set to start with */
146 signal(SIGHUP, sighandler);
147 signal(SIGINT, sighandler);
148 signal(SIGQUIT, sighandler);
149 signal(SIGABRT, sighandler);
150 signal(SIGPIPE, sighandler);
151 signal(SIGTERM, sighandler);
157 struct hostent* hostptr;
158 struct sockaddr_in addr;
159 unsigned int namelen = sizeof(addr);
163 hostptr = gethostbyname(hostname);
166 fprintf(stderr, "logrunner: FATAL: cannot resolve %s\n", hostname);
169 out_stream = socket(PF_INET, SOCK_STREAM, 0);
170 if ( out_stream < 0 )
172 fprintf(stderr, "logrunner: FATAL: Socket creation failed\n");
175 addr.sin_family = AF_INET;
176 addr.sin_addr.s_addr = *((long*)(hostptr->h_addr));
177 addr.sin_port = htons(port);
178 if ( connect(out_stream, (struct sockaddr*) &addr, namelen) < 0 )
180 fprintf(stderr, "logrunner: FATAL: connect to %s failed\n", hostname);
190 void xsend(const char* str)
192 write(out_stream, str, strlen(str));
195 void xml_header(String type)
197 struct hostent *host;
200 gethostname(buffer, sizeof(buffer));
202 // Try to obtain the official name of the host (FQDN)
204 host = gethostbyname(buffer);
207 strcpy(buffer, host->h_name);
210 xsend("<?xml version='1.0'?>\n");
211 xsend("<gcmt:message xmlns:gcmt=\"http://gnucomo.org/transport/\">\n");
212 xsend("<gcmt:header>\n<gcmt:hostname>");
214 xsend("</gcmt:hostname>\n<gcmt:messagetype>");
216 xsend("</gcmt:messagetype>\n</gcmt:header>\n");
217 xsend("<gcmt:data><gcmt:log>");
220 void xml_footer(void)
222 xsend("</gcmt:log></gcmt:data>\n</gcmt:message>\n");
225 void output(const char* str)
230 void output_error(const char* str)
232 std::cerr << str << " errno=" << errno << "\n";
235 void LogFile::do_file()
237 struct stat statinfo;
241 if ( stat(name, &statinfo) )
243 std::cerr << "!!! logfile: stat failed: " << name << ", errno=" << errno << "\n";
247 if ( statinfo.st_ino != inode )
257 std::cerr << "@@@ logfile: logrotate detected: " << name << "\n";
259 inode = statinfo.st_ino;
265 /* attempt to open the file */
266 fd = open(name, O_RDONLY);
269 std::cerr << "!!! logfile: open failed: " << name << ", " << strerror(errno) << "\n";
272 lseek(fd, position, SEEK_SET);
276 std::cerr << "*** logfile: opened: " << name;
277 std::cerr << "\n*** logfile: resumed read from position ";
278 std::cerr << position << "\n";
279 std::cerr << "This logfile has " << filter.size() << " filters.\n";
286 void LogFile::copy_data()
292 /* read data and dump to output */
293 ndata = read(fd, buffer, sizeof(buffer)-1);
299 // Make a separate <gcmt:raw> element from each line
301 char *line, *nextline;
306 while (nextline < buffer + ndata)
308 while (*nextline != '\n' && nextline < buffer + ndata)
312 if (*nextline == '\n')
314 // Another line found - make the split.
319 // See if we have to select the host and apply filters to this log entry
321 bool filtered_out = false;
323 if (fromhost && (fromhost.in(logline) == -1 || fromhost.in(logline) > 20))
328 std::list<regex>::iterator f = filter.begin();
329 while (f != filter.end())
340 logline = XML_Entities(logline);
342 write(out_stream, "<gcmt:raw>", 10);
343 write(out_stream, logline, ~logline);
344 write(out_stream, "</gcmt:raw>\n", 12);
348 std::cerr << logline << " is filtered out.\n";
356 // We have a buffer full of data but no newline.
357 // Flush the buffer into logline and continue reading.
364 if (line != nextline)
366 // There is still an incomplete line in the buffer.
367 memmove(buffer, line, nextline - line);
369 position += ndata - (nextline - line);
370 ndata -= line - buffer;
371 ndata += read(fd, buffer + (nextline - line), sizeof(buffer) - 1 - (nextline - line));
374 something_has_changed = 1;
378 std::cerr << "!!! logfile: read failed: " << name << ", " << strerror(errno) << "\n";
379 std::cerr << " file descriptor = " << fd << "\n";
383 void write_status_file()
388 std::ofstream statusfile(newstatusname);
389 std::list<LogFile>::iterator lf = logs_to_run.begin();
390 while (lf != logs_to_run.end())
394 std::cerr << "Write status for " << lf->pathname() << "\n";
396 statusfile << lf->status() << "\n";
400 if ( localerror == 0 )
402 if ( rename(newstatusname, statusname) )
404 output_error("!!! dumpstatus: rename failed");
407 something_has_changed = 0;
419 statusfile = fopen(statusname, "r");
420 if ( statusfile == NULL )
422 fprintf(stderr, "logrunner: can\'t open status file \'%s\': ",
427 while ( fgets(buffer, sizeof(buffer), statusfile ) )
429 xp = strchr(buffer, ' ');
433 sscanf(xp+1, "%ld %lu", &ino, &pos);
435 // Search for the logfile in the list of logs to run
437 std::list<LogFile>::iterator lf = logs_to_run.begin();
438 while (lf != logs_to_run.end())
440 if (lf->pathname() == String(buffer))
444 std::cerr << "Read status for " << lf->pathname() << "\n";
446 lf->update_status(ino, pos);
456 void read_config(gnucomo_config cfg)
459 * The configuration for logrunner is stored in the central Gnucomo
460 * configuration file. Multiple 'logfile' elements can be put in this
461 * XML file, one for each logfile to scan.
462 * Each 'logfile' element has at least a 'name' and a 'type' element
463 * that denote the pathname of the logfile and the type of its content.
472 logfilename = cfg.find_parameter("logfile", "name", l);
473 while (logfilename != String(""))
475 logfiletype = cfg.find_parameter("logfile", "type", l);
476 fromhost = cfg.find_parameter("logfile", "fromhost", l);
479 std::cerr << "LogFile " << logfilename << " of type "
480 << logfiletype << " from host " << fromhost << "\n";
483 LogFile lf(logfilename, logfiletype, fromhost);
486 String exp = cfg.find_parameter("logfile", "filter", l, f);
492 exp = cfg.find_parameter("logfile", "filter", l, f);
495 logs_to_run.push_back(lf);
498 logfilename = cfg.find_parameter("logfile", "name", l);
503 void process_options(int argc, char* argv[])
505 const char* const options = "1c:l:p:s:v";
510 opt = getopt(argc, argv, options);
519 confname = strdup(optarg);
522 logfilename = optarg;
528 statusname = strdup(optarg);
529 newstatusname = (char *)malloc(strlen(statusname) + 6);
530 strcpy(newstatusname, statusname);
531 strcat(newstatusname, ".new");
537 fputs(usage, stderr);
540 opt = getopt(argc, argv, options);
542 switch ( argc-optind )
547 hostname = argv[optind];
550 fputs(usage, stderr);
556 LogFile lf(logfilename, String("system log"), String(""));
557 logs_to_run.push_back(lf);
561 int main(int argc, char* argv[])
566 process_options(argc, argv);
569 if (!cfg.read(confname))
571 std::cerr << "Can not read Gnucomo configuration file for " << confname << ".\n";
575 if (logs_to_run.empty())
581 set_signal_handler();
583 while ( sig_seen == 0 )
585 std::list<LogFile>::iterator lf = logs_to_run.begin();
586 while (lf != logs_to_run.end() && !(something_has_changed && oneshot))
592 if ( something_has_changed )
603 fprintf(stderr, "logrunner: stopped by signal %d\n", sig_seen);
604 /* shouldn't we close files and release memory here? */