3 * (c) Peter Roozemaal, feb 2003
5 * $Id: logrunner.cpp,v 1.4 2007-10-27 08:46:21 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 " -p <number>: set IP port number to connect to\n"
55 " -c <file>: specify alternative configuration file, default is \'logrunner.conf\'\n"
56 "If no hostname is specified, logrunner sends output to stdout\n";
67 std::list<regex> filter;
72 LogFile(String n, String t, String fh)
82 LogFile(const LogFile &lf)
86 fromhost = lf.fromhost;
90 position = lf.position;
99 void update_status(ino_t i, off_t p)
107 return name + " " + String(inode) + " " + String(position);
110 void add_filter(String filter_expression)
112 filter.push_back(filter_expression);
113 std::cerr << "add_filter, filter size is " << filter.size() << "\n";
119 static std::list<LogFile> logs_to_run;
121 String confname = "gnucomo";
122 static const char* statusname = "/var/lib/logrunner.status";
123 static const char* newstatusname = "/var/lib/logrunner.status.new";
125 static const char *hostname = NULL;
126 static int port = 2996; /* random magic number */
127 static int out_stream = 1;
129 static int oneshot = 0;
131 static int something_has_changed = 0;
133 static volatile sig_atomic_t sig_seen = 0;
135 static void sighandler(int sig)
140 static void set_signal_handler(void)
142 /* These aren't all; but it's a nice set to start with */
143 signal(SIGHUP, sighandler);
144 signal(SIGINT, sighandler);
145 signal(SIGQUIT, sighandler);
146 signal(SIGABRT, sighandler);
147 signal(SIGPIPE, sighandler);
148 signal(SIGTERM, sighandler);
154 struct hostent* hostptr;
155 struct sockaddr_in addr;
156 unsigned int namelen = sizeof(addr);
160 hostptr = gethostbyname(hostname);
163 fprintf(stderr, "logrunner: FATAL: cannot resolve %s\n", hostname);
166 out_stream = socket(PF_INET, SOCK_STREAM, 0);
167 if ( out_stream < 0 )
169 fprintf(stderr, "logrunner: FATAL: Socket creation failed\n");
172 addr.sin_family = AF_INET;
173 addr.sin_addr.s_addr = *((long*)(hostptr->h_addr));
174 addr.sin_port = htons(port);
175 if ( connect(out_stream, (struct sockaddr*) &addr, namelen) < 0 )
177 fprintf(stderr, "logrunner: FATAL: connect to %s failed\n", hostname);
187 void xsend(const char* str)
189 write(out_stream, str, strlen(str));
192 void xml_header(String type)
196 gethostname(buffer, sizeof(buffer));
197 xsend("<?xml version='1.0'?>\n");
198 xsend("<gcmt:message xmlns:gcmt=\"http://gnucomo.org/transport/\">\n");
199 xsend("<gcmt:header>\n<gcmt:hostname>");
201 xsend("</gcmt:hostname>\n<gcmt:messagetype>");
203 xsend("</gcmt:messagetype>\n</gcmt:header>\n");
204 xsend("<gcmt:data><gcmt:log>");
207 void xml_footer(void)
209 xsend("</gcmt:log></gcmt:data>\n</gcmt:message>\n");
212 void output(const char* str)
217 void output_error(const char* str)
219 std::cerr << str << " errno=" << errno << "\n";
222 void LogFile::do_file()
224 struct stat statinfo;
228 if ( stat(name, &statinfo) )
230 std::cerr << "!!! logfile: stat failed: " << name << ", errno=" << errno << "\n";
234 if ( statinfo.st_ino != inode )
242 std::cerr << "@@@ logfile: logrotate detected: " << name << "\n";
243 inode = statinfo.st_ino;
249 /* attempt to open the file */
250 fd = open(name, O_RDONLY);
253 std::cerr << "!!! logfile: open failed: " << name << ", " << strerror(errno) << "\n";
256 std::cerr << "*** logfile: opened: " << name;
257 std::cerr << "\n*** logfile: resumed read from position ";
258 std::cerr << (long) lseek(fd, position, SEEK_SET) << "\n";
259 std::cerr << "This logfile has " << filter.size() << " filters.\n";
265 void LogFile::copy_data()
270 /* read data and dump to output */
271 ndata = read(fd, buffer, sizeof(buffer));
277 // Make a separate <gcmt:raw> element from each line
279 char *line, *nextline;
284 while (nextline < buffer + ndata)
286 while (*nextline != '\n' && nextline < buffer + ndata)
290 if (*nextline == '\n')
292 // Another line found - make the split.
295 String logline(line);
297 // See if have to select the host and apply filters to this log entry
299 bool filtered_out = false;
301 if (fromhost && (fromhost.in(logline) == -1 || fromhost.in(logline) > 20))
306 std::list<regex>::iterator f = filter.begin();
307 while (f != filter.end())
318 logline = XML_Entities(logline);
320 write(out_stream, "<gcmt:raw>", 10);
321 write(out_stream, logline, ~logline);
322 write(out_stream, "</gcmt:raw>\n", 12);
326 std::cerr << logline << " is filtred out.\n";
332 if (line != nextline)
334 // There is still an incomplete line in the buffer.
335 memmove(buffer, line, nextline - line);
337 position += ndata - (nextline - line);
338 ndata -= line - buffer;
339 ndata += read(fd, buffer + (nextline - line), sizeof(buffer) - (nextline - line));
342 something_has_changed = 1;
346 std::cerr << "!!! logfile: read failed: " << name << ", " << strerror(errno) << "\n";
347 std::cerr << " file descriptor = " << fd << "\n";
351 void write_status_file()
356 std::ofstream statusfile(newstatusname);
357 std::list<LogFile>::iterator lf = logs_to_run.begin();
358 while (lf != logs_to_run.end())
360 std::cerr << "Write status for " << lf->pathname() << "\n";
361 statusfile << lf->status() << "\n";
365 if ( localerror == 0 )
367 if ( rename(newstatusname, statusname) )
369 output_error("!!! dumpstatus: rename failed");
372 something_has_changed = 0;
384 statusfile = fopen(statusname, "r");
385 if ( statusfile == NULL )
387 fprintf(stderr, "logrunner: can\'t open status file \'%s\': ",
392 while ( fgets(buffer, sizeof(buffer), statusfile ) )
394 xp = strchr(buffer, ' ');
398 sscanf(xp+1, "%ld %lu", &ino, &pos);
400 // Search for the logfile in the list of logs to run
402 std::list<LogFile>::iterator lf = logs_to_run.begin();
403 while (lf != logs_to_run.end())
405 if (lf->pathname() == String(buffer))
407 std::cerr << "Read status for " << lf->pathname() << "\n";
408 lf->update_status(ino, pos);
418 void read_config(gnucomo_config cfg)
421 * The configuration for logrunner is stored in the central Gnucomo
422 * configuration file. Multiple 'logfile' elements can be put in this
423 * XML file, one for each logfile to scan.
424 * Each 'logfile' element has at least a 'name' and a 'type' element
425 * that denote the pathname of the logfile and the type of its content.
434 logfilename = cfg.find_parameter("logfile", "name", l);
435 while (logfilename != String(""))
437 std::cerr << "Configuration for logfile " << logfilename << "\n";
438 logfiletype = cfg.find_parameter("logfile", "type", l);
439 fromhost = cfg.find_parameter("logfile", "fromhost", l);
440 std::cerr << "LogFile " << logfilename << " of type " << logfiletype << " from host " << fromhost << "\n";
442 LogFile lf(logfilename, logfiletype, fromhost);
445 String exp = cfg.find_parameter("logfile", "filter", l, f);
448 std::cerr << "Adding filter " << exp << "\n";
452 exp = cfg.find_parameter("logfile", "filter", l, f);
455 logs_to_run.push_back(lf);
456 std::cerr << "Logfile added to list.\n";
459 logfilename = cfg.find_parameter("logfile", "name", l);
460 std::cerr << "Next logfile = " << logfilename << "\n";
465 void process_options(int argc, char* argv[])
467 const char* const options = "1c:p:";
470 opt = getopt(argc, argv, options);
479 confname = strdup(optarg);
485 fputs(usage, stderr);
488 opt = getopt(argc, argv, options);
490 switch ( argc-optind )
495 hostname = argv[optind];
498 fputs(usage, stderr);
503 int main(int argc, char* argv[])
508 process_options(argc, argv);
511 if (!cfg.read(confname))
513 std::cerr << "Can not read Gnucomo configuration file for " << confname << ".\n";
520 set_signal_handler();
522 while ( sig_seen == 0 )
524 std::list<LogFile>::iterator lf = logs_to_run.begin();
525 while (lf != logs_to_run.end() && !(something_has_changed && oneshot))
527 std::cerr << "Scanning logfile " << lf->pathname() << "\n";
532 if ( something_has_changed )
543 fprintf(stderr, "logrunner: stopped by signal %d\n", sig_seen);
544 /* shouldn't we close files and release memory here? */