]> git.ipfire.org Git - thirdparty/sarg.git/commitdiff
Get rid of the dependency to an external tail command
authorFrederic Marchal <fmarchal@users.sourceforge.net>
Thu, 19 Mar 2015 19:56:30 +0000 (20:56 +0100)
committerFrederic Marchal <fmarchal@users.sourceforge.net>
Thu, 19 Mar 2015 19:56:30 +0000 (20:56 +0100)
The real time report relied on an external "tail" command to get the last
lines of the input log file. That dependency has been removed.

Moreover, if several log files were passed, the real time report would
always read the first one. Now, sarg chooses the most recently modified log
file.

The input log file can be in any supported format. It doesn't have to be
a standard squid log format.

include/readlog.h
log.c
readlog.c
readlog_squid.c
realtime.c

index 425a5c9f21410fd55c6de39af750aad5155879cc..a481362292df1dce3eb1290fd7c7e7674214e05d 100644 (file)
@@ -41,6 +41,8 @@ struct ReadLogStruct
        long long int DataSize;
        //! HTTP code returned to the user for the entry.
        char *HttpCode;
+       //! HTTP method or NULL if the information is not stored in the log.
+       char *HttpMethod;
 };
 
 /*!
@@ -56,4 +58,23 @@ struct ReadLogProcessStruct
        enum ReadLogReturnCodeEnum (*ReadEntry)(char *Line,struct ReadLogStruct *Entry);
 };
 
+/*!
+ * \brief Persistant data to parse a log line.
+ */
+struct LogLineStruct
+{
+       const struct ReadLogProcessStruct *current_format;
+       int current_format_idx;
+       int successive_errors;
+       int total_errors;
+       const char *file_name;
+};
+
+//! Opaque object used to parse a log line.
+typedef struct LogLineStruct *LogLineObject;
+
+void LogLine_Init(struct LogLineStruct *log_line);
+void LogLine_File(struct LogLineStruct *log_line,const char *file_name);
+enum ReadLogReturnCodeEnum LogLine_Parse(struct LogLineStruct *log_line,struct ReadLogStruct *log_entry,char *linebuf);
+
 #endif //READLOG_HEADER
diff --git a/log.c b/log.c
index 82ee9998728bcc4f3ad4b7489132b2a8f32693a7..57cbc7e38a3bb0b3a0e2bafdd46370c37158b504 100644 (file)
--- a/log.c
+++ b/log.c
@@ -458,11 +458,6 @@ int main(int argc,char *argv[])
        if(outdir[0] == '\0') strcpy(outdir,OutputDir);
        if(outdir[0] != '\0') strcat(outdir,"/");
 
-       if(realt) {
-               realtime();
-               exit(EXIT_SUCCESS);
-       }
-
        if(IndexTree == INDEX_TREE_FILE)
                strcpy(ImageFile,"../images");
        else
@@ -483,6 +478,10 @@ int main(int argc,char *argv[])
                }
        }
 
