Rewrote most functions and datastructures into C++
authorarjen <arjen>
Sun, 6 May 2007 08:35:16 +0000 (08:35 +0000)
committerarjen <arjen>
Sun, 6 May 2007 08:35:16 +0000 (08:35 +0000)
Use the Gnucomo (XML) configuration file

src/gcm_input/logrunner.cpp

index f4d0ecd..2e610dd 100644 (file)
@@ -2,16 +2,26 @@
  * logrunner.c
  * (c) Peter Roozemaal, feb 2003
  *
- * $Id: logrunner.cpp,v 1.2 2005-05-31 05:47:33 arjen Exp $
+ * $Id: logrunner.cpp,v 1.3 2007-05-06 08:35:16 arjen Exp $
  *
  * 1) compile,
- * 2) create 'logrunner.conf' containing a list of filenames and types of the
- *    logfiles to scan; one file per line:
- *       <gnucomo_messagetype> TAB <logfilename>
+ * 2) Add 'logfile' elements to the gnucomo configuration file
+ *    defining the name and type of each logfile to scan.
+ *    'filter' elements can be addes to pre-filter lines from the log.
+ *
  *    example:
- *      syslog   /var/log/messages
- *      syslog   /var/log/secure
- * 3) run executable
+ *       <logfile>
+ *          <name>/var/log/httpd/access_log</name>
+ *          <type>apache access log</type>
+ *       </logfile>
+ *       <logfile>
+ *          <name>/var/log/messages</name>
+ *          <type>system log</type>
+ *          <filter>open_scanner</filter>
+ *          <filter>session closed</filter>
+ *       </logfile>
+ *
+ * 3) run executable and feed the output to gcm_input
  */
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <stdlib.h>
 #include <string.h>
 
+#include <fstream>
+#include <list>
+
 #include <AXE/String.h>
 
+#include "gnucomo_config.h"
+
 extern String XML_Entities(String s);
 
 static const char* const usage =
@@ -40,16 +55,65 @@ static const char* const usage =
    " -c <file>: specify alternative configuration file, default is \'logrunner.conf\'\n"
    "If no hostname is specified, logrunner sends output to stdout\n";
 
-struct fileinfo {
-   struct fileinfo* next;
-   char*   name;      /* strdupped name */
-   char*   type;      /* strdupped logtype */
+class LogFile {
+   String   name;
+   String   type;
    int     fd;
    ino_t   inode;
    off_t   position;
+
+   std::list<regex> filter;
+
+   void copy_data();
+
+public:
+   LogFile(String n, String t)
+   {
+      name = n;
+      type = t;
+      inode     = 0;
+      position  = 0;
+      fd        = -1;
+   }
+
+   LogFile(const LogFile &lf)
+   {
+      name    = lf.name;
+      type    = lf.type;
+      fd      = lf.fd;
+      inode   = lf.inode;
+      position = lf.position;
+      filter   = lf.filter;
+   }
+
+   String pathname()
+   {
+      return name;
+   }
+
+   void update_status(ino_t i, off_t p)
+   {
+      inode     = i;
+      position  = p;
+   }
+
+   String status()
+   {
+      return name + " " + String(inode) + " " + String(position);
+   }
+
+   void add_filter(String filter_expression)
+   {
+      filter.push_back(filter_expression);
+      std::cerr << "add_filter, filter size is " << filter.size() << "\n";
+   }
+
+   void do_file();
 };
 
-String confname = "/etc/logrunner.conf";
+static std::list<LogFile>     logs_to_run;
+
+String confname = "gnucomo";
 static const char* statusname = "/var/lib/logrunner.status";
 static const char* newstatusname = "/var/lib/logrunner.status.new";
 
@@ -59,8 +123,6 @@ static int out_stream = 1;
 
 static int oneshot = 0;
 
-static struct fileinfo* filelist = NULL;
-
 static int something_has_changed = 0;
    
 static volatile sig_atomic_t sig_seen = 0;
@@ -81,6 +143,7 @@ static void set_signal_handler(void)
    signal(SIGTERM, sighandler);
 }
 
+
 void open_output()
 {
    struct hostent* hostptr;
@@ -121,7 +184,7 @@ void xsend(const char* str)
    write(out_stream, str, strlen(str));
 }
 
-void xml_header(struct fileinfo *f)
+void xml_header(String type)
 {
    char buffer[256];
    *buffer = 0;
@@ -131,7 +194,7 @@ void xml_header(struct fileinfo *f)
    xsend("<gcmt:header>\n<gcmt:hostname>");
    xsend(buffer);
    xsend("</gcmt:hostname>\n<gcmt:messagetype>");
-   xsend(f->type);
+   xsend(type);
    xsend("</gcmt:messagetype>\n</gcmt:header>\n");
    xsend("<gcmt:data><gcmt:log>");
 }
