/** * @file logger.cpp * @author myusgun@gmail.com * @brief logger */ #include "cf/logger.h" #include "cf/codec.h" #include "cf/file.h" #include "cf/task.h" #include "cf/util.h" #ifdef _ON_WINDOWS # include #else # include # include #endif #include #include #define LOG_SUFFIX ".log" #define DATE_NUM(_d) ((_d.mYear * 10000) + (_d.mMonth * 100) + (_d.mDay)) /*--------------------------------------------------------------*/ static std::string getFullPath(const std::string & path, const std::string & prefix, const cf::int32_t option) throw (cf::exception) { try { std::string filepath = path + cf::file::getDelimiter() + prefix; /* OPTINAL : append date if daily-rotated */ if (option & cf::logger::DAILY) { cf::util::datetime dt = cf::util::getInstance()->getDateTime(); cf::char_t buf[16] = {0x00,}; snprintf(buf, sizeof(buf) - 1, ".%04d%02d%02d", dt.mYear, dt.mMonth, dt.mDay); filepath += buf; } filepath += STR("." << cf::task::process::id()); /* FORCED : append file-extension */ filepath += LOG_SUFFIX; return filepath; } catch (cf::exception &e) { FORWARD_EXCEPTION(e); } } /*--------------------------------------------------------------*/ cf::logger::logger() throw (cf::exception) { /* none : level 0 */ cf::logger::LOG_CTX ctx; ctx.mOption = 0; memset(&ctx.mOptionParam, 0x00, sizeof(ctx.mOptionParam)); ctx.mFP = NULL; mLogPool.push_back(ctx); } cf::logger::~logger() { /* close fp / release-vector-item */ std::vector fpPool; std::vector::iterator fpIter; std::vector::iterator ctxIter; for (ctxIter = mLogPool.begin() ; ctxIter != mLogPool.end() ; ctxIter++) { cf::bool_t found = false; for (fpIter = fpPool.begin() ; fpIter != fpPool.end() ; fpIter++) { if (ctxIter->mFP == (*fpIter)) { found = true; break; } } if (!found) fpPool.push_back(ctxIter->mFP); } for (fpIter = fpPool.begin() ; fpIter != fpPool.end() ; fpIter++) { if (*fpIter) fclose(*fpIter); } } cf::logger * cf::logger::getInstance() { static logger instance; return &instance; } cf::void_t cf::logger::rotateByDate(const cf::int32_t level, const cf::util::datetime & dt) throw (cf::exception) { try { /* check datetime */ cf::int32_t current = DATE_NUM(dt); if (mLogPool[level].mOptionParam.mCurrentDateTime == current) return; open(level); /* replace datetime */ std::vector::iterator iter; for (iter = mLogPool.begin() ; iter != mLogPool.end() ; iter++) { if (iter->mFullPath == mLogPool[level].mFullPath) iter->mOptionParam.mCurrentDateTime = current; } } catch (cf::exception &e) { FORWARD_EXCEPTION(e); } } cf::void_t cf::logger::rotateBySize(const cf::int32_t level) throw (cf::exception) { try { /* check size */ cf::file logFile(mLogPool[level].mFullPath.c_str()); if (logFile.size() < mLogPool[level].mOptionParam.mMaxLogSize) return; rename(level); open(level); } catch (cf::exception &e) { FORWARD_EXCEPTION(e); } } cf::void_t cf::logger::open(const cf::int32_t level) throw (cf::exception) { try { if (!mOpenMutex.trylock()) { while (mOpenMutex.locked()) /* waiting */; return; } if (!cf::file(mPath.c_str()).exists()) cf::file::makedir(mPath.c_str()); std::string prefix = mLogPool[level].mPrefix; const cf::int32_t option = mLogPool[level].mOption; std::string fullpath = getFullPath(mPath, prefix, option); #ifdef _ON_WINDOWS # define FOPEN_MODE "ab+" #else # define FOPEN_MODE "a+" #endif FILE * fp = fopen(fullpath.c_str(), FOPEN_MODE); if (!fp) THROW_EXCEPTION("cannot open logfile {" << fullpath << "} (" << errno << ":" << strerror(errno) << ")"); replace(prefix, option, fp); mOpenMutex.unlock(); } catch (cf::exception &e) { mOpenMutex.unlock(); FORWARD_EXCEPTION(e); } } cf::void_t cf::logger::rename(const cf::int32_t level) throw (cf::exception) { try { if (!mRenameMutex.trylock()) { while (mRenameMutex.locked()) /* waiting */; return; } std::string oldpath = mLogPool[level].mFullPath; std::string newpath = oldpath; cf::int32_t idx = 0; cf::bool_t continued = true; while (continued) { std::string name = newpath + "." + STR(++idx); file file(name.c_str()); if (!file.exists()) break; } newpath += "." + STR(idx); replace(mLogPool[level].mPrefix, mLogPool[level].mOption, NULL); file(oldpath.c_str()).rename(newpath.c_str()); mRenameMutex.unlock(); } catch (cf::exception &e) { mRenameMutex.unlock(); FORWARD_EXCEPTION(e); } } cf::void_t cf::logger::replace(const std::string & prefix, const cf::int32_t option, FILE * newfp) { FILE * oldfp = NULL; std::vector::iterator iter; for (iter = mLogPool.begin() ; iter != mLogPool.end() ; iter++) { if (iter->mPrefix == prefix && iter->mOption == option) { if (!oldfp) oldfp = iter->mFP; iter->mFP = newfp; } } if (oldfp) fclose(oldfp); } cf::void_t cf::logger::init(const std::string & path) { mPath = path; } cf::void_t cf::logger::add(const std::string & prefix, const cf::int32_t level, const std::string & description, const cf::int32_t option, const cf::int32_t rotationSize) throw (cf::exception) { try { /* if already registered level */ if (level < static_cast(mLogPool.size())) return; if (level > static_cast(mLogPool.size())) THROW_EXCEPTION("level {" << level << "} is not sequential(expected {" << mLogPool.size() << "}"); cf::logger::LOG_CTX ctx; ctx.mOption = option; ctx.mOptionParam.mMaxLogSize = rotationSize * 1024 * 1024; ctx.mPrefix = prefix; ctx.mDescription = description; ctx.mFullPath = getFullPath(mPath, prefix, option); ctx.mFP = NULL; mLogPool.push_back(ctx); open(level); } catch (cf::exception &e) { FORWARD_EXCEPTION(e); } } cf::int32_t cf::logger::getLevel() { return mLogLevel; } cf::void_t cf::logger::setLevel(const cf::int32_t level) { mLogLevel = level; } std::string cf::logger::getPath(const cf::int32_t level) const throw (cf::exception) { return mLogPool[level].mFullPath; } cf::bool_t cf::logger::isRegistered(const cf::int32_t level) const { if (level >= static_cast(mLogPool.size())) return false; return true; } cf::bool_t cf::logger::isEnabled(const cf::int32_t level) const { /* check registered */ if (!isRegistered(level)) return false; /* check option */ if (mLogPool[level].mOption & logger::FORCED) return true; /* check max level */ if (level > mLogLevel) return false; return true; } cf::void_t cf::logger::write(const cf::int32_t level, const std::string & msg) { try { /* check level */ if (!isRegistered(level)) THROW_EXCEPTION("{" << level << "} not registered"); if (!isEnabled(level)) THROW_EXCEPTION("not enabled {" << level << "}"); /* ready */ cf::int32_t option = mLogPool[level].mOption; cf::util::datetime dt = cf::util::getInstance()->getDateTime(); /* rotate */ if (option & logger::DAILY) rotateByDate(level, dt); if (option & logger::SIZE) rotateBySize(level); if (!cf::file(mLogPool[level].mFullPath.c_str()).exists()) open(level); /* set current */ const cf::char_t * desc = mLogPool[level].mDescription.c_str(); cf::char_t info[64] = {0x00,}; snprintf(info, sizeof(info) - 1, "[%04d.%02d.%02d %02d:%02d:%02d.%03d][%u]", dt.mYear, dt.mMonth, dt.mDay, dt.mHour, dt.mMin, dt.mSec, dt.mUsec, task::thread::id()); /* write */ if (mLogPool[level].mFP) { fprintf(mLogPool[level].mFP, "%s [%s] %s\n", info, desc, msg.c_str()); fflush(mLogPool[level].mFP); } } catch (cf::exception &e) { fprintf(stderr, "%s\n", e.stackTrace().c_str()); fprintf(stderr, "%s\n", msg.c_str()); } }