+       if(realt) {
+               realtime();
+               exit(EXIT_SUCCESS);
+       }
        if(split) {
                const char *file;
 
index de6199d2edbb3b4d41b113cb6acd4a6c1d16a088..514ad07855e0bacd0814cdf9d169e0646ad7894b 100644 (file)
--- a/readlog.c
+++ b/readlog.c
@@ -135,6 +135,101 @@ static int LatestDate=-1;
 //! The latest date in time format.
 static struct tm LatestDateTime;
 
+/*!
+ * Initialize the memory structure needed by LogLine_Parse() to parse
+ * a log line.
+ *
+ * \param log_line The structure to initialize.
+ */
+void LogLine_Init(struct LogLineStruct *log_line)
+{
+       log_line->current_format=NULL;
+       log_line->current_format_idx=-1;
+       log_line->file_name="";
+       log_line->successive_errors=0;
+       log_line->total_errors=0;
+}
+
+/*!
+ * Set the name of the log file being parsed.
+ *
+ * \param log_line Data structure to parse the log line.
+ * \param file_name The name of the log file being read.
+ */
+void LogLine_File(struct LogLineStruct *log_line,const char *file_name)
+{
+       log_line->file_name=file_name;
+}
+
+/*!
+ * Parse the next line from a log file.
+ *
+ * \param log_line A buffer to store the data about the current parsing.
+ * \param log_entry The variable to store the parsed data.
+ * \param linebuf The text line read from the log file.
+ *
+ * \return
+ */
+enum ReadLogReturnCodeEnum LogLine_Parse(struct LogLineStruct *log_line,struct ReadLogStruct *log_entry,char *linebuf)
+{
+       enum ReadLogReturnCodeEnum log_entry_status=RLRC_Unknown;
+       int x;
+
+       if (log_line->current_format)
+       {
+               memset(log_entry,0,sizeof(*log_entry));
+               log_entry_status=log_line->current_format->ReadEntry(linebuf,log_entry);
+       }
+
+       // find out what line format to use
+       if (log_entry_status==RLRC_Unknown)
+       {
+               for (x=0 ; x<(int)(sizeof(LogFormats)/sizeof(*LogFormats)) ; x++)
+               {
+                       if (LogFormats[x]==log_line->current_format) continue;
+                       memset(log_entry,0,sizeof(*log_entry));
+                       log_entry_status=LogFormats[x]->ReadEntry(linebuf,log_entry);
+                       if (log_entry_status!=RLRC_Unknown)
+                       {
+                               log_line->current_format=LogFormats[x];
+                               log_line->current_format_idx=x;
+                               if (debugz>=LogLevel_Process)
+                               {
+                                       /* TRANSLATORS: The argument is the log format name as translated by you. */
+                                       debuga(_("Log format identified as \"%s\" for %s\n"),_(log_line->current_format->Name),log_line->file_name);
+                               }
+                               break;
+                       }
+               }
+               if (x>=(int)(sizeof(LogFormats)/sizeof(*LogFormats)))
+               {
+                       if (++log_line->successive_errors>NumLogSuccessiveErrors) {
+                               debuga(ngettext("%d consecutive error found in the input log file %s\n",
+                                                                                               "%d consecutive errors found in the input log file %s\n",log_line->successive_errors),log_line->successive_errors,log_line->file_name);
+                               exit(EXIT_FAILURE);
+                       }
+                       if (NumLogTotalErrors>=0 && ++log_line->total_errors>NumLogTotalErrors) {
+                               debuga(ngettext("%d error found in the input log file (last in %s)\n",
+                                                                                               "%d errors found in the input log file (last in %s)\n",log_line->total_errors),log_line->total_errors,log_line->file_name);
+                               exit(EXIT_FAILURE);
+                       }
+                       debuga(_("The following line read from %s could not be parsed and is ignored\n%s\n"),log_line->file_name,linebuf);
+               }
+               else
+                       log_line->successive_errors=0;
+       }
+
+       if (log_line->current_format_idx<0 || log_line->current_format==NULL) {
+               debuga(_("Sarg failed to determine the format of the input log file %s\n"),log_line->file_name);
+               exit(EXIT_FAILURE);
+       }
+       if (log_entry_status==RLRC_InternalError) {
+               debuga(_("Internal error encountered while processing %s\nSee previous message to know the reason for that error.\n"),log_line->file_name);
+               exit(EXIT_FAILURE);
+       }
+       return(log_entry_status);
+}
+
 /*!
 Read a single log file.
 
@@ -153,15 +248,12 @@ static void ReadOneLogFile(struct ReadLogDataStruct *Filter,const char *arq)
        char smartfilter[MAXLEN];
        const char *url;
        const char *user;
-       int current_format_idx;
        int OutputNonZero = REPORT_EVERY_X_LINES ;
        int idata=0;
        int x;
        int hmr;
        int nopen;
        int maxopenfiles=MAX_OPEN_USER_FILES;
-       int successive_errors=0;
-       int total_errors=0;
        unsigned long int recs1=0UL;
        unsigned long int recs2=0UL;
        FILE *fp_in=NULL;
@@ -177,10 +269,10 @@ static void ReadOneLogFile(struct ReadLogDataStruct *Filter,const char *arq)
        struct userfilestruct *ufile;
        struct userfilestruct *ufile1;
        struct ReadLogStruct log_entry;
-       const struct ReadLogProcessStruct *current_format=NULL;
+       struct LogLineStruct log_line;
 
-       current_format=NULL;
-       current_format_idx=-1;
+       LogLine_Init(&log_line);
+       LogLine_File(&log_line,arq);
        for (x=0 ; x<sizeof(LogFormats)/sizeof(*LogFormats) ; x++)
                if (LogFormats[x]->NewFile)
                        LogFormats[x]->NewFile(arq);
@@ -303,58 +395,19 @@ static void ReadOneLogFile(struct ReadLogDataStruct *Filter,const char *arq)
                        printf("BUF=%s\n",linebuf);
 
                // process the line
-               log_entry_status=RLRC_Unknown;
-               memset(&log_entry,0,sizeof(log_entry));
-               if (current_format) {
-                       log_entry_status=current_format->ReadEntry(linebuf,&log_entry);
-               }
-
-               // find out what line format to use
-               if (log_entry_status==RLRC_Unknown) {
-                       for (x=0 ; x<(int)(sizeof(LogFormats)/sizeof(*LogFormats)) ; x++) {
-                               if (LogFormats[x]==current_format) continue;
-                               memset(&log_entry,0,sizeof(log_entry));
-                               log_entry_status=LogFormats[x]->ReadEntry(linebuf,&log_entry);
-                               if (log_entry_status!=RLRC_Unknown) break;
-                       }
-                       if (x>=(int)(sizeof(LogFormats)/sizeof(*LogFormats))) {
-                               if (++successive_errors>NumLogSuccessiveErrors) {
-                                       debuga(ngettext("%d consecutive error found in the input log file %s\n",
-                                                                                                       "%d consecutive errors found in the input log file %s\n",successive_errors),successive_errors,arq);
-                                       exit(EXIT_FAILURE);
-                               }
-                               if (NumLogTotalErrors>=0 && ++total_errors>NumLogTotalErrors) {
-                                       debuga(ngettext("%d error found in the input log file (last in %s)\n",
-                                                                                                       "%d errors found in the input log file (last in %s)\n",total_errors),total_errors,arq);
-                                       exit(EXIT_FAILURE);
-                               }
-                               debuga(_("The following line read from %s could not be parsed and is ignored\n%s\n"),arq,linebuf);
-                               excluded_count[ER_UnknownFormat]++;
-                               continue;
-                       }
-                       current_format=LogFormats[x];
-                       current_format_idx=x;
-                       if (debugz>=LogLevel_Process) {
-                               /* TRANSLATORS: The argument is the log format name as translated by you. */
-                               debuga(_("Log format identified as \"%s\" for %s\n"),_(current_format->Name),arq);
-                       }
-                       successive_errors=0;
+               log_entry_status=LogLine_Parse(&log_line,&log_entry,linebuf);
+               if (log_entry_status==RLRC_Unknown)
+               {
+                       excluded_count[ER_UnknownFormat]++;
+                       continue;
                }
                if (log_entry_status==RLRC_Ignore) {
                        excluded_count[ER_FormatData]++;
                        continue;
                }