@@ -151,164 +214,59 @@ void output_error(const char* str)
    std::cerr << str << " errno=" << errno << "\n";
 }
 
-void write_status_file(struct fileinfo* fl)
-{
-   FILE* dumpfile;
-   int localerror = 0;
-   dumpfile = fopen(newstatusname, "w");
-   if ( !dumpfile )
-   {
-      char error[80];
-      sprintf(error, ", errno=%d\n", errno);
-      output("!!! dumpstatus: open failed: ");
-      output(newstatusname);
-      output(error);
-      something_has_changed = 0;
-      return;
-   }
-   while ( fl )
-   {
-      if ( fprintf(dumpfile, "%s %li %lu\n",
-              fl->name, (long) fl->inode,
-              (unsigned long) fl->position) < 0 )
-      {
-         output_error("!!! dumpstatus: write failed");
-         localerror = 1;
-         break;
-      }
-      fl = fl->next;
-   }
-   if ( fclose(dumpfile) && localerror == 0 )
-   {
-      output_error("!!! dumpstatus: close failed");
-      localerror = 1;
-   }
-   if ( localerror == 0 )
-   {
-      if ( rename(newstatusname, statusname) )
-      {
-         output_error("!!! dumpstatus: rename failed");
-      }
-   }
-   something_has_changed = 0;
-}
-
-void read_status()
+void LogFile::do_file()
 {
-   FILE* statusfile;
-   char buffer[4096];
-   char* xp;
-   long ino;
-   unsigned long pos;
-   struct fileinfo* fp;
+   struct stat statinfo;
+   char buffer[80];
 
-   statusfile = fopen(statusname, "r");
-   if ( statusfile == NULL )
+   /* inode check */
+   if ( stat(name, &statinfo) )
    {
-      fprintf(stderr, "logrunner: can\'t open status file \'%s\': ",
-         statusname);
-      perror("");
-      return;
+      std::cerr << "!!! logfile: stat failed: " << name << ", errno=" << errno << "\n";
    }
-   while ( fgets(buffer, sizeof(buffer), statusfile ) )
+   else
    {
-      xp = strchr(buffer, ' ');
-      if ( xp )
+      if ( statinfo.st_ino != inode )
       {
-         *xp = 0;
-         sscanf(xp+1, "%ld %lu", &ino, &pos);
-         fp = filelist;
-         while ( fp )
+         if ( fd >= 0 )
          {
-            if ( strcmp(buffer, fp->name) == 0 )
-            {
-               fp->inode = ino;
-               fp->position = pos;
-               break;
-            }
-            fp = fp->next;
+            copy_data();
+            close(fd);
+            fd = -1;
          }
+         std::cerr << "@@@ logfile: logrotate detected: " << name << "\n";
+         inode = statinfo.st_ino;
+         position = 0;
       }
    }
-   fclose(statusfile);
-}
-
-struct fileinfo* new_info()
-{
-   struct fileinfo* rv;
-   rv = (struct fileinfo *)malloc(sizeof(struct fileinfo));
-   if ( rv )
+   if ( fd < 0 )
    {
-      rv->next = NULL;
-      rv->name = NULL;
-      rv->type = NULL;
-      rv->fd = -1;
-      rv->position = 0;
-      rv->inode = 0;
-   }
-   return rv;
-}
-
-void read_config()
-{
-   /* The config file now is a simple list of type, filenames;
-    * it should be more user-friendly in the future!
-    */
-   FILE* conffile;
-   char buffer[4096];
-
-   conffile = fopen(confname, "r");
-   if ( !conffile )
-   {
-      std::cerr << "logrunner: can\'t open configuration file \'" << confname << "\': ";
-      perror("");
-      exit(2);
-   }
-   while ( fgets(buffer, sizeof(buffer), conffile) )
-   {
-      int len = strlen(buffer);
-      char *fname;
-      if ( buffer[len-1] == '\n' )
-      {
-         buffer[--len] = 0;
-      }
-      fname = strchr(buffer, '\t');
-      if ( fname )
-      {
-         struct fileinfo* fi;
-         fi = new_info();
-         *(fname++) = 0;
-         if ( fi )
-         {
-            fi->name = strdup(fname);
-            fi->type = strdup(buffer);
-            fi->next = filelist;
-            filelist = fi;
-         }
-         else
-         {
-            fprintf(stderr, "logrunner: out of memory\n");
-            exit(2);
-         }
-      }
-      else if ( len > 0 )
+      /* attempt to open the file */
+      fd = open(name, O_RDONLY);
+      if ( fd < 0 )
       {
-         fprintf(stderr, "logrunner: WARNING: bad config line: %s\n", buffer);
+         std::cerr << "!!! logfile: open failed: " << name << ", " << strerror(errno) << "\n";
+         return;
       }
+      std::cerr << "*** logfile: opened: " << name;
+      std::cerr << "\n*** logfile: resumed read from position ";
+      std::cerr << (long) lseek(fd, position, SEEK_SET)  << "\n";
+      std::cerr << "This logfile has " << filter.size() << " filters.\n";
    }
-   fclose(conffile);
+
+   copy_data();
 }
 
