LogFile类图
LogFile类主要负责日志的滚动,日志滚动有两种条件,一种是日志文件大小达到预设值,另一种是时间到达超过当天。由几个成员变量来控制日志滚动。
// 基本的文件名,日志文件名由基本文件名和时间日期等元素构造
const string basename_;
// 日志文件的极限容量,当日志文件的数据量达到这个限制之后就重新生成一个日志文件
const size_t rollSize_;
// 刷新间隔
const int flushInterval_;
// 每调用checkEveryN_次日志写,就滚动一次日志
const int checkEveryN_;
// 写入的次数
int count_;
// 锁
std::unique_ptr mutex_;
// 开始的周期,在同一个周期内的日志数据会写入同一个日志文件中(如果数据不是太大的话)
time_t startOfPeriod_;
// 最后一次滚动日志的时间
time_t lastRoll_;
// 上一次冲刷的时间
time_t lastFlush_;
// 日志文件
std::unique_ptr file_;
// roll的周期:即60*60*24= 一天
const static int kRollPerSeconds_ = 60*60*24;
muduo库独特之处在于使用了一个threadsafe标志,来决定是否分配mutex_,如果为假,将不使用mutex_,非线程安全但是提高了效率。
日志的滚动的实现主要由rollFile()函数。
/*
* 滚动日志
* 相当于重新生成日志文件,再向里面写数据
*/
bool LogFile::rollFile()
{
time_t now = 0;
string filename = getLogFileName(basename_, &now); //获取生成一个文件名称
//注意,这里先除KRollPerSeconds然后乘KPollPerSeconds表示对齐值KRollPerSeconds的整数倍,也就是事件调整到当天零点(/除法会引发取整)
time_t start = now / kRollPerSeconds_ * kRollPerSeconds_;
if (now > lastRoll_) //如果大于lastRoll,产生一个新的日志文件,并更新lastRoll
{
lastRoll_ = now;
lastFlush_ = now;
startOfPeriod_ = start;
file_.reset(new FileUtil::AppendFile(filename));
return true;
}
return false;
}
/*
* 构造一个日志文件名
* 日志名由基本名字+时间戳+主机名+进程id+加上“.log”后缀
*/
string LogFile::getLogFileName(const string& basename, time_t* now)
{
string filename;
//reserve()将字符串的容量设置为至少basename.size() + 64,因为后面要添加时间、主机名、进程id等内容,预先设置容量大小,为了避免反复重新分配缓冲区内存而导致效率降低,或者在使用某些STL操作(例如std::copy)之前保证缓冲区够大
filename.reserve(basename.size() + 64);
// 基本文件名
filename = basename;
// 获取当前年月日
char timebuf[32];
struct tm tm;
*now = time(NULL);
gmtime_r(now, &tm); // FIXME: localtime_r ?
strftime(timebuf, sizeof timebuf, ".%Y%m%d-%H%M%S.", &tm); //格式化时间
// 加上时间戳
filename += timebuf;
// 加上主机名
filename += ProcessInfo::hostname();
// 机上进程id
char pidbuf[32];
snprintf(pidbuf, sizeof pidbuf, ".%d", ProcessInfo::pid());
filename += pidbuf;
filename += ".log";
return filename;
}
logfile_test.20130411-115604.popo.7743.log
// 进程名字.文件的创建时间.主机名.进程id.log
日志文件名的获取由getLogFileName()函数实现:
(1)给string filename预先设置容量
(2) 用gmtime_r()获取当前时间。
(3)取主机名和进程id#include
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
这两个函数意思一样,将timep这个秒数转换成以UTC时区为标准的年月日时分秒时间。gmtime_r是线程安全的,推荐使用这个。gmtime返回的是一个structtm*,这个指针指向一个静态的内存,这块区域是会经常被改动的。你刚调用gmtime(),执行了其他几条命令,然后想使用刚才gmtime()得到struct tm,会发现内容不对了,所以很危险,使用gmtime_r后就没有问题,gmtime_r会将结果保存到你传入的内存中。
gmtime_r()函数功能与此相同,但是它可以将数据存储到用户提供的结构体中。
strftime()函数说明:https://baike.baidu.com/item/strftime/9569073?fr=aladdin
string ProcessInfo::hostname()
{
// HOST_NAME_MAX 64
// _POSIX_HOST_NAME_MAX 255
char buf[256];
if (::gethostname(buf, sizeof buf) == 0)
{
buf[sizeof(buf)-1] = '\0';
return buf;
}
else
{
return "unknownhost";
}
}
pid_t ProcessInfo::pid()
{
return ::getpid();
}
下面的这行代码是获取到的是这天的凌晨零点,因为我们使用的是北京时间,东8区,实际上是这天的8点。
time_t start = now / kRollPerSeconds_ * kRollPerSeconds_;
append()函数添加日志内容
void LogFile::append(const char* logline, int len)
{
if (mutex_)
{
MutexLockGuard lock(*mutex_);
append_unlocked(logline, len);
}
else
{
append_unlocked(logline, len);
}
}
void LogFile::append_unlocked(const char* logline, int len)
{
// 添加到日志文件中(其实只是存放在缓冲区中
file_->append(logline, len);
if (file_->writtenBytes() > rollSize_)
{
rollFile();
}
else
{
++count_;
if (count_ >= checkEveryN_)
{
count_ = 0;
time_t now = ::time(NULL);
time_t thisPeriod_ = now / kRollPerSeconds_ * kRollPerSeconds_;
if (thisPeriod_ != startOfPeriod_)
{
rollFile();
}
else if (now - lastFlush_ > flushInterval_)
{
lastFlush_ = now;
file_->flush();
}
}
}
}