/* * Worldvisions Weaver Software: * Copyright (C) 1997-2002 Net Integration Technologies, Inc. * * A "Log Receiver" that logs messages to a file */ #include "wvlogfile.h" #include "wvtimeutils.h" #include "wvdiriter.h" #include "strutils.h" #include "wvdailyevent.h" #include "wvfork.h" #include #include #ifndef _WIN32 #include #endif #define MAX_LOGFILE_SZ 1024*1024*100 // 100 Megs #ifdef MACOS #define O_LARGEFILE 00000000 // MAC doesn't need Largefile support, so just make it a dummy value when ORd #endif static time_t gmtoffset() { time_t nowgmt = time(NULL); struct tm gmt = *gmtime(&nowgmt); struct tm local = *localtime(&nowgmt); time_t nowantilocal = mktime(&gmt); // mktime assumes gmt return nowgmt - nowantilocal; } //----------------------------------- WvLogFileBase ------------------ WvLogFileBase::WvLogFileBase(WvStringParm _filename, WvLog::LogLevel _max_level) : WvLogRcv(_max_level), WvFile(_filename, O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE, 0644) { fsync_every = fsync_count = 0; } WvLogFileBase::WvLogFileBase(WvLog::LogLevel _max_level) : WvLogRcv(_max_level) { fsync_every = fsync_count = 0; } void WvLogFileBase::_mid_line(const char *str, size_t len) { WvFile::write(str, len); } void WvLogFileBase::_end_line() { if (fsync_every) { fsync_count--; if (fsync_count <= 0 || fsync_count > fsync_every) { fsync_count = fsync_every; //WvFile::print("tick!\n"); WvFile::flush(1000); fsync(getwfd()); } } } #ifdef _WIN32 #define TIME_FORMAT "%b %d %H:%M:%S" // timezones in win32 look stupid #else #define TIME_FORMAT "%b %d %H:%M:%S %Z" #endif void WvLogFileBase::_make_prefix(time_t timenow) { struct tm* tmstamp = localtime(&timenow); char timestr[30]; strftime(×tr[0], 30, TIME_FORMAT, tmstamp); prefix = WvString("%s: %s<%s>: ", timestr, last_source, loglevels[last_level]); prelen = prefix.len(); } //----------------------------------- WvLogFile ---------------------- WvLogFile::WvLogFile(WvStringParm _filename, WvLog::LogLevel _max_level, int _keep_for, bool _force_new_line, bool _allow_append) : WvLogFileBase(_max_level), keep_for(_keep_for), filename(_filename), allow_append(_allow_append) { WvLogRcv::force_new_line = _force_new_line; // start_log(); // don't open log until the first message gets printed } void WvLogFile::_make_prefix(time_t timenow) { if (!WvFile::isok()) start_log(); // struct tm *tmstamp = localtime(&timenow); struct stat statbuf; // Get the filesize if (fstat(getfd(), &statbuf) == -1) statbuf.st_size = 0; // Make sure we are calculating last_day in the current time zone. if (last_day != ((timenow + gmtoffset())/86400) || statbuf.st_size > MAX_LOGFILE_SZ) start_log(); WvLogFileBase::_make_prefix(timenow); } static void trim_old_logs(WvStringParm filename, WvStringParm base, int keep_for) { if (!keep_for) return; WvDirIter i(getdirname(filename), false); for (i.rewind(); i.next(); ) { // if it begins with the base name if (!strncmp(i.ptr()->name, base, strlen(base))) { // and it's older than 'keep_for' days if (i.ptr()->st_mtime < wvtime().tv_sec - keep_for*86400) ::unlink(i.ptr()->fullname); } } } WvString WvLogFile::start_log() { WvFile::close(); int num = 0; struct stat statbuf; time_t timenow = wvtime().tv_sec; last_day = (timenow + gmtoffset()) / 86400; struct tm* tmstamp = localtime(&timenow); char buf[20]; WvString fullname; strftime(buf, 20, "%Y-%m-%d", tmstamp); // Get the next filename do fullname = WvString("%s.%s.%s", filename, buf, num++); while (stat(fullname, &statbuf) != -1 && (statbuf.st_size >= MAX_LOGFILE_SZ || !allow_append)); WvString curname("%s.current", filename); WvString base = getfilename(filename); WvFile::open(fullname, O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE, 0644); #ifndef _WIN32 // no symlinks in win32 // Don't delete the file, unless it's a symlink! int sym = readlink(curname, buf, 20); if (sym > 0 || errno == ENOENT) { unlink(curname); symlink(getfilename(fullname), curname); } #endif #ifndef _WIN32 // We fork here because this can be really slow when the directory has // (oh, say 32,000 files) pid_t forky = wvfork(); if (!forky) { // ForkTwiceSoTheStupidThingWorksRight if (!wvfork()) { // Child will Look for old logs and purge them trim_old_logs(filename, base, keep_for); _exit(0); } _exit(0); } // In case a signal is in the process of being delivered... pid_t rv; while ((rv = waitpid(forky, NULL, 0)) != forky) if (rv == -1 && errno != EINTR) break; #else // just do it in the foreground on Windows trim_old_logs(filename, base, keep_for); #endif return fullname; }