-void copy_data(struct fileinfo *f)
+void LogFile::copy_data()
 {
    char buffer[4096];
    int ndata;
 
    /* read data and dump to output */
-   ndata = read(f->fd, buffer, sizeof(buffer));
+   ndata = read(fd, buffer, sizeof(buffer));
    if ( ndata > 0 )
    {
-      xml_header(f);
+      xml_header(type);
       while ( ndata > 0 )
       {
          //  Make a separate <gcmt:raw> element from each line
@@ -328,12 +286,35 @@ void copy_data(struct fileinfo *f)
             {
                // Another line found - make the split.
                *nextline++ = '\0';
-               write(out_stream, "<gcmt:raw>", 10);
 
                String logline(line);
-               logline = XML_Entities(logline);
-               write(out_stream, logline, ~logline);
-               write(out_stream, "</gcmt:raw>\n", 12);
+
+               // Apply filters to this log entry
+
+               std::list<regex>::iterator f = filter.begin();
+               bool filtered_out = false;
+               while (f != filter.end())
+               {
+                  if (logline == *f)
+                  {
+                     filtered_out = true;
+                  }
+                  f++;
+               }
+
+               if (!filtered_out)
+               {
+                  logline = XML_Entities(logline);
+
+                  write(out_stream, "<gcmt:raw>", 10);
+                  write(out_stream, logline, ~logline);
+                  write(out_stream, "</gcmt:raw>\n", 12);
+               }
+               else
+               {
+                  std::cerr << logline << " is filtred out.\n";
+               }
+
                line = nextline;
             }
          }
@@ -342,74 +323,130 @@ void copy_data(struct fileinfo *f)
             //  There is still an incomplete line in the buffer.
             memmove(buffer, line, nextline - line);
          }
-         f->position += ndata - (nextline - line);
+         position += ndata - (nextline - line);
          ndata -= line - buffer;
-         ndata += read(f->fd, buffer + (nextline - line), sizeof(buffer) - (nextline - line));
+         ndata += read(fd, buffer + (nextline - line), sizeof(buffer) - (nextline - line));
       }
       xml_footer();
       something_has_changed = 1;
    }   
    if ( ndata < 0 )
    {
-      char error[80];
-      sprintf(error, ", errno=%d\n", errno);
-      output("!!! logfile: read failed: ");
-      output(f->name);
-      output(error);
+      std::cerr << "!!! logfile: read failed: " << name << ", " << strerror(errno) << "\n";
+      std::cerr << "    file descriptor = " << fd << "\n";
    }
 }
 