-               if (current_format_idx<0 || current_format==NULL) {
-                       debuga(_("Sarg failed to determine the format of the input log file %s\n"),arq);
-                       exit(EXIT_FAILURE);
-               }
-               if (log_entry_status==RLRC_InternalError) {
-                       debuga(_("Internal error encountered while processing %s\nSee previous message to know the reason for that error.\n"),arq);
-                       exit(EXIT_FAILURE);
-               }
-               format_count[current_format_idx]++;
+               format_count[log_line.current_format_idx]++;
 
-               if (!fp_log && ParsedOutputLog[0] && current_format!=&ReadSargLog) {
+               if (!fp_log && ParsedOutputLog[0] && log_line.current_format!=&ReadSargLog) {
                        if(access(ParsedOutputLog,R_OK) != 0) {
                                my_mkdir(ParsedOutputLog);
                        }
@@ -447,7 +500,7 @@ static void ReadOneLogFile(struct ReadLogDataStruct *Filter,const char *arq)
                for (str=log_entry.HttpCode ; *str ; str++)
                        if (*str=='\t') *str=' ';
 
-               if (current_format!=&ReadSargLog) {
+               if (log_line.current_format!=&ReadSargLog) {
                        /*
                        The full URL is not saved in sarg log. There is no point in testing the URL to detect
                        a downloaded file.
@@ -640,7 +693,7 @@ static void ReadOneLogFile(struct ReadLogDataStruct *Filter,const char *arq)
                }
                records_kept++;
 
-               if (fp_log && current_format!=&ReadSargLog) {
+               if (fp_log && log_line.current_format!=&ReadSargLog) {
                        fprintf(fp_log, "%s\t%s\t%s\t%s\t%s\t%"PRIu64"\t%s\t%ld\t%s\n",dia,hora,
                                                        log_entry.User,log_entry.Ip,url,(uint64_t)log_entry.DataSize,
                                                        log_entry.HttpCode,log_entry.ElapsedTime,smartfilter);
@@ -652,7 +705,7 @@ static void ReadOneLogFile(struct ReadLogDataStruct *Filter,const char *arq)
                authfail_write(&log_entry);
                if (download_flag) download_write(&log_entry,download_url);
 
-               if (current_format!=&ReadSargLog) {
+               if (log_line.current_format!=&ReadSargLog) {
                        if (period.start.tm_year==0 || idata<mindate || compare_date(&period.start,&log_entry.EntryTime)>0){
                                mindate=idata;
                                memcpy(&period.start,&log_entry.EntryTime,sizeof(log_entry.EntryTime));
index 4436321563ca7ff735356d54058ca79bafdf215c..256a0517e7a87a273718b52a5f7f40dc7292f202 100644 (file)
@@ -51,6 +51,7 @@ static enum ReadLogReturnCodeEnum Squid_ReadEntry(char *Line,struct ReadLogStruc
        time_t log_time;
        int IpLen;
        int HttpCodeLen;
+       int HttpMethodLen;
        int UrlLen;
        int UserLen;
        struct tm *tt;
@@ -109,10 +110,10 @@ static enum ReadLogReturnCodeEnum Squid_ReadEntry(char *Line,struct ReadLogStruc
        while (isdigit(*Line)) Entry->DataSize=Entry->DataSize*10+(*Line++-'0');
        if (*Line!=' ' || Begin==Line) return(RLRC_Unknown);
 
-       // skip the HTTP function
-       Begin=++Line;
-       while (*Line && *Line!=' ') Line++;
-       if (*Line!=' '|| Line==Begin) return(RLRC_Unknown);
+       // get the HTTP method
+       Entry->HttpMethod=++Line;
+       for (HttpMethodLen=0 ; *Line && *Line!=' ' ; HttpMethodLen++) Line++;
+       if (*Line!=' '|| HttpMethodLen==0) return(RLRC_Unknown);
 
        // the url
        Entry->Url=++Line;
@@ -135,6 +136,7 @@ static enum ReadLogReturnCodeEnum Squid_ReadEntry(char *Line,struct ReadLogStruc
        // it is safe to alter the line buffer now that we are returning a valid entry
        Ip[IpLen]='\0';
        Entry->HttpCode[HttpCodeLen]='\0';
+       Entry->HttpMethod[HttpMethodLen]='\0';
        Entry->Url[UrlLen]='\0';
        User[UserLen]='\0';
 
index aa7f9566ef9fa69adfa2f7a1b69eec43ad9a222a..4e8560ed84ca84ac28b036e0ce550fe063bb0b8d 100755 (executable)
 #include "include/conf.h"
 #include "include/defs.h"
 #include "include/filelist.h"
+#include "include/readlog.h"
 
-extern FileListObject AccessLog;
-
-static int getdata(char*, FILE*);
-static void datashow(const char *);
-static void getlog(void);
-static void header(void);
+//! Maximum length of the scheme plus host name from the url.
+#define MAX_URL_HOST_LEN 260
 
-void realtime(void)
+/*!
+\brief Data read from an input log file.
+*/
+struct RealtimeReadLogStruct
 {
-       getlog();
-}
+       //! The time corresponding to the entry.
+       struct tm EntryTime;
+       //! The IP address connecting to internet.
+       char Ip[48];
+       //! The user's name.
+       char User[MAX_USER_LEN];
+       /*!
+       The URL of the visited site.
+
+       The pointer may be NULL if the URL doesn't exists in the log file.
+       */
+       char Url[MAX_URL_HOST_LEN];
+       //! HTTP method or NULL if the information is not stored in the log.
+       char HttpMethod[32];
+};
+
+extern FileListObject AccessLog;
 
-static void getlog(void)
+static bool GetLatestModified(char *file_name,int file_name_size)
 {
-       FILE *tmp, *fp;
-       char template1[255]="/var/tmp/sargtpl1.XXXXXX";
-       char template2[255]="/var/tmp/sargtpl2.XXXXXX";
-       char cmd[2048];
-       char *buf;
-       const char *file;
-       int  fd1,fd2;
-       int cstatus;
-       longline line;
        FileListIterator FIter;
-
-       init_usertab(UserTabFile);
-
-#ifdef HAVE_MKSTEMP
-       fd2 = mkstemp(template2);
-       if (fd2 == -1) {
-               debuga(_("Cannot create a temporary file name to produce the report: %s\n"),strerror(errno));
-               exit(EXIT_FAILURE);
-       }
-       fd1 = mkstemp(template1);
-#else
-       buf = mktemp(template2);
-       if (buf[0]=='\0') {
-               debuga(_("Cannot create a temporary file name to produce the report: %s\n"),strerror(errno));
-               exit(EXIT_FAILURE);
-       }
-       fd2 = -1;
-       fd1 = open(mktemp(template1),O_RDWR);
-#endif
-
-       if((fd1 == -1 ) || ((tmp = fdopen (fd1, "w+" )) == NULL)  ) {    /* failure, bail out */
-               debuga(_("(realtime) mkstemp error - %s\n"),strerror(errno));
-               exit(EXIT_FAILURE);
-       }
-
-       if ((line=longline_create())==NULL) {
-               debuga(_("Not enough memory to read the log file\n"));
-               exit(EXIT_FAILURE);
-       }
+       const char *file;
+       bool found=false;
+       struct stat st;
+       time_t latest;
 
        FIter=FileListIter_Open(AccessLog);
-       file=FileListIter_Next(FIter);
-       if (file==NULL) {
-               debuga(_("No log file to read the last %d lines from\n"),realtime_access_log_lines);
-               exit(EXIT_FAILURE);
-       }
-       if (snprintf(cmd,sizeof(cmd),"tail -%d \"%s\"",realtime_access_log_lines,file)>=sizeof(cmd)) {
-               debuga(_("Input log file name too long: %s\n"),file);
-               exit(EXIT_FAILURE);
-       }
-       fp = popen(cmd, "r");
-       if (!fp) {
-               debuga(_("Failed to get %d trailing lines from %s: %s\n"),realtime_access_log_lines,file,strerror(errno));
-               debuga(_("tail command: %s\n"),cmd);
-               exit(EXIT_FAILURE);
+       while ((file=FileListIter_Next(FIter))!=NULL)
+       {
+               if (stat(file,&st)==-1) {
+                       debuga(_("Cannot stat \"%s\": %s"),file,strerror(errno));
+               }
+               if (!found)
+               {
+                       found=true;
+                       latest=st.st_mtime;
+                       safe_strcpy(file_name,file,file_name_size);
+               }
+               else if (st.st_mtime>latest)
+               {
+                       latest=st.st_mtime;
+                       safe_strcpy(file_name,file,file_name_size);
+               }
        }
        FileListIter_Close(FIter);
-       while((buf=longline_read(fp,line)) != NULL )
-               if (getdata(buf,tmp)<0) {
-                       debuga(_("Maybe a broken record or garbage was returned by %s\n"),cmd);
-                       exit(EXIT_FAILURE);
-               }
-       pclose(fp);
-       fclose(tmp);
-       longline_destroy(&line);
+       return(found);
+}
 
-       if (fd2!=-1) close(fd2);//not safe at all but good enough for now.
-       if (snprintf(cmd,sizeof(cmd),"sort -t \"\t\" -r -n -k 1,1 -o \"%s\" \"%s\"",template2,template1)>=sizeof(cmd)) {
-               debuga(_("Sort command too long when sorting file \"%s\" to \"%s\"\n"),template1,template2);
-               exit(EXIT_FAILURE);
-       }
-       cstatus=system(cmd);
-       if (!WIFEXITED(cstatus) || WEXITSTATUS(cstatus)) {
-               debuga(_("sort command return status %d\n"),WEXITSTATUS(cstatus));
-               debuga(_("sort command: %s\n"),cmd);
-               exit(EXIT_FAILURE);
-       }
-       if (!KeepTempLog && unlink(template1)) {
-               debuga(_("Cannot delete \"%s\": %s\n"),template1,strerror(errno));
-               exit(EXIT_FAILURE);
+/*!
+ * \brief Store a log entry.
+ *
+ * \param Dest A pointer to the list entry where to store the entry.
+ * \param Entry The entry to store.
+ */
+static void StoreLogEntry(struct RealtimeReadLogStruct *Dest,struct ReadLogStruct *Entry)
+{
+       memcpy(&Dest->EntryTime,&Entry->EntryTime,sizeof(Dest->EntryTime));
+       safe_strcpy(Dest->Ip,Entry->Ip,sizeof(Dest->Ip));
+       if (Entry->Url)
+       {
+               int i;
+               const char *url=Entry->Url;
+
+               // skip the scheme
+               for (i=0 ; i<8 && url[i] && (isalnum(url[i]) || url[i]=='+' || url[i]=='-' || url[i]=='.') ; i++);
+               if (url[i]==':' && url[i+1]=='/' && url[i+2]=='/')
+               {
+                       url+=i+3;
+                       for (i=0 ; url[i] && url[i]!='/' ; i++);
+               }
+               if (i>=sizeof(Dest->Url)) i=sizeof(Dest->Url)-1;
+               strncpy(Dest->Url,url,i);
+               Dest->Url[i]='\0';
        }
-       datashow(template2);
+       safe_strcpy(Dest->User,Entry->User,sizeof(Dest->User));
+       safe_strcpy(Dest->HttpMethod,Entry->HttpMethod,sizeof(Dest->HttpMethod));
 }
 
-static int getdata(char *rec, FILE *ftmp)
+static void header(void)
 {
-       int dat;
-       char typ[128];
-       char warea[MAXLEN];
+       puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"");
+       puts(" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
+       puts("<html>\n");
+       puts("<head>\n");
+       if(realtime_refresh)
+               printf("  <meta http-equiv=refresh content=\"%d\" url=\"sarg-php/sarg-realtime.php\"; charset=\"%s\">\n",realtime_refresh,CharSet);
+       else
+               printf("  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\">\n",CharSet);
+       css(stdout);
+       puts("</head>\n");
+       printf("<body style=\"font-family:%s;font-size:%s;background-color:%s;background-image:url(%s)\">\n",FontFace,TitleFontSize,BgColor,BgImage);
+       puts("<div align=\"center\"><table cellpadding=\"1\" cellspacing=\"1\">\n");
+       printf("<tr><th class=\"title_l\" colspan=\"10\">SARG %s</th></tr>\n",_("Realtime"));
+       printf("<tr><th class=\"text\" colspan=\"10\">%s: %d s</th></tr>\n",_("Auto refresh"),realtime_refresh);
+       printf("<tr><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_l\">%s</th></tr>\n",_("DATE/TIME"),_("IP/NAME"),_("USERID"),_("TYPE"),_("ACCESSED SITE"));
+}
+
+static void datashow(struct RealtimeReadLogStruct *List,int Index,int Size)
+{
+       char tbuf[128];
        char user[MAX_USER_LEN];
-       char ip[45];
-       char *url;
-       struct getwordstruct gwarea;
+       char name[MAX_USER_LEN];
+       int i;
+       struct RealtimeReadLogStruct *entry;
 
-       getword_start(&gwarea,rec);
-       if (getword_atoi(&dat,&gwarea,'.')<0) {
-               debuga(_("The time stamp at column 1 is too long\n"));
-               return(-1);
-       }
-       if (getword_skip(10,&gwarea,' ')<0) {
-               debuga(_("The time stamp decimal part at column 1 is too long\n"));
-               return(-1);
-       }
-       if (getword(warea,sizeof(warea),&gwarea,' ')<0) {
-               debuga(_("The connection duration at column 2 is too long\n"));
-               return(-1);
-       }
-       while(strcmp(warea,"") == 0 && gwarea.current[0] != '\0')
-               if (getword(warea,sizeof(warea),&gwarea,' ')<0) {
-                       return(-1);
-               }
-       if (getword(ip,sizeof(ip),&gwarea,' ')<0) {
-               debuga(_("The IP address at column 3 is too long\n"));
-               return(-1);
-       }
-       if (getword_skip(MAXLEN,&gwarea,' ')<0) {
-               debuga(_("The status at column 4 is too long\n"));
-               return(-1);
-       }
-       if (getword_skip(MAXLEN,&gwarea,' ')<0) {
-               debuga(_("The size at column 5 is too long\n"));
-               return(-1);
-       }
-       if (getword(typ,sizeof(typ),&gwarea,' ')<0) {
-               debuga(_("The action at column 6 is too long\n"));
-               return(-1);
-       }
-       if(strncmp(typ,"CONNECT",7) == 0) {
-               if (getword_ptr(rec,&url,&gwarea,' ')<0) {
-                       debuga(_("The URL at column 7 is too long\n"));
-                       return(-1);
-               }
-               if (getword(user,sizeof(user),&gwarea,' ')<0) {
-                       debuga(_("The user ID at column 8 is too long\n"));
-                       return(-1);
-               }
-       }else {
-               if (getword_skip(MAXLEN,&gwarea,'/')<0) {
-                       debuga(_("The URL at column 7 is too long\n"));
-                       return(-1);
-               }
-               if (getword_skip(MAXLEN,&gwarea,'/')<0) {
-                       debuga(_("The URL at column 7 is too long\n"));
-                       return(-1);
-               }
-               if (getword_ptr(rec,&url,&gwarea,'/')<0) {
-                       debuga(_("The URL at column 7 is too long\n"));
-                       return(-1);
-               }
-               if (getword_skip(MAXLEN,&gwarea,' ')<0) {
-                       debuga(_("The data at column 8 is too long\n"));
-                       return(-1);
-               }
-               if (getword(user,sizeof(user),&gwarea,' ')<0) {
-                       debuga(_("The user at column 9 is too long\n"));
-                       return(-1);
-               }
-       }
+       header();
+       for (i=0 ; i<realtime_access_log_lines ; i++)
+       {
+               entry=List+Index;
+               Index--;
+               if (Index<0) Index=Size-1;
+
+               if (UserIp)
+                       strcpy(user,entry->Ip);
+               else
+                       strcpy(user,entry->User);
+               if(Ip2Name)
+                       ip2name(user,sizeof(user));
+               user_find(name, sizeof(name), user);
 
-       if(strncmp(user,"-",1) == 0 && RealtimeUnauthRec==REALTIME_UNAUTH_REC_IGNORE)
-               return(0);
+               if (df=='u')
+                       strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M", &entry->EntryTime);
+               else if (df=='e')
+                       strftime(tbuf, sizeof(tbuf), "%d-%m-%Y %H:%M", &entry->EntryTime);
 
-       fprintf(ftmp,"%d\t%s\t%s\t%s\t%s\n",dat,ip,user,url,typ);
-       return(0);
+               printf("<tr><td class=\"data\">%s</td><td class=\"data3\">%s</td><td class=\"data3\">%s</td><td class=\"data3\">%s</td><td class=\"data2\"><a href=\"http://%s\">%s</td></tr>\n",
+                          tbuf,entry->Ip,name,entry->HttpMethod,entry->Url,entry->Url);
+       }
+
+       puts("</table>\n</div>\n</body>\n</html>\n");
+       fflush(NULL);
 }
 
-static void datashow(const char *tmp)
+void realtime(void)
 {
-       FILE *fin;
-       time_t tt;
-       struct tm *t;
-       char tbuf[128];
-       int dat;
+       FILE *fp;
+       char file_name[2048];
        char *buf;
-       char *url;
-       char *ourl=NULL;
-       char ouser[MAX_USER_LEN]="";
-       char typ[128];
-       char user[MAX_USER_LEN];
-       char u2[MAX_USER_LEN];
-       char ip[45];
-       int url_len;
-       int ourl_size=0;
-       struct getwordstruct gwarea;
        longline line;
+       struct ReadLogStruct log_entry;
+       enum ReadLogReturnCodeEnum log_entry_status;
+       struct LogLineStruct log_line;
+       struct RealtimeReadLogStruct *StoredLogEntries;
+       int StoreIndex=0;
+       int StoreSize=0;
+       int NextIndex=1;
 
-       if((fin=fopen(tmp,"r"))==NULL) {
-               debuga(_("(realtime) open error %s - %s\n"),tmp,strerror(errno));
-               exit(EXIT_FAILURE);
-       }
-
-       header();
+       init_usertab(UserTabFile);
+       LogLine_Init(&log_line);
 
        if ((line=longline_create())==NULL) {
                debuga(_("Not enough memory to read the log file\n"));
                exit(EXIT_FAILURE);
        }
 
-       while((buf=longline_read(fin,line))!=NULL) {
-               fixendofline(buf);
-               getword_start(&gwarea,buf);
-               if (getword_atoi(&dat,&gwarea,'\t')<0) {
-                       debuga(_("Invalid time column in file %s\n"),tmp);
-                       exit(EXIT_FAILURE);
-               }
-               if (getword(ip,sizeof(ip),&gwarea,'\t')<0) {
-                       debuga(_("Invalid IP address in file %s\n"),tmp);
-                       exit(EXIT_FAILURE);
-               }
-               if (getword(user,sizeof(user),&gwarea,'\t')<0) {
-                       debuga(_("Invalid user name in file %s\n"),tmp);
-                       exit(EXIT_FAILURE);
-               }
-               if (strlen(user) < 1) continue;
-               if (getword_ptr(buf,&url,&gwarea,'\t')<0) {
-                       debuga(_("Invalid URL in file %s\n"),tmp);
-                       exit(EXIT_FAILURE);
+       /*
+        * Store one more entry to prepare the memory structure in place and reject it if
+        * it is about the same user and url as the last stored one.
+        */
+       StoredLogEntries=calloc(realtime_access_log_lines+1,sizeof(struct RealtimeReadLogStruct));
+       if (!StoredLogEntries)
+       {
+               debuga(_("Not enough memory to store %d records"),realtime_access_log_lines);
+               exit(EXIT_FAILURE);
+       }
+       /*
+        * Clear the url and user strings so that strcmp on the user and url are not
+        * satisfied and the first entry can be stored.
+        */
+       memset(StoredLogEntries,0,sizeof(struct RealtimeReadLogStruct));
+
+       if (!GetLatestModified(file_name,sizeof(file_name)))
+       {
+               debuga(_("No log file to read the last %d lines from\n"),realtime_access_log_lines);
+               exit(EXIT_FAILURE);
+       }
+       fp = fopen(file_name, "r");
+       if (!fp) {
+               debuga(_("Failed to open the last modified log file \"%s\": %s\n"),file_name,strerror(errno));
+               exit(EXIT_FAILURE);
+       }
+       while((buf=longline_read(fp,line)) != NULL )
+       {
+               log_entry_status=LogLine_Parse(&log_line,&log_entry,buf);
+               if (log_entry_status==RLRC_Unknown)
+               {
+                       continue;
                }
-               if (getword(typ,sizeof(typ),&gwarea,'\t')<0) {
-                       debuga(_("Invalid access type in file %s\n"),tmp);
-                       exit(EXIT_FAILURE);
+               if (log_entry_status==RLRC_Ignore)
+               {
+                       continue;
                }
-               if(strstr(RealtimeTypes,typ) == 0)
+               if (log_entry.HttpMethod && strstr(RealtimeTypes,log_entry.HttpMethod)==0)
                        continue;
-
-               if(strcmp(ouser,user) == 0 && ourl && strcmp(ourl,url) == 0)
+               if (RealtimeUnauthRec==REALTIME_UNAUTH_REC_IGNORE && log_entry.User[0]=='-' && log_entry.User[1]=='\0')
+                       continue;
+               StoreLogEntry(StoredLogEntries+NextIndex,&log_entry);
+               if (strcmp(StoredLogEntries[StoreIndex].User,StoredLogEntries[NextIndex].User)==0 && strcmp(StoredLogEntries[StoreIndex].Url,StoredLogEntries[NextIndex].Url)==0)
                        continue;
 
-               if(UserIp)
-                       strcpy(user,ip);
-               strcpy(u2,user);
-               if(Ip2Name)
-                       ip2name(u2,sizeof(u2));
-               user_find(name, sizeof(name), u2);
-
-               tt=(time_t)dat;
-               t=localtime(&tt);
-               if (df=='u')
-                       strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M", t);
-               else if (df=='e')
-                       strftime(tbuf, sizeof(tbuf), "%d-%m-%Y %H:%M", t);
-
-               printf("<tr><td class=\"data\">%s</td><td class=\"data3\">%s</td><td class=\"data3\">%s</td><td class=\"data3\">%s</td><td class=\"data2\"><a href=\"http://%s\">%s</td></tr>\n",tbuf,ip,name,typ,url,url);
-               strcpy(ouser,user);
-
-               url_len=strlen(url);
-               if (!ourl || url_len>=ourl_size) {
-                       ourl_size=url_len+1;
-                       ourl=realloc(ourl,ourl_size);
-                       if (!ourl) {
-                               debuga(_("Not enough memory to store the url\n"));
-                               exit(EXIT_FAILURE);
-                       }
-               }
-               strcpy(ourl,url);
+               StoreIndex=NextIndex;
+               NextIndex++;
+               if (NextIndex>StoreSize) StoreSize=NextIndex;
+               if (NextIndex>realtime_access_log_lines) NextIndex=0;
        }
+       fclose(fp);
        longline_destroy(&line);
-       if (ourl) free(ourl);
-
-       puts("</table>\n</div>\n</body>\n</html>\n");
-       fclose(fin);
-       if (!KeepTempLog && unlink(tmp)) {
-               debuga(_("Cannot delete \"%s\": %s\n"),tmp,strerror(errno));
-               exit(EXIT_FAILURE);
-       }
-       fflush(NULL);
-}
 
-static void header(void)
-{
-       puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"");
-       puts(" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
-       puts("<html>\n");
-       puts("<head>\n");
-       if(realtime_refresh)
-               printf("  <meta http-equiv=refresh content=\"%d\" url=\"sarg-php/sarg-realtime.php\"; charset=\"%s\">\n",realtime_refresh,CharSet);
-       else
-               printf("  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\">\n",CharSet);
-       css(stdout);
-       puts("</head>\n");
-       printf("<body style=\"font-family:%s;font-size:%s;background-color:%s;background-image:url(%s)\">\n",FontFace,TitleFontSize,BgColor,BgImage);
-       puts("<div align=\"center\"><table cellpadding=\"1\" cellspacing=\"1\">\n");
-       printf("<tr><th class=\"title_l\" colspan=\"10\">SARG %s</th></tr>\n",_("Realtime"));
-       printf("<tr><th class=\"text\" colspan=\"10\">%s: %d s</th></tr>\n",_("Auto refresh"),realtime_refresh);
-       printf("<tr><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_l\">%s</th></tr>\n",_("DATE/TIME"),_("IP/NAME"),_("USERID"),_("TYPE"),_("ACCESSED SITE"));
+       datashow(StoredLogEntries,StoreIndex,StoreSize);
+       free(StoredLogEntries);
 }