Rewrote most functions and datastructures into C++
[gnucomo.git] / src / gcm_input / logrunner.cpp
1 /*
2  * logrunner.c
3  * (c) Peter Roozemaal, feb 2003
4  *
5  * $Id: logrunner.cpp,v 1.3 2007-05-06 08:35:16 arjen Exp $
6  *
7  * 1) compile,
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.
11  *
12  *    example:
13  *       <logfile>
14  *          <name>/var/log/httpd/access_log</name>
15  *          <type>apache access log</type>
16  *       </logfile>
17  *       <logfile>
18  *          <name>/var/log/messages</name>
19  *          <type>system log</type>
20  *          <filter>open_scanner</filter>
21  *          <filter>session closed</filter>
22  *       </logfile>
23  *
24  * 3) run executable and feed the output to gcm_input
25  */
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <unistd.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <netdb.h>
35 #include <stdio.h>
36 #include <signal.h>
37 #include <stdlib.h>
38 #include <string.h>
39
40 #include <fstream>
41 #include <list>
42
43 #include <AXE/String.h>
44
45 #include "gnucomo_config.h"
46
47 extern String XML_Entities(String s);
48
49 static const char* const usage =
50    "Usage: logrunner [<options>] [<hostname>]\n\n"
51    "<hostname> is the gnucomo data collection server\n"
52    "Options are:\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";
57
58 class LogFile {
59    String   name;
60    String   type;
61    int     fd;
62    ino_t   inode;
63    off_t   position;
64
65    std::list<regex> filter;
66
67    void copy_data();
68
69 public:
70    LogFile(String n, String t)
71    {
72       name = n;
73       type = t;
74       inode     = 0;
75       position  = 0;
76       fd        = -1;
77    }
78
79    LogFile(const LogFile &lf)
80    {
81       name    = lf.name;
82       type    = lf.type;
83       fd      = lf.fd;
84       inode   = lf.inode;
85       position = lf.position;
86       filter   = lf.filter;
87    }
88
89    String pathname()
90    {
91       return name;
92    }
93
94    void update_status(ino_t i, off_t p)
95    {
96       inode     = i;
97       position  = p;
98    }
99
100    String status()
101    {
102       return name + " " + String(inode) + " " + String(position);
103    }
104
105    void add_filter(String filter_expression)
106    {
107       filter.push_back(filter_expression);
108       std::cerr << "add_filter, filter size is " << filter.size() << "\n";
109    }
110
111    void do_file();
112 };
113
114 static std::list<LogFile>     logs_to_run;
115
116 String confname = "gnucomo";
117 static const char* statusname = "/var/lib/logrunner.status";
118 static const char* newstatusname = "/var/lib/logrunner.status.new";
119
120 static const char *hostname = NULL;
121 static int port = 2996;         /* random magic number */
122 static int out_stream = 1;
123
124 static int oneshot = 0;
125
126 static int something_has_changed = 0;
127    
128 static volatile sig_atomic_t sig_seen = 0;
129
130 static void sighandler(int sig)
131 {
132    sig_seen = sig;
133 }
134
135 static void set_signal_handler(void)
136 {
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);
144 }
145
146
147 void open_output()
148 {
149    struct hostent* hostptr;
150    struct sockaddr_in addr;
151    unsigned int namelen = sizeof(addr);
152
153    if ( hostname )
154    {
155       hostptr = gethostbyname(hostname);
156       if ( !hostptr )
157       {
158          fprintf(stderr, "logrunner: FATAL: cannot resolve %s\n", hostname);
159          exit(2);
160       }
161       out_stream = socket(PF_INET, SOCK_STREAM, 0);
162       if ( out_stream < 0 )
163       {
164          fprintf(stderr, "logrunner: FATAL: Socket creation failed\n");
165          exit(2);
166       }
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 )
171       {
172          fprintf(stderr, "logrunner: FATAL: connect to %s failed\n", hostname);
173          exit(2);
174       }
175    }
176    else
177    {
178       out_stream = 1;
179    }
180 }
181
182 void xsend(const char* str)
183 {
184    write(out_stream, str, strlen(str));
185 }
186
187 void xml_header(String type)
188 {
189    char buffer[256];
190    *buffer = 0;
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>");
195    xsend(buffer);
196    xsend("</gcmt:hostname>\n<gcmt:messagetype>");
197    xsend(type);
198    xsend("</gcmt:messagetype>\n</gcmt:header>\n");
199    xsend("<gcmt:data><gcmt:log>");
200 }
201
202 void xml_footer(void)
203 {
204    xsend("</gcmt:log></gcmt:data>\n</gcmt:message>\n");
205 }
206
207 void output(const char* str)
208 {
209    std::cerr << str;
210 }
211
212 void output_error(const char* str)
213 {
214    std::cerr << str << " errno=" << errno << "\n";
215 }
216
217 void LogFile::do_file()
218 {
219    struct stat statinfo;
220    char buffer[80];
221
222    /* inode check */
223    if ( stat(name, &statinfo) )
224    {
225       std::cerr << "!!! logfile: stat failed: " << name << ", errno=" << errno << "\n";
226    }
227    else
228    {
229       if ( statinfo.st_ino != inode )
230       {
231          if ( fd >= 0 )
232          {
233             copy_data();
234             close(fd);
235             fd = -1;
236          }
237          std::cerr << "@@@ logfile: logrotate detected: " << name << "\n";
238          inode = statinfo.st_ino;
239          position = 0;
240       }
241    }
242    if ( fd < 0 )
243    {
244       /* attempt to open the file */
245       fd = open(name, O_RDONLY);
246       if ( fd < 0 )
247       {
248          std::cerr << "!!! logfile: open failed: " << name << ", " << strerror(errno) << "\n";
249          return;
250       }
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";
255    }
256
257    copy_data();
258 }
259
260 void LogFile::copy_data()
261 {
262    char buffer[4096];
263    int ndata;
264
265    /* read data and dump to output */
266    ndata = read(fd, buffer, sizeof(buffer));
267    if ( ndata > 0 )
268    {
269       xml_header(type);
270       while ( ndata > 0 )
271       {
272          //  Make a separate <gcmt:raw> element from each line
273  
274          char *line, *nextline;
275
276          line = buffer;
277          nextline = buffer;
278
279          while (nextline < buffer + ndata)
280          {
281             while (*nextline != '\n' && nextline < buffer + ndata)
282             {
283                nextline++;
284             }
285             if (*nextline == '\n')
286             {
287                // Another line found - make the split.
288                *nextline++ = '\0';
289
290                String logline(line);
291
292                // Apply filters to this log entry
293
294                std::list<regex>::iterator f = filter.begin();
295                bool filtered_out = false;
296                while (f != filter.end())
297                {
298                   if (logline == *f)
299                   {
300                      filtered_out = true;
301                   }
302                   f++;
303                }
304
305                if (!filtered_out)
306                {
307                   logline = XML_Entities(logline);
308
309                   write(out_stream, "<gcmt:raw>", 10);
310                   write(out_stream, logline, ~logline);
311                   write(out_stream, "</gcmt:raw>\n", 12);
312                }
313                else
314                {
315                   std::cerr << logline << " is filtred out.\n";
316                }
317
318                line = nextline;
319             }
320          }
321          if (line != nextline)
322          {
323             //  There is still an incomplete line in the buffer.
324             memmove(buffer, line, nextline - line);
325          }
326          position += ndata - (nextline - line);
327          ndata -= line - buffer;
328          ndata += read(fd, buffer + (nextline - line), sizeof(buffer) - (nextline - line));
329       }
330       xml_footer();
331       something_has_changed = 1;
332    }   
333    if ( ndata < 0 )
334    {
335       std::cerr << "!!! logfile: read failed: " << name << ", " << strerror(errno) << "\n";
336       std::cerr << "    file descriptor = " << fd << "\n";
337    }
338 }
339
340 void write_status_file()
341 {
342    FILE* dumpfile;
343    int localerror = 0;
344
345    std::ofstream   statusfile(newstatusname);
346    std::list<LogFile>::iterator lf = logs_to_run.begin();
347    while (lf != logs_to_run.end())
348    {
349       std::cerr  << "Write status for " << lf->pathname() << "\n";
350       statusfile << lf->status() << "\n";
351       lf++;
352    }
353    
354    if ( localerror == 0 )
355    {
356       if ( rename(newstatusname, statusname) )
357       {
358          output_error("!!! dumpstatus: rename failed");
359       }
360    }
361    something_has_changed = 0;
362 }
363
364 void read_status()
365 {
366    FILE* statusfile;
367    char buffer[4096];
368    char* xp;
369    long ino;
370    unsigned long pos;
371    struct fileinfo* fp;
372
373    statusfile = fopen(statusname, "r");
374    if ( statusfile == NULL )
375    {
376       fprintf(stderr, "logrunner: can\'t open status file \'%s\': ",
377          statusname);
378       perror("");
379       return;
380    }
381    while ( fgets(buffer, sizeof(buffer), statusfile ) )
382    {
383       xp = strchr(buffer, ' ');
384       if ( xp )
385       {
386          *xp = 0;
387          sscanf(xp+1, "%ld %lu", &ino, &pos);
388
389          //  Search for the logfile in the list of logs to run
390
391          std::list<LogFile>::iterator lf = logs_to_run.begin();
392          while (lf != logs_to_run.end())
393          {
394             if (lf->pathname() == String(buffer))
395             {
396                std::cerr << "Read status for " << lf->pathname() << "\n";
397                lf->update_status(ino, pos);
398             }
399             lf++;
400          }
401
402       }
403    }
404    fclose(statusfile);
405 }
406
407 void read_config(gnucomo_config cfg)
408 {
409    /*
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.
415     */
416
417    String logfilename;
418    String logfiletype;
419
420    int l = 0;
421
422    logfilename = cfg.find_parameter("logfile", "name", l);
423    while (logfilename != String(""))
424    {
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";
428
429       LogFile lf(logfilename, logfiletype);
430
431       int f = 0;
432       String exp  = cfg.find_parameter("logfile", "filter", l, f);
433       while (exp != "")
434       {
435          std::cerr << "Adding filter " << exp << "\n";
436          lf.add_filter(exp);
437
438          f++;
439          exp = cfg.find_parameter("logfile", "filter", l, f);
440       }
441
442       logs_to_run.push_back(lf);
443       std::cerr << "Logfile added to list.\n";
444
445       l++;
446       logfilename = cfg.find_parameter("logfile", "name", l);
447       std::cerr << "Next logfile = " << logfilename << "\n";
448    }
449
450 }
451
452 void process_options(int argc, char* argv[])
453 {
454    const char* const options = "1c:p:";
455    int opt;
456
457    opt = getopt(argc, argv, options);
458    while ( opt != -1 )
459    {
460       switch ( opt )
461       {
462       case '1':
463          oneshot = 1;
464          break;
465       case 'c':
466          confname = strdup(optarg);
467          break;
468       case 'p':
469          port = atoi(optarg);
470          break;
471       default:
472          fputs(usage, stderr);
473          exit(2);
474       }
475       opt = getopt(argc, argv, options);
476    }
477    switch ( argc-optind )
478    {
479    case 0:
480       break;
481    case 1:
482       hostname = argv[optind];
483       break;
484    default:
485       fputs(usage, stderr);
486       exit(2);
487    }
488 }
489
490 int main(int argc, char* argv[])
491 {
492
493    gnucomo_config    cfg;
494
495    process_options(argc, argv);
496    open_output();
497    
498    if (!cfg.read(confname))
499    {
500       std::cerr << "Can not read Gnucomo configuration file for " << confname << ".\n";
501       exit(1);
502    }
503
504    read_config(cfg);
505    read_status();
506
507    set_signal_handler();
508
509    while ( sig_seen == 0 )
510    {
511       std::list<LogFile>::iterator lf = logs_to_run.begin();
512       while (lf != logs_to_run.end() && !(something_has_changed && oneshot))
513       {
514          std::cerr  << "Scanning logfile " << lf->pathname() << "\n";
515          lf->do_file();
516          lf++;
517       }
518
519       if ( something_has_changed )
520       {
521          write_status_file();
522       }
523       if ( oneshot )
524       {
525          return 0;
526       }
527       sleep(1);
528    }
529
530    fprintf(stderr, "logrunner: stopped by signal %d\n", sig_seen);
531    /* shouldn't we close files and release memory here? */
532    return 0;
533 }