WvStreams
wvlogfile.cc
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  *
00005  * A "Log Receiver" that logs messages to a file
00006  */
00007 #include "wvlogfile.h"
00008 #include "wvtimeutils.h"
00009 #include "wvdiriter.h"
00010 #include "strutils.h"
00011 #include "wvdailyevent.h"
00012 #include "wvfork.h"
00013 #include <time.h>
00014 #include <sys/types.h>
00015 #ifndef _WIN32
00016 #include <sys/wait.h>
00017 #endif
00018 
00019 #define MAX_LOGFILE_SZ  1024*1024*100   // 100 Megs
00020 
00021 static time_t gmtoffset()
00022 {
00023     time_t nowgmt = time(NULL);
00024     struct tm gmt = *gmtime(&nowgmt);
00025     struct tm local = *localtime(&nowgmt);
00026     time_t nowantilocal = mktime(&gmt); // mktime assumes gmt
00027     return nowgmt - nowantilocal;
00028 }
00029 
00030 
00031 //----------------------------------- WvLogFileBase ------------------
00032 
00033 WvLogFileBase::WvLogFileBase(WvStringParm _filename, WvLog::LogLevel _max_level)
00034     : WvLogRcv(_max_level),
00035       WvFile(_filename, O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE, 0644)
00036 {
00037     fsync_every = fsync_count = 0;
00038 }
00039 
00040 
00041 WvLogFileBase::WvLogFileBase(WvLog::LogLevel _max_level) 
00042     : WvLogRcv(_max_level) 
00043 { 
00044     fsync_every = fsync_count = 0;
00045 }
00046 
00047 
00048 void WvLogFileBase::_mid_line(const char *str, size_t len)
00049 {
00050     WvFile::write(str, len);
00051 }
00052 
00053 
00054 void WvLogFileBase::_end_line()
00055 {
00056     if (fsync_every)
00057     {
00058         fsync_count--;
00059         if (fsync_count <= 0 || fsync_count > fsync_every)
00060         {
00061             fsync_count = fsync_every;
00062             //WvFile::print("tick!\n");
00063             WvFile::flush(1000);
00064             fsync(getwfd());
00065         }
00066     }
00067 }
00068 
00069 #ifdef _WIN32
00070 #define TIME_FORMAT "%b %d %H:%M:%S" // timezones in win32 look stupid
00071 #else
00072 #define TIME_FORMAT "%b %d %H:%M:%S %Z"
00073 #endif
00074 
00075 void WvLogFileBase::_make_prefix(time_t timenow)
00076 {
00077     struct tm* tmstamp = localtime(&timenow);
00078     char timestr[30];
00079     strftime(&timestr[0], 30, TIME_FORMAT, tmstamp);
00080 
00081     prefix = WvString("%s: %s<%s>: ", timestr, last_source,
00082         loglevels[last_level]);
00083     prelen = prefix.len();
00084 }
00085 
00086 //----------------------------------- WvLogFile ----------------------
00087 
00088 WvLogFile::WvLogFile(WvStringParm _filename, WvLog::LogLevel _max_level,
00089                      int _keep_for, bool _force_new_line, bool _allow_append)
00090     : WvLogFileBase(_max_level), keep_for(_keep_for), filename(_filename),
00091       allow_append(_allow_append)
00092 {
00093     WvLogRcv::force_new_line = _force_new_line;
00094     // start_log(); // don't open log until the first message gets printed
00095 }
00096 
00097 void WvLogFile::_make_prefix(time_t timenow)
00098 {
00099     if (!WvFile::isok())
00100         start_log();
00101     
00102     // struct tm *tmstamp = localtime(&timenow);
00103     struct stat statbuf;
00104 
00105     // Get the filesize
00106     if (fstat(getfd(), &statbuf) == -1)
00107         statbuf.st_size = 0;
00108 
00109     // Make sure we are calculating last_day in the current time zone.
00110     if (last_day != ((timenow + gmtoffset())/86400) 
00111         || statbuf.st_size > MAX_LOGFILE_SZ)
00112         start_log();
00113 
00114     WvLogFileBase::_make_prefix(timenow);
00115 }
00116 
00117 static void trim_old_logs(WvStringParm filename, WvStringParm base,
00118                           int keep_for)
00119 {
00120     if (!keep_for) return;
00121     WvDirIter i(getdirname(filename), false);
00122     for (i.rewind(); i.next(); )
00123     {
00124         // if it begins with the base name
00125         if (!strncmp(i.ptr()->name, base, strlen(base)))
00126         {
00127             // and it's older than 'keep_for' days
00128             if (i.ptr()->st_mtime < wvtime().tv_sec - keep_for*86400)
00129                 ::unlink(i.ptr()->fullname);
00130         }
00131     }
00132 }
00133 
00134 
00135 WvString WvLogFile::start_log()
00136 {
00137     WvFile::close();
00138 
00139     int num = 0;
00140     struct stat statbuf;
00141     time_t timenow = wvtime().tv_sec;
00142     last_day = (timenow + gmtoffset()) / 86400;
00143     struct tm* tmstamp = localtime(&timenow);
00144     char buf[20];
00145     WvString fullname;
00146     strftime(buf, 20, "%Y-%m-%d", tmstamp);
00147 
00148     // Get the next filename
00149     do
00150         fullname = WvString("%s.%s.%s", filename, buf, num++);
00151     while (stat(fullname, &statbuf) != -1
00152                 && (statbuf.st_size >= MAX_LOGFILE_SZ || !allow_append));
00153 
00154     WvString curname("%s.current", filename);
00155     WvString base = getfilename(filename);
00156 
00157     WvFile::open(fullname, O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE, 0644);
00158 
00159 #ifndef _WIN32 // no symlinks in win32
00160     // Don't delete the file, unless it's a symlink!
00161     int sym = readlink(curname, buf, 20);
00162     if (sym > 0 || errno == ENOENT)
00163     {
00164         unlink(curname);
00165         symlink(getfilename(fullname), curname);
00166     }
00167 #endif
00168 
00169 #ifndef _WIN32
00170     // We fork here because this can be really slow when the directory has
00171     // (oh, say 32,000 files)
00172     pid_t forky = wvfork();
00173     if (!forky)
00174     {
00175         // ForkTwiceSoTheStupidThingWorksRight
00176         if (!wvfork())
00177         {
00178             // Child will Look for old logs and purge them
00179             trim_old_logs(filename, base, keep_for);
00180             _exit(0);
00181         }
00182         _exit(0);
00183     }
00184     // In case a signal is in the process of being delivered...
00185     pid_t rv;
00186     while ((rv = waitpid(forky, NULL, 0)) != forky)
00187         if (rv == -1 && errno != EINTR)
00188             break;
00189 #else
00190     // just do it in the foreground on Windows
00191     trim_old_logs(filename, base, keep_for);
00192 #endif
00193     
00194     return fullname;
00195 }