// Module: Log4CPLUS // File: fileappender.cxx // Created: 6/2001 // Author: Tad E. Smith // // // Copyright 2001-2010 Tad E. Smith // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #if defined (__BORLANDC__) // For _wrename() and _wremove() on Windows. # include #endif #if ! defined (_WIN32_WCE) #include #endif #include #if defined (_WIN32_WCE) #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include #endif namespace log4cplus { using helpers::Properties; using helpers::Time; const long MINIMUM_ROLLING_LOG_SIZE = 200*1024L; /////////////////////////////////////////////////////////////////////////////// // File LOCAL definitions /////////////////////////////////////////////////////////////////////////////// namespace { #if defined (_WIN32_WCE) long const LOG4CPLUS_FILE_NOT_FOUND = ERROR_FILE_NOT_FOUND; #else long const LOG4CPLUS_FILE_NOT_FOUND = ENOENT; #endif static long file_rename (tstring const & src, tstring const & target) { #if defined (_WIN32_WCE) if (MoveFile (src.c_str (), target.c_str ())) return 0; else return GetLastError (); #elif defined (UNICODE) && defined (WIN32) if (_wrename (src.c_str (), target.c_str ()) == 0) return 0; else return errno; #else if (std::rename (LOG4CPLUS_TSTRING_TO_STRING (src).c_str (), LOG4CPLUS_TSTRING_TO_STRING (target).c_str ()) == 0) return 0; else return errno; #endif } static long file_remove (tstring const & src) { #if defined (_WIN32_WCE) if (DeleteFile (src.c_str ())) return 0; else return GetLastError (); #elif defined (UNICODE) && defined (WIN32) if (_wremove (src.c_str ()) == 0) return 0; else return errno; #else if (std::remove (LOG4CPLUS_TSTRING_TO_STRING (src).c_str ()) == 0) return 0; else return errno; #endif } static void loglog_renaming_result (helpers::LogLog & loglog, tstring const & src, tstring const & target, long ret) { if (ret == 0) { loglog.debug ( LOG4CPLUS_TEXT("Renamed file ") + src + LOG4CPLUS_TEXT(" to ") + target); } else if (ret != LOG4CPLUS_FILE_NOT_FOUND) { tostringstream oss; oss << LOG4CPLUS_TEXT("Failed to rename file from ") << target << LOG4CPLUS_TEXT(" to ") << target << LOG4CPLUS_TEXT("; error ") << ret; loglog.error (oss.str ()); } } static void loglog_opening_result (helpers::LogLog & loglog, log4cplus::tostream const & os, tstring const & filename) { if (! os) { loglog.error ( LOG4CPLUS_TEXT("Failed to open file ") + filename); } } static void rolloverFiles(const tstring& filename, unsigned int maxBackupIndex) { helpers::SharedObjectPtr loglog = helpers::LogLog::getLogLog(); // Delete the oldest file tostringstream buffer; buffer << filename << LOG4CPLUS_TEXT(".") << maxBackupIndex; long ret = file_remove (buffer.str ()); tostringstream source_oss; tostringstream target_oss; // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} for (int i = maxBackupIndex - 1; i >= 1; --i) { source_oss.str(LOG4CPLUS_TEXT("")); target_oss.str(LOG4CPLUS_TEXT("")); source_oss << filename << LOG4CPLUS_TEXT(".") << i; target_oss << filename << LOG4CPLUS_TEXT(".") << (i+1); tstring const source (source_oss.str ()); tstring const target (target_oss.str ()); #if defined (WIN32) // Try to remove the target first. It seems it is not // possible to rename over existing file. ret = file_remove (target); #endif ret = file_rename (source, target); loglog_renaming_result (*loglog, source, target, ret); } } // end rolloverFiles() } /////////////////////////////////////////////////////////////////////////////// // FileAppender ctors and dtor /////////////////////////////////////////////////////////////////////////////// FileAppender::FileAppender(const tstring& filename_, LOG4CPLUS_OPEN_MODE_TYPE mode, bool immediateFlush_) : immediateFlush(immediateFlush_) , reopenDelay(1) , bufferSize (0) , buffer (0) { init(filename_, mode); } FileAppender::FileAppender(const Properties& properties, LOG4CPLUS_OPEN_MODE_TYPE mode) : Appender(properties) , immediateFlush(true) , reopenDelay(1) , bufferSize (0) , buffer (0) { bool append_ = (mode == std::ios::app); tstring filename_ = properties.getProperty( LOG4CPLUS_TEXT("File") ); if (filename_.empty()) { getErrorHandler()->error( LOG4CPLUS_TEXT("Invalid filename") ); return; } if(properties.exists( LOG4CPLUS_TEXT("ImmediateFlush") )) { tstring tmp = properties.getProperty( LOG4CPLUS_TEXT("ImmediateFlush") ); immediateFlush = (helpers::toLower(tmp) == LOG4CPLUS_TEXT("true")); } if(properties.exists( LOG4CPLUS_TEXT("Append") )) { tstring tmp = properties.getProperty( LOG4CPLUS_TEXT("Append") ); append_ = (helpers::toLower(tmp) == LOG4CPLUS_TEXT("true")); } if(properties.exists( LOG4CPLUS_TEXT("ReopenDelay") )) { tstring tmp = properties.getProperty( LOG4CPLUS_TEXT("ReopenDelay") ); reopenDelay = std::atoi(LOG4CPLUS_TSTRING_TO_STRING(tmp).c_str()); } if(properties.exists( LOG4CPLUS_TEXT("BufferSize") )) { tstring tmp = properties.getProperty( LOG4CPLUS_TEXT("BufferSize") ); bufferSize = std::atoi(LOG4CPLUS_TSTRING_TO_STRING(tmp).c_str()); } init(filename_, (append_ ? std::ios::app : std::ios::trunc)); } void FileAppender::init(const tstring& filename_, LOG4CPLUS_OPEN_MODE_TYPE mode) { this->filename = filename_; open(mode); if (bufferSize != 0) { delete[] buffer; buffer = new tchar[bufferSize]; out.rdbuf ()->pubsetbuf (buffer, bufferSize); } if(!out.good()) { getErrorHandler()->error( LOG4CPLUS_TEXT("Unable to open file: ") + filename); return; } getLogLog().debug(LOG4CPLUS_TEXT("Just opened file: ") + filename); } FileAppender::~FileAppender() { destructorImpl(); } /////////////////////////////////////////////////////////////////////////////// // FileAppender public methods /////////////////////////////////////////////////////////////////////////////// void FileAppender::close() { LOG4CPLUS_BEGIN_SYNCHRONIZE_ON_MUTEX( access_mutex ) out.close(); delete[] buffer; buffer = 0; closed = true; LOG4CPLUS_END_SYNCHRONIZE_ON_MUTEX; } /////////////////////////////////////////////////////////////////////////////// // FileAppender protected methods /////////////////////////////////////////////////////////////////////////////// // This method does not need to be locked since it is called by // doAppend() which performs the locking void FileAppender::append(const spi::InternalLoggingEvent& event) { if(!out.good()) { if(!reopen()) { getErrorHandler()->error( LOG4CPLUS_TEXT("file is not open: ") + filename); return; } // Resets the error handler to make it // ready to handle a future append error. else getErrorHandler()->reset(); } layout->formatAndAppend(out, event); if(immediateFlush) { out.flush(); } } void FileAppender::open(std::ios::openmode mode) { out.open(LOG4CPLUS_TSTRING_TO_STRING(filename).c_str(), mode); } bool FileAppender::reopen() { // When append never failed and the file re-open attempt must // be delayed, set the time when reopen should take place. if (reopen_time == log4cplus::helpers::Time () && reopenDelay != 0) reopen_time = log4cplus::helpers::Time::gettimeofday() + log4cplus::helpers::Time(reopenDelay); else { // Otherwise, check for end of the delay (or absence of delay) to re-open the file. if (reopen_time <= log4cplus::helpers::Time::gettimeofday() || reopenDelay == 0) { // Close the current file out.close(); out.clear(); // reset flags since the C++ standard specified that all the // flags should remain unchanged on a close // Re-open the file. open(std::ios::app); // Reset last fail time. reopen_time = log4cplus::helpers::Time (); // Succeed if no errors are found. if(out.good()) return true; } } return false; } /////////////////////////////////////////////////////////////////////////////// // RollingFileAppender ctors and dtor /////////////////////////////////////////////////////////////////////////////// RollingFileAppender::RollingFileAppender(const tstring& filename_, long maxFileSize_, int maxBackupIndex_, bool immediateFlush_) : FileAppender(filename_, std::ios::app, immediateFlush_) { init(maxFileSize_, maxBackupIndex_); } RollingFileAppender::RollingFileAppender(const Properties& properties) : FileAppender(properties, std::ios::app) { int maxFileSize_ = 10*1024*1024; int maxBackupIndex_ = 1; if(properties.exists( LOG4CPLUS_TEXT("MaxFileSize") )) { tstring tmp = properties.getProperty( LOG4CPLUS_TEXT("MaxFileSize") ); tmp = helpers::toUpper(tmp); maxFileSize_ = std::atoi(LOG4CPLUS_TSTRING_TO_STRING(tmp).c_str()); if(tmp.find( LOG4CPLUS_TEXT("MB") ) == (tmp.length() - 2)) { maxFileSize_ *= (1024 * 1024); // convert to megabytes } if(tmp.find( LOG4CPLUS_TEXT("KB") ) == (tmp.length() - 2)) { maxFileSize_ *= 1024; // convert to kilobytes } } if(properties.exists( LOG4CPLUS_TEXT("MaxBackupIndex") )) { tstring tmp = properties.getProperty(LOG4CPLUS_TEXT("MaxBackupIndex")); maxBackupIndex_ = std::atoi(LOG4CPLUS_TSTRING_TO_STRING(tmp).c_str()); } init(maxFileSize_, maxBackupIndex_); } void RollingFileAppender::init(long maxFileSize_, int maxBackupIndex_) { if (maxFileSize_ < MINIMUM_ROLLING_LOG_SIZE) { tostringstream oss; oss << LOG4CPLUS_TEXT ("RollingFileAppender: MaxFileSize property") LOG4CPLUS_TEXT (" value is too small. Resetting to ") << MINIMUM_ROLLING_LOG_SIZE << "."; getLogLog ().warn (oss.str ()); maxFileSize_ = MINIMUM_ROLLING_LOG_SIZE; } this->maxFileSize = maxFileSize_; this->maxBackupIndex = (std::max)(maxBackupIndex_, 1); } RollingFileAppender::~RollingFileAppender() { destructorImpl(); } /////////////////////////////////////////////////////////////////////////////// // RollingFileAppender protected methods /////////////////////////////////////////////////////////////////////////////// // This method does not need to be locked since it is called by // doAppend() which performs the locking void RollingFileAppender::append(const spi::InternalLoggingEvent& event) { FileAppender::append(event); if(out.tellp() > maxFileSize) { rollover(); } } void RollingFileAppender::rollover() { helpers::LogLog & loglog = getLogLog(); // Close the current file out.close(); out.clear(); // reset flags since the C++ standard specified that all the // flags should remain unchanged on a close // If maxBackups <= 0, then there is no file renaming to be done. if (maxBackupIndex > 0) { rolloverFiles(filename, maxBackupIndex); // Rename fileName to fileName.1 tstring target = filename + LOG4CPLUS_TEXT(".1"); long ret; #if defined (WIN32) // Try to remove the target first. It seems it is not // possible to rename over existing file. ret = file_remove (target); #endif loglog.debug ( LOG4CPLUS_TEXT("Renaming file ") + filename + LOG4CPLUS_TEXT(" to ") + target); ret = file_rename (filename, target); loglog_renaming_result (loglog, filename, target, ret); } else { loglog.debug (filename + LOG4CPLUS_TEXT(" has no backups specified")); } // Open it up again in truncation mode open(std::ios::out | std::ios::trunc); loglog_opening_result (loglog, out, filename); } /////////////////////////////////////////////////////////////////////////////// // DailyRollingFileAppender ctors and dtor /////////////////////////////////////////////////////////////////////////////// DailyRollingFileAppender::DailyRollingFileAppender( const tstring& filename_, DailyRollingFileSchedule schedule_, bool immediateFlush_, int maxBackupIndex_) : FileAppender(filename_, std::ios::app, immediateFlush_) , maxBackupIndex(maxBackupIndex_) { init(schedule_); } DailyRollingFileAppender::DailyRollingFileAppender( const Properties& properties) : FileAppender(properties, std::ios::app) , maxBackupIndex(10) { DailyRollingFileSchedule theSchedule = DAILY; tstring scheduleStr = properties.getProperty(LOG4CPLUS_TEXT("Schedule")); scheduleStr = helpers::toUpper(scheduleStr); if(scheduleStr == LOG4CPLUS_TEXT("MONTHLY")) theSchedule = MONTHLY; else if(scheduleStr == LOG4CPLUS_TEXT("WEEKLY")) theSchedule = WEEKLY; else if(scheduleStr == LOG4CPLUS_TEXT("DAILY")) theSchedule = DAILY; else if(scheduleStr == LOG4CPLUS_TEXT("TWICE_DAILY")) theSchedule = TWICE_DAILY; else if(scheduleStr == LOG4CPLUS_TEXT("HOURLY")) theSchedule = HOURLY; else if(scheduleStr == LOG4CPLUS_TEXT("MINUTELY")) theSchedule = MINUTELY; else { getLogLog().warn( LOG4CPLUS_TEXT("DailyRollingFileAppender::ctor()- \"Schedule\" not valid: ") + properties.getProperty(LOG4CPLUS_TEXT("Schedule"))); theSchedule = DAILY; } if(properties.exists( LOG4CPLUS_TEXT("MaxBackupIndex") )) { tstring tmp = properties.getProperty(LOG4CPLUS_TEXT("MaxBackupIndex")); maxBackupIndex = std::atoi(LOG4CPLUS_TSTRING_TO_STRING(tmp).c_str()); } init(theSchedule); } void DailyRollingFileAppender::init(DailyRollingFileSchedule schedule_) { this->schedule = schedule_; Time now = Time::gettimeofday(); now.usec(0); struct tm time; now.localtime(&time); time.tm_sec = 0; switch (schedule) { case MONTHLY: time.tm_mday = 1; time.tm_hour = 0; time.tm_min = 0; break; case WEEKLY: time.tm_mday -= (time.tm_wday % 7); time.tm_hour = 0; time.tm_min = 0; break; case DAILY: time.tm_hour = 0; time.tm_min = 0; break; case TWICE_DAILY: if(time.tm_hour >= 12) { time.tm_hour = 12; } else { time.tm_hour = 0; } time.tm_min = 0; break; case HOURLY: time.tm_min = 0; break; case MINUTELY: break; }; now.setTime(&time); scheduledFilename = getFilename(now); nextRolloverTime = calculateNextRolloverTime(now); } DailyRollingFileAppender::~DailyRollingFileAppender() { destructorImpl(); } /////////////////////////////////////////////////////////////////////////////// // DailyRollingFileAppender public methods /////////////////////////////////////////////////////////////////////////////// void DailyRollingFileAppender::close() { rollover(); FileAppender::close(); } /////////////////////////////////////////////////////////////////////////////// // DailyRollingFileAppender protected methods /////////////////////////////////////////////////////////////////////////////// // This method does not need to be locked since it is called by // doAppend() which performs the locking void DailyRollingFileAppender::append(const spi::InternalLoggingEvent& event) { if(event.getTimestamp() >= nextRolloverTime) { rollover(); } FileAppender::append(event); } void DailyRollingFileAppender::rollover() { // Close the current file out.close(); out.clear(); // reset flags since the C++ standard specified that all the // flags should remain unchanged on a close // If we've already rolled over this time period, we'll make sure that we // don't overwrite any of those previous files. // E.g. if "log.2009-11-07.1" already exists we rename it // to "log.2009-11-07.2", etc. rolloverFiles(scheduledFilename, maxBackupIndex); // Do not overwriet the newest file either, e.g. if "log.2009-11-07" // already exists rename it to "log.2009-11-07.1" tostringstream backup_target_oss; backup_target_oss << scheduledFilename << LOG4CPLUS_TEXT(".") << 1; tstring backupTarget = backup_target_oss.str(); helpers::LogLog & loglog = getLogLog(); long ret; #if defined (WIN32) // Try to remove the target first. It seems it is not // possible to rename over existing file, e.g. "log.2009-11-07.1". ret = file_remove (backupTarget); #endif // Rename e.g. "log.2009-11-07" to "log.2009-11-07.1". ret = file_rename (scheduledFilename, backupTarget); loglog_renaming_result (loglog, scheduledFilename, backupTarget, ret); #if defined (WIN32) // Try to remove the target first. It seems it is not // possible to rename over existing file, e.g. "log.2009-11-07". ret = file_remove (scheduledFilename); #endif // Rename filename to scheduledFilename, // e.g. rename "log" to "log.2009-11-07". loglog.debug( LOG4CPLUS_TEXT("Renaming file ") + filename + LOG4CPLUS_TEXT(" to ") + scheduledFilename); ret = file_rename (filename, scheduledFilename); loglog_renaming_result (loglog, filename, scheduledFilename, ret); // Open a new file, e.g. "log". open(std::ios::out | std::ios::trunc); loglog_opening_result (loglog, out, filename); // Calculate the next rollover time log4cplus::helpers::Time now = Time::gettimeofday(); if (now >= nextRolloverTime) { scheduledFilename = getFilename(now); nextRolloverTime = calculateNextRolloverTime(now); } } Time DailyRollingFileAppender::calculateNextRolloverTime(const Time& t) const { switch(schedule) { case MONTHLY: { struct tm nextMonthTime; t.localtime(&nextMonthTime); nextMonthTime.tm_mon += 1; nextMonthTime.tm_isdst = 0; Time ret; if(ret.setTime(&nextMonthTime) == -1) { getLogLog().error( LOG4CPLUS_TEXT("DailyRollingFileAppender::calculateNextRolloverTime()-") LOG4CPLUS_TEXT(" setTime() returned error")); // Set next rollover to 31 days in future. ret = (t + Time(2678400)); } return ret; } case WEEKLY: return (t + Time(7 * 24 * 60 * 60)); default: getLogLog ().error ( LOG4CPLUS_TEXT ("DailyRollingFileAppender::calculateNextRolloverTime()-") LOG4CPLUS_TEXT (" invalid schedule value")); // Fall through. case DAILY: return (t + Time(24 * 60 * 60)); case TWICE_DAILY: return (t + Time(12 * 60 * 60)); case HOURLY: return (t + Time(60 * 60)); case MINUTELY: return (t + Time(60)); }; } tstring DailyRollingFileAppender::getFilename(const Time& t) const { tchar const * pattern = 0; switch (schedule) { case MONTHLY: pattern = LOG4CPLUS_TEXT("%Y-%m"); break; case WEEKLY: pattern = LOG4CPLUS_TEXT("%Y-%W"); break; default: getLogLog ().error ( LOG4CPLUS_TEXT ("DailyRollingFileAppender::getFilename()-") LOG4CPLUS_TEXT (" invalid schedule value")); // Fall through. case DAILY: pattern = LOG4CPLUS_TEXT("%Y-%m-%d"); break; case TWICE_DAILY: pattern = LOG4CPLUS_TEXT("%Y-%m-%d-%p"); break; case HOURLY: pattern = LOG4CPLUS_TEXT("%Y-%m-%d-%H"); break; case MINUTELY: pattern = LOG4CPLUS_TEXT("%Y-%m-%d-%H-%M"); break; }; tstring result (filename); result += LOG4CPLUS_TEXT("."); result += t.getFormattedTime(pattern, false); return result; } } // namespace log4cplus