From f438a7bc612e94bfcd1ef2b61a7d5b61ce2eaaa6 Mon Sep 17 00:00:00 2001 From: Yohann Ferreira Date: Wed, 29 Sep 2010 20:39:56 +0200 Subject: Add log file rotation support based on ExceptionFault's work. This patch adds options to enable log rotations base on files size and or change of date. Note: Zip support will be added in a second commit. Reviewed-by: CodyMartin, Thorbjorn. --- src/utils/logger.cpp | 154 +++++++++++++++++++++++++++++++++++++++++++++------ src/utils/logger.h | 43 +++++++++++++- src/utils/string.hpp | 14 +++++ 3 files changed, 192 insertions(+), 19 deletions(-) (limited to 'src/utils') diff --git a/src/utils/logger.cpp b/src/utils/logger.cpp index 71f92a88..45a68eb5 100644 --- a/src/utils/logger.cpp +++ b/src/utils/logger.cpp @@ -1,6 +1,7 @@ /* * The Mana Server * Copyright (C) 2004-2010 The Mana World Development Team + * Copyright (C) 2010 The Mana Development Team * * This file is part of The Mana Server. * @@ -19,6 +20,8 @@ */ #include "logger.h" +#include "common/resourcemanager.hpp" +#include "utils/string.hpp" #include #include @@ -31,11 +34,24 @@ namespace utils { - -static std::ofstream mLogFile; /**< Log file. */ -bool Logger::mHasTimestamp = true; /**< Timestamp flag. */ -bool Logger::mTeeMode = false; /**< Tee mode flag. */ -Logger::Level Logger::mVerbosity = Logger::Info; /**< Verbosity level. */ +/** Log file. */ +static std::ofstream mLogFile; +/** current log filename */ +std::string Logger::mFilename = ""; +/** Timestamp flag. */ +bool Logger::mHasTimestamp = true; +/** Tee mode flag. */ +bool Logger::mTeeMode = false; +/** Verbosity level. */ +Logger::Level Logger::mVerbosity = Logger::Info; +/** Enables logrotation by size of the logfile. */ +bool Logger::mLogRotation = false; +/** Maximum size of current logfile. */ +long Logger::mMaxFileSize = 1024; // 1 Mb +/** Switch log file each day. */ +bool Logger::mSwitchLogEachDay = false; +/** Last call date */ +static std::string mLastCallDate = ""; /** * Gets the current time. @@ -47,29 +63,72 @@ static std::string getCurrentTime() time_t now; tm local; - // get current time_t value + // Get current time_t value time(&now); - // convert time_t to tm struct to break the time into individual - // constituents + // Convert time_t to tm struct to break the time into individual + // constituents. local = *(localtime(&now)); - // stringify the time, the format is: [hh:mm:ss] + // Stringify the time, the format is: [hh-mm-ss] using namespace std; ostringstream os; - os << "[" << setw(2) << setfill('0') << local.tm_hour - << ":" << setw(2) << setfill('0') << local.tm_min - << ":" << setw(2) << setfill('0') << local.tm_sec - << "]"; + os << setw(2) << setfill('0') << local.tm_hour + << "-" << setw(2) << setfill('0') << local.tm_min + << "-" << setw(2) << setfill('0') << local.tm_sec; return os.str(); } +/** + * Gets the current date. + * + * @return the current date as string. + */ +static std::string getCurrentDate() +{ + time_t now; + tm local; + + // Get current time_t value + time(&now); + + // Convert time_t to tm struct to break the time into individual + // constituents. + local = *(localtime(&now)); + + // Stringify the time, the format is: yyyy-mm-dd + using namespace std; + ostringstream os; + os << setw(4) << setfill('0') << (local.tm_year + 1900) + << "-" << setw(2) << setfill('0') << local.tm_mon + << "-" << setw(2) << setfill('0') << local.tm_mday; + + return os.str(); +} + +/** + * Check whether the day has changed since the last call. + * + * @return whether the day has changed. + */ +static bool getDayChanged() +{ + static std::string date = getCurrentDate(); + if (mLastCallDate.compare(date)) + { + // Reset the current date for next call. + mLastCallDate = date; + return true; + } + return false; +} + void Logger::output(std::ostream &os, const std::string &msg, const char *prefix) { if (mHasTimestamp) { - os << getCurrentTime() << ' '; + os << "[" << getCurrentTime() << "]" << ' '; } if (prefix) @@ -80,7 +139,7 @@ void Logger::output(std::ostream &os, const std::string &msg, const char *prefix os << msg << std::endl; } -void Logger::setLogFile(const std::string &logFile) +void Logger::setLogFile(const std::string &logFile, bool append) { // Close the current log file. if (mLogFile.is_open()) @@ -88,8 +147,12 @@ void Logger::setLogFile(const std::string &logFile) mLogFile.close(); } - // Open the file for output and remove the former file contents. - mLogFile.open(logFile.c_str(), std::ios::trunc); + // Open the file for output + // and remove the former file contents depending on the append flag. + mLogFile.open(logFile.c_str(), + append ? std::ios::ate : std::ios::trunc); + mFilename = logFile; + mLastCallDate = getCurrentDate(); if (!mLogFile.is_open()) { @@ -97,7 +160,7 @@ void Logger::setLogFile(const std::string &logFile) } else { - // by default the streams do not throw any exception + // By default the streams do not throw any exception // let std::ios::failbit and std::ios::badbit throw exceptions. mLogFile.exceptions(std::ios::failbit | std::ios::badbit); } @@ -127,6 +190,7 @@ void Logger::output(const std::string &msg, Level atVerbosity) if (open) { output(mLogFile, msg, prefixes[atVerbosity]); + switchLogs(); } if (!open || mTeeMode) @@ -137,4 +201,58 @@ void Logger::output(const std::string &msg, Level atVerbosity) } } +void Logger::switchLogs() +{ + // Handles logswitch if enabled + // and if at least one switch condition permits it. + if (!mLogRotation || (mMaxFileSize <= 0 && !mSwitchLogEachDay)) + return; + + // Update current filesize + long mFileSize = mLogFile.tellp(); + + if ((mFileSize >= mMaxFileSize * 1024) + || (mSwitchLogEachDay && getDayChanged())) + { + // Close logfile, rename it and open a new one + mLogFile.flush(); + mLogFile.close(); + + // Stringify the time, the format is: yyyy-mm-dd_hh-mm-ss-logFilename. + using namespace std; + ostringstream os; + os << getCurrentDate(); + + int fileNum = 1; + std::string newFileName = os.str() + "-" + toString(fileNum) + + "_" + mFilename; + // Keeping a hard limit of 100 files per day. + while (ResourceManager::exists(newFileName) && fileNum < 100) + { + fileNum++; + newFileName = os.str() + "-" + toString(fileNum) + + "_" + mFilename; + } + + if (rename(mFilename.c_str(), newFileName.c_str())) + { + ostringstream errorOs; + errorOs << "Error renaming file: " << mFilename << " to: " + << newFileName << std::endl << "Continuing on the same log file."; + perror(errorOs.str().c_str()); + + // Continue appending on the original file. + setLogFile(mFilename, true); + } + else + { + // Keep the logging after emptying the original log file. + setLogFile(mFilename); + } + + mLogFile << "---- Continue logging from former file " << os.str() + << " ----" << std::endl; + } +} + } // namespace utils diff --git a/src/utils/logger.h b/src/utils/logger.h index 473e0257..ba64b10b 100644 --- a/src/utils/logger.h +++ b/src/utils/logger.h @@ -1,6 +1,7 @@ /* * The Mana Server * Copyright (C) 2004-2010 The Mana World Development Team + * Copyright (C) 2010 The Mana Development Team * * This file is part of The Mana Server. * @@ -88,10 +89,11 @@ class Logger * contents are removed. * * @param logFile the log file name (may include path). + * @param append whether the file is cleaned up before logging in. * * @exception std::ios::failure if the log file could not be opened. */ - static void setLogFile(const std::string &logFile); + static void setLogFile(const std::string &logFile, bool append = false); /** * Add/removes the timestamp. @@ -119,6 +121,31 @@ class Logger static void setVerbosity(Level verbosity) { mVerbosity = verbosity; } + /** + * Enable logrotation based on the maximum filesize given in + * setMaxLogfileSize. + * + * @param enable Set to true to enable logrotation. + */ + static void enableLogRotation(bool enable = true) + { mLogRotation = enable; } + + /** + * Sets the maximum size of a logfile before logrotation occurs. + * + * @param kiloBytes Maximum size of logfile in bytes. Defaults to 1MB. + */ + static void setMaxLogfileSize(long kiloBytes = 1024) + { mMaxFileSize = kiloBytes; } + + /** + * Sets whether the logfile switches when changing date. + * + * @param switchLogEachDay Keeps whether the parameter is activated. + */ + static void setSwitchLogEachDay(bool switchLogEachDay) + { mSwitchLogEachDay = switchLogEachDay; } + /** * Logs a generic message. * @@ -136,6 +163,14 @@ class Logger static bool mHasTimestamp; /**< Timestamp flag. */ static bool mTeeMode; /**< Tee mode flag. */ + static std::string mFilename; /**< Name of the current logfile. */ + /** Enable rotation of logfiles by size. */ + static bool mLogRotation; + /** Maximum size of current logfile in bytes */ + static long mMaxFileSize; + /** Sets whether the logfile switches when changing date. */ + static bool mSwitchLogEachDay; + /** * Logs a generic message. * @@ -147,6 +182,12 @@ class Logger */ static void output(std::ostream &os, const std::string &msg, const char *prefix); + + /** + * Switch the log file based on a maximum size + * and/or and a date change. + */ + static void switchLogs(); }; diff --git a/src/utils/string.hpp b/src/utils/string.hpp index 9f2b4ac6..6127bfed 100644 --- a/src/utils/string.hpp +++ b/src/utils/string.hpp @@ -22,6 +22,7 @@ #define UTILS_STRING_H #include +#include namespace utils { @@ -67,6 +68,19 @@ namespace utils * @param str the string to trim spaces off */ void trim(std::string &str); + + /** + * Converts the given value to a string using std::stringstream. + * + * @param arg the value to convert to a string + * @return the string representation of arg + */ + template std::string toString(const T &arg) + { + std::stringstream ss; + ss << arg; + return ss.str(); + } } #endif // UTILS_STRING_H -- cgit v1.2.3-60-g2f50