muduo库里面的实现日志滚动有两种条件,一种是日志文件大小达到预设值,另一种是时间到达超过当天。滚动日志类的文件是LogFile.cc ,LogFile.h
代码如下:
LogFile.cc
#include
#include // strerror_tl
#include
#include
#include
#include
using namespace muduo;
//LogFile里面嵌套的File类
// not thread safe
class LogFile::File : boost::noncopyable
{
public:
//传进文件名称,执行打开文件,文件指针保存到fp_
explicit File(const string& filename): fp_(::fopen(filename.data(), "ae")), writtenBytes_(0)
{
assert(fp_);//断言文件已经打开
::setbuffer(fp_, buffer_, sizeof buffer_);//设定文件指针的缓冲区
// posix_fadvise POSIX_FADV_DONTNEED ?
}
~File()
{//析构函数关闭文件指针
::fclose(fp_);
}
//把logline这一行信息添加到文件当中
void append(const char* logline, const size_t len)
{
size_t n = write(logline, len);//这个write是一个内部的成员函数
//计算剩余的字节数,len是要写入的,n是已经写入的
size_t remain = len - n;
while (remain > 0)//大于-则表示没有写完
{//一直写直到写完为止
size_t x = write(logline + n, remain);
if (x == 0)
{
int err = ferror(fp_);
if (err)//写入错误
{
fprintf(stderr, "LogFile::File::append() failed %s\n", strerror_tl(err));
}
break;
}
n += x;//更新已经写的个数
remain = len - n; // remain -= x,更新剩余字节数
}
writtenBytes_ += len;//更新已经写的字节数
}
void flush()
{//清空缓冲区
::fflush(fp_);
}
//返回已经写的字节数
size_t writtenBytes() const { return writtenBytes_; }
private:
size_t write(const char* logline, size_t len)
{
#undef fwrite_unlocked
//使用fwrite_unlocked方式写入效率会高一些
return ::fwrite_unlocked(logline, 1, len, fp_);
}
FILE* fp_;//fp_为文件指针
char buffer_[64*1024];//文件指针的缓冲区,64k
size_t writtenBytes_;//已经写入的字节数
};
LogFile::LogFile(const string& basename,
size_t rollSize,
bool threadSafe,
int flushInterval)
: basename_(basename),//日志文件的basename
rollSize_(rollSize),//日志文件写到rollSize_这么大的容量时就换一个新的文件
flushInterval_(flushInterval),//日志写入的间隔时间,默认是3秒钟
count_(0),//计数器初始化为0
mutex_(threadSafe ? new MutexLock : NULL),//如果是线程安全的则构造一个互斥锁
//mutex_是个智能指针能够自动销毁
startOfPeriod_(0),//开始记录日志时间(将会调整至0点时间)
lastRoll_(0),//上一次滚动日志的时间
lastFlush_(0)//上一次日志写入文件的时间
{
assert(basename.find('/') == string::npos);//断言basename不能找到'/'
rollFile();//滚动日志(第一次,产生一个文件)
}
LogFile::~LogFile()
{
}
void LogFile::append(const char* logline, int len)
{
if (mutex_)//如果是线程安全的
{
MutexLockGuard lock(*mutex_);//先加锁
append_unlocked(logline, len);//再添加
}
else//否则直接添加
{
append_unlocked(logline, len);
}
}
void LogFile::flush()
{
if (mutex_)
{
MutexLockGuard lock(*mutex_);
file_->flush();
}
else
{
file_->flush();
}
}
//日志滚动的条件有两种:满,到达第二天
void LogFile::append_unlocked(const char* logline, int len)
{//加入
file_->append(logline, len);
if (file_->writtenBytes() > rollSize_)//如果已经写入的字节数大于rollSize_
{
rollFile();//滚动日志
}
else
{//否则查看计数值count是否超过kCheckTimeRoll_
if (count_ > kCheckTimeRoll_)
{
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();
}
}
else
{
++count_;//计数值增加
}
}
}
//滚动日志
void LogFile::rollFile()
{
time_t now = 0;
string filename = getLogFileName(basename_, &now);//获取文件名,并且返回时间
//这里除以kRollPerSeconds_又乘以kRollPerSeconds_表示对齐至kRollPerSeconds_的整数倍
//即把时间调整到当天的零点
time_t start = now / kRollPerSeconds_ * kRollPerSeconds_;
if (now > lastRoll_)
{//滚动日志
lastRoll_ = now;
lastFlush_ = now;
startOfPeriod_ = start;
file_.reset(new File(filename));//产生一个新的日志文件
}
}
//获取文件名
string LogFile::getLogFileName(const string& basename, time_t* now)
{
string filename;
//string类对象filename预留这么多的空间,basename后面还要加内容,所以再加64
filename.reserve(basename.size() + 64);
filename = basename;
char timebuf[32];
char pidbuf[32];
struct tm tm;
*now = time(NULL);//获取当前时间
//gmtime_r是线程安全的,gmtime不是线程安全的
gmtime_r(now, &tm); // FIXME: localtime_r ?GMT时间也就是UTC时间,保存在tm中
//把时间格式化放在缓冲区timebuf中
strftime(timebuf, sizeof timebuf, ".%Y%m%d-%H%M%S.", &tm);
//时间加入到filename中
filename += timebuf;
filename += ProcessInfo::hostname();//主机名称加入到filename中
//获取进程号pid
snprintf(pidbuf, sizeof pidbuf, ".%d", ProcessInfo::pid());
//pid加入到filename中
filename += pidbuf;
//后缀名加入到filename中
filename += ".log";
//返回filename
return filename;
}
LogFile.h
//日志滚动
#ifndef MUDUO_BASE_LOGFILE_H
#define MUDUO_BASE_LOGFILE_H
#include
#include
#include
#include
namespace muduo
{
class LogFile : boost::noncopyable
{
public:
//线程安全默认是true,日志写入的间隔时间是3秒钟
LogFile(const string& basename,size_t rollSize,bool threadSafe = true,int flushInterval = 3);
~LogFile();
//将长度为len的一行添加到日志当中
void append(const char* logline, int len);
//清空缓冲区
void flush();
private:
//以不加锁的方式添加
void append_unlocked(const char* logline, int len);
//获取日志文件的名称
static string getLogFileName(const string& basename, time_t* now);
//滚动日志
void rollFile();
const string basename_;//日志文件的basename
const size_t rollSize_;//日志文件写到rollSize_这么大的容量时就换一个新的文件
const int flushInterval_;//日志写入的间隔时间
int count_;//计数器,当达到kCheckTimeRoll_会去检测一下是否需要写入新文件
boost::scoped_ptr mutex_;//互斥量的智能指针
time_t startOfPeriod_;//开始记录日志时间(将会调整至0点时间)
time_t lastRoll_;//上一次滚动日志的时间
time_t lastFlush_;//上一次日志写入文件的时间
class File;//File嵌套类
boost::scoped_ptr file_;//File嵌套类的一个智能指针
const static int kCheckTimeRoll_ = 1024;
const static int kRollPerSeconds_ = 60*60*24;//一天的秒数
};
}
#endif // MUDUO_BASE_LOGFILE_H
测试代码则是输出一系列的日志,进行日志滚动,代码如下:
LogFile_test.cc
//日志滚动测试程序
#include
#include
boost::scoped_ptr g_logFile;
void outputFunc(const char* msg, int len)
{
g_logFile->append(msg, len);
}
void flushFunc()
{
g_logFile->flush();
}
int main(int argc, char* argv[])
{
char name[256];
strncpy(name, argv[0], 256);
//滚动日志的话肯定就是输出到文件了
g_logFile.reset(new muduo::LogFile(::basename(name), 200*1000));
muduo::Logger::setOutput(outputFunc);
muduo::Logger::setFlush(flushFunc);
muduo::string line = "1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ ";
for (int i = 0; i < 10000; ++i)
{//输出日志
LOG_INFO << line << i;
usleep(1000);
}
}
运行结果如下:
另一个文件测试日志的各种性能
Logging_test.cc
#include
#include
#include
#include
int g_total;
FILE* g_file;//文件指针
boost::scoped_ptr g_logFile;//智能指针
void dummyOutput(const char* msg, int len)
{
g_total += len;//更新g_total
if (g_file)//最开始g_file,g_logFile都是空的,不执行
{
fwrite(msg, 1, len, g_file);
}
else if (g_logFile)
{
g_logFile->append(msg, len);
}
}
void bench(const char* type)
{
muduo::Logger::setOutput(dummyOutput);//更改默认输出
muduo::Timestamp start(muduo::Timestamp::now());//开始时间
g_total = 0;
int n = 1000*1000;//100w次
const bool kLongLog = false;
muduo::string empty = " ";
muduo::string longStr(3000, 'X');//3000个X
longStr += " ";
for (int i = 0; i < n; ++i)
{
LOG_INFO << "Hello 0123456789" << " abcdefghijklmnopqrstuvwxyz"
<< (kLongLog ? longStr : empty)//kLongLog真,输出longStr,假输出empty
<< i;
}
muduo::Timestamp end(muduo::Timestamp::now());//截止时间
double seconds = timeDifference(end, start);//计算时间差
//打印到标准输出
printf("%12s: %f seconds, %d bytes, %10.2f msg/s, %.2f MiB/s\n",
type, seconds, g_total, n / seconds, g_total / seconds / (1024 * 1024));
}
void logInThread()
{//线程池当中的线程,更新日志
LOG_INFO << "logInThread";
usleep(1000);
}
int main()
{//获取父进程pid
getppid(); // for ltrace and strace
//建立线程池
muduo::ThreadPool pool("pool");
//线程池启动5个线程
pool.start(5);
//线程池添加5个任务
pool.run(logInThread);
pool.run(logInThread);
pool.run(logInThread);
pool.run(logInThread);
pool.run(logInThread);
//主线程输出日志
LOG_TRACE << "trace";
LOG_DEBUG << "debug";
LOG_INFO << "Hello";
LOG_WARN << "World";
LOG_ERROR << "Error";
LOG_INFO << sizeof(muduo::Logger);
LOG_INFO << sizeof(muduo::LogStream);
LOG_INFO << sizeof(muduo::Fmt);
LOG_INFO << sizeof(muduo::LogStream::Buffer);
//睡眠1秒钟
sleep(1);
//性能测试程序
bench("nop");
char buffer[64*1024];
//空文件,测试数据写入到/dev/null中的性能
g_file = fopen("/dev/null", "w");
setbuffer(g_file, buffer, sizeof buffer);
bench("/dev/null");
fclose(g_file);
//测试数据写入到/tmp/log中的性能
g_file = fopen("/tmp/log", "w");
setbuffer(g_file, buffer, sizeof buffer);
bench("/tmp/log");
fclose(g_file);
g_file = NULL;
//不是线程安全的
g_logFile.reset(new muduo::LogFile("test_log_st", 500*1000*1000, false));
bench("test_log_st");
//线程安全的
g_logFile.reset(new muduo::LogFile("test_log_mt", 500*1000*1000, true));
bench("test_log_mt");
g_logFile.reset();
}