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