-void do_file(struct fileinfo *f)
+void write_status_file()
 {
-   struct stat statinfo;
-   char buffer[80];
+   FILE* dumpfile;
+   int localerror = 0;
 
-   /* inode check */
-   if ( stat(f->name, &statinfo) )
+   std::ofstream   statusfile(newstatusname);
+   std::list<LogFile>::iterator lf = logs_to_run.begin();
+   while (lf != logs_to_run.end())
    {
-      sprintf(buffer, ", errno=%d\n", errno);
-      output("!!! logfile: stat failed: ");
-      output(f->name);
-      output(buffer);
+      std::cerr  << "Write status for " << lf->pathname() << "\n";
+      statusfile << lf->status() << "\n";
+      lf++;
    }
-   else
+   
+   if ( localerror == 0 )
    {
-      if ( statinfo.st_ino != f->inode )
+      if ( rename(newstatusname, statusname) )
       {
-         if ( f->fd >= 0 )
+         output_error("!!! dumpstatus: rename failed");
+      }
+   }
+   something_has_changed = 0;
+}
+
+void read_status()
+{
+   FILE* statusfile;
+   char buffer[4096];
+   char* xp;
+   long ino;
+   unsigned long pos;
+   struct fileinfo* fp;
+
+   statusfile = fopen(statusname, "r");
+   if ( statusfile == NULL )
+   {
+      fprintf(stderr, "logrunner: can\'t open status file \'%s\': ",
+         statusname);
+      perror("");
+      return;
+   }
+   while ( fgets(buffer, sizeof(buffer), statusfile ) )
+   {
+      xp = strchr(buffer, ' ');
+      if ( xp )
+      {
+         *xp = 0;
+         sscanf(xp+1, "%ld %lu", &ino, &pos);
+
+         //  Search for the logfile in the list of logs to run
+
+         std::list<LogFile>::iterator lf = logs_to_run.begin();
+         while (lf != logs_to_run.end())
          {
-            copy_data(f);
-            close(f->fd);
-            f->fd = -1;
+            if (lf->pathname() == String(buffer))
+            {
+               std::cerr << "Read status for " << lf->pathname() << "\n";
+               lf->update_status(ino, pos);
+            }
+            lf++;
          }
-         output("@@@ logfile: logrotate detected: ");
-         output(f->name);
-         output("\n");
-         f->inode = statinfo.st_ino;
-         f->position = 0;
+
       }
    }
-   if ( f->fd < 0 )
+   fclose(statusfile);
+}
+
+void read_config(gnucomo_config cfg)
+{
+   /*
+    *   The configuration for logrunner is stored in the central Gnucomo
+    *   configuration file. Multiple 'logfile' elements can be put in this
+    *   XML file, one for each logfile to scan.
+    *   Each 'logfile' element has at least a 'name' and a 'type' element
+    *   that denote the pathname of the logfile and the type of its content.
+    */
+
+   String logfilename;
+   String logfiletype;
+
+   int l = 0;
+
+   logfilename = cfg.find_parameter("logfile", "name", l);
+   while (logfilename != String(""))
    {
-      /* attempt to open the file */
-      f->fd = open(f->name, O_RDONLY);
-      if ( f->fd < 0 )
+      std::cerr << "Configuration for logfile " << logfilename << "\n";
+      logfiletype = cfg.find_parameter("logfile", "type", l);
+      std::cerr << "LogFile " << logfilename << " of type " << logfiletype << "\n";
+
+      LogFile lf(logfilename, logfiletype);
+
+      int f = 0;
+      String exp  = cfg.find_parameter("logfile", "filter", l, f);
+      while (exp != "")
       {
-         sprintf(buffer, ", errno=%d\n", errno);
-         output("!!! logfile: open failed: ");
-         output(f->name);
-         output(buffer);
-         return;
+         std::cerr << "Adding filter " << exp << "\n";
+         lf.add_filter(exp);
+
+         f++;
+         exp = cfg.find_parameter("logfile", "filter", l, f);
       }
-      output("*** logfile: opened: ");
-      output(f->name);
-      sprintf(buffer,
-         "\n*** logfile: resumed read from position %ld\n",
-         (long) lseek(f->fd, f->position, SEEK_SET) );
-      output(buffer);
+
+      logs_to_run.push_back(lf);
+      std::cerr << "Logfile added to list.\n";
+
+      l++;
+      logfilename = cfg.find_parameter("logfile", "name", l);
+      std::cerr << "Next logfile = " << logfilename << "\n";
    }
 
-   copy_data(f);
 }
 
 void process_options(int argc, char* argv[])
@@ -452,30 +489,36 @@ void process_options(int argc, char* argv[])
 
 int main(int argc, char* argv[])
 {
+
+   gnucomo_config    cfg;
+
    process_options(argc, argv);
    open_output();
    
-   read_config();
+   if (!cfg.read(confname))
+   {
+      std::cerr << "Can not read Gnucomo configuration file for " << confname << ".\n";
+      exit(1);
+   }
+
+   read_config(cfg);
    read_status();
 
    set_signal_handler();
 
    while ( sig_seen == 0 )
    {
-      struct fileinfo* fip;
-      fip = filelist;
-      while ( fip )
+      std::list<LogFile>::iterator lf = logs_to_run.begin();
+      while (lf != logs_to_run.end() && !(something_has_changed && oneshot))
       {
-         do_file(fip);
-         if (something_has_changed && oneshot)
-         {
-            break;
-         }
-         fip = fip->next;
+         std::cerr  << "Scanning logfile " << lf->pathname() << "\n";
+         lf->do_file();
+         lf++;
       }
+
       if ( something_has_changed )
       {
-         write_status_file(filelist);
+         write_status_file();
       }
       if ( oneshot )
       {