3 * (c) Peter Roozemaal, feb 2003
5 * $Id: logrunner.cpp,v 1.3 2007-05-06 08:35:16 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";
65 std::list<regex> filter;
70 LogFile(String n, String t)
79 LogFile(const LogFile &lf)
85 position = lf.position;
94 void update_status(ino_t i, off_t p)
102 return name + " " + String(inode) + " " + String(position);
105 void add_filter(String filter_expression)
107 filter.push_back(filter_expression);
108 std::cerr << "add_filter, filter size is " << filter.size() << "\n";
114 static std::list<LogFile> logs_to_run;
116 String confname = "gnucomo";
117 static const char* statusname = "/var/lib/logrunner.status";
118 static const char* newstatusname = "/var/lib/logrunner.status.new";
120 static const char *hostname = NULL;
121 static int port = 2996; /* random magic number */
122 static int out_stream = 1;
124 static int oneshot = 0;
126 static int something_has_changed = 0;
128 static volatile sig_atomic_t sig_seen = 0;
130 static void sighandler(int sig)
135 static void set_signal_handler(void)
137 /* These aren't all; but it's a nice set to start with */
138 signal(SIGHUP, sighandler);
139 signal(SIGINT, sighandler);
140 signal(SIGQUIT, sighandler);
141 signal(SIGABRT, sighandler);
142 signal(SIGPIPE, sighandler);
143 signal(SIGTERM, sighandler);
149 struct hostent* hostptr;
150 struct sockaddr_in addr;
151 unsigned int namelen = sizeof(addr);
155 hostptr = gethostbyname(hostname);
158 fprintf(stderr, "logrunner: FATAL: cannot resolve %s\n", hostname);
161 out_stream = socket(PF_INET, SOCK_STREAM, 0);
162 if ( out_stream < 0 )
164 fprintf(stderr, "logrunner: FATAL: Socket creation failed\n");
167 addr.sin_family = AF_INET;
168 addr.sin_addr.s_addr = *((long*)(hostptr->h_addr));
169 addr.sin_port = htons(port);
170 if ( connect(out_stream, (struct sockaddr*) &addr, namelen) < 0 )
172 fprintf(stderr, "logrunner: FATAL: connect to %s failed\n", hostname);
182 void xsend(const char* str)
184 write(out_stream, str, strlen(str));
187 void xml_header(String type)
191 gethostname(buffer, sizeof(buffer));
192 xsend("<?xml version='1.0'?>\n");
193 xsend("<gcmt:message xmlns:gcmt=\"http://gnucomo.org/transport/\">\n");
194 xsend("<gcmt:header>\n<gcmt:hostname>");
196 xsend("</gcmt:hostname>\n<gcmt:messagetype>");
198 xsend("</gcmt:messagetype>\n</gcmt:header>\n");
199 xsend("<gcmt:data><gcmt:log>");
202 void xml_footer(void)
204 xsend("</gcmt:log></gcmt:data>\n</gcmt:message>\n");
207 void output(const char* str)
212 void output_error(const char* str)
214 std::cerr << str << " errno=" << errno << "\n";
217 void LogFile::do_file()
219 struct stat statinfo;
223 if ( stat(name, &statinfo) )
225 std::cerr << "!!! logfile: stat failed: " << name << ", errno=" << errno << "\n";
229 if ( statinfo.st_ino != inode )
237 std::cerr << "@@@ logfile: logrotate detected: " << name << "\n";
238 inode = statinfo.st_ino;
244 /* attempt to open the file */
245 fd = open(name, O_RDONLY);
248 std::cerr << "!!! logfile: open failed: " << name << ", " << strerror(errno) << "\n";
251 std::cerr << "*** logfile: opened: " << name;
252 std::cerr << "\n*** logfile: resumed read from position ";
253 std::cerr << (long) lseek(fd, position, SEEK_SET) << "\n";
254 std::cerr << "This logfile has " << filter.size() << " filters.\n";
260 void LogFile::copy_data()
265 /* read data and dump to output */
266 ndata = read(fd, buffer, sizeof(buffer));
272 // Make a separate <gcmt:raw> element from each line
274 char *line, *nextline;
279 while (nextline < buffer + ndata)
281 while (*nextline != '\n' && nextline < buffer + ndata)
285 if (*nextline == '\n')
287 // Another line found - make the split.
290 String logline(line);
292 // Apply filters to this log entry
294 std::list<regex>::iterator f = filter.begin();
295 bool filtered_out = false;
296 while (f != filter.end())
307 logline = XML_Entities(logline);
309 write(out_stream, "<gcmt:raw>", 10);
310 write(out_stream, logline, ~logline);
311 write(out_stream, "</gcmt:raw>\n", 12);
315 std::cerr << logline << " is filtred out.\n";
321 if (line != nextline)
323 // There is still an incomplete line in the buffer.
324 memmove(buffer, line, nextline - line);
326 position += ndata - (nextline - line);
327 ndata -= line - buffer;
328 ndata += read(fd, buffer + (nextline - line), sizeof(buffer) - (nextline - line));
331 something_has_changed = 1;
335 std::cerr << "!!! logfile: read failed: " << name << ", " << strerror(errno) << "\n";
336 std::cerr << " file descriptor = " << fd << "\n";
340 void write_status_file()
345 std::ofstream statusfile(newstatusname);
346 std::list<LogFile>::iterator lf = logs_to_run.begin();
347 while (lf != logs_to_run.end())
349 std::cerr << "Write status for " << lf->pathname() << "\n";
350 statusfile << lf->status() << "\n";
354 if ( localerror == 0 )
356 if ( rename(newstatusname, statusname) )
358 output_error("!!! dumpstatus: rename failed");
361 something_has_changed = 0;
373 statusfile = fopen(statusname, "r");
374 if ( statusfile == NULL )
376 fprintf(stderr, "logrunner: can\'t open status file \'%s\': ",
381 while ( fgets(buffer, sizeof(buffer), statusfile ) )
383 xp = strchr(buffer, ' ');
387 sscanf(xp+1, "%ld %lu", &ino, &pos);
389 // Search for the logfile in the list of logs to run
391 std::list<LogFile>::iterator lf = logs_to_run.begin();
392 while (lf != logs_to_run.end())
394 if (lf->pathname() == String(buffer))
396 std::cerr << "Read status for " << lf->pathname() << "\n";
397 lf->update_status(ino, pos);
407 void read_config(gnucomo_config cfg)
410 * The configuration for logrunner is stored in the central Gnucomo
411 * configuration file. Multiple 'logfile' elements can be put in this
412 * XML file, one for each logfile to scan.
413 * Each 'logfile' element has at least a 'name' and a 'type' element
414 * that denote the pathname of the logfile and the type of its content.
422 logfilename = cfg.find_parameter("logfile", "name", l);
423 while (logfilename != String(""))
425 std::cerr << "Configuration for logfile " << logfilename << "\n";
426 logfiletype = cfg.find_parameter("logfile", "type", l);
427 std::cerr << "LogFile " << logfilename << " of type " << logfiletype << "\n";
429 LogFile lf(logfilename, logfiletype);
432 String exp = cfg.find_parameter("logfile", "filter", l, f);
435 std::cerr << "Adding filter " << exp << "\n";
439 exp = cfg.find_parameter("logfile", "filter", l, f);
442 logs_to_run.push_back(lf);
443 std::cerr << "Logfile added to list.\n";
446 logfilename = cfg.find_parameter("logfile", "name", l);
447 std::cerr << "Next logfile = " << logfilename << "\n";
452 void process_options(int argc, char* argv[])
454 const char* const options = "1c:p:";
457 opt = getopt(argc, argv, options);
466 confname = strdup(optarg);
472 fputs(usage, stderr);
475 opt = getopt(argc, argv, options);
477 switch ( argc-optind )
482 hostname = argv[optind];
485 fputs(usage, stderr);
490 int main(int argc, char* argv[])
495 process_options(argc, argv);
498 if (!cfg.read(confname))
500 std::cerr << "Can not read Gnucomo configuration file for " << confname << ".\n";
507 set_signal_handler();
509 while ( sig_seen == 0 )
511 std::list<LogFile>::iterator lf = logs_to_run.begin();
512 while (lf != logs_to_run.end() && !(something_has_changed && oneshot))
514 std::cerr << "Scanning logfile " << lf->pathname() << "\n";
519 if ( something_has_changed )
530 fprintf(stderr, "logrunner: stopped by signal %d\n", sig_seen);
531 /* shouldn't we close files and release memory here? */