muduo库里面的实现日志滚动有两种条件,一种是日志文件大小达到预设值,另一种是时间到达超过当天。滚动日志类的文件是LogFile.cc ,LogFile.h

代码如下:

LogFile.cc

#include <muduo/base/LogFile.h>
#include <muduo/base/Logging.h> // strerror_tl
#include <muduo/base/ProcessInfo.h> #include <assert.h>
#include <stdio.h>
#include <time.h> 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 <muduo/base/Mutex.h>
#include <muduo/base/Types.h> #include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp> 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<MutexLock> mutex_;//互斥量的智能指针
time_t startOfPeriod_;//开始记录日志时间(将会调整至0点时间)
time_t lastRoll_;//上一次滚动日志的时间
time_t lastFlush_;//上一次日志写入文件的时间
class File;//File嵌套类
boost::scoped_ptr<File> file_;//File嵌套类的一个智能指针 const static int kCheckTimeRoll_ = 1024;
const static int kRollPerSeconds_ = 60*60*24;//一天的秒数
}; }
#endif // MUDUO_BASE_LOGFILE_H

测试代码则是输出一系列的日志,进行日志滚动,代码如下:

LogFile_test.cc

//日志滚动测试程序
#include <muduo/base/LogFile.h>
#include <muduo/base/Logging.h> boost::scoped_ptr<muduo::LogFile> 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 <muduo/base/Logging.h>
#include <muduo/base/LogFile.h>
#include <muduo/base/ThreadPool.h> #include <stdio.h> int g_total;
FILE* g_file;//文件指针
boost::scoped_ptr<muduo::LogFile> 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();
}

运行结果如下:

muduo网络库源码学习————日志滚动的更多相关文章

  1. muduo网络库源码学习————日志类封装

    muduo库里面的日志使方法如下 这里定义了一个宏 #define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) ...

  2. muduo网络库源码学习————Timestamp.cc

    今天开始学习陈硕先生的muduo网络库,moduo网络库得到很多好评,陈硕先生自己也说核心代码不超过5000行,所以我觉得有必要拿过来好好学习下,学习的时候在源码上面添加一些自己的注释,方便日后理解, ...

  3. muduo网络库源码学习————线程类

    muduo库里面的线程类是使用基于对象的编程思想,源码目录为muduo/base,如下所示: 线程类头文件: // Use of this source code is governed by a B ...

  4. muduo网络库源码学习————线程池实现

    muduo库里面的线程池是固定线程池,即创建的线程池里面的线程个数是一定的,不是动态的.线程池里面一般要包含线程队列还有任务队列,外部程序将任务存放到线程池的任务队列中,线程池中的线程队列执行任务,也 ...

  5. muduo网络库源码学习————互斥锁

    muduo源码的互斥锁源码位于muduo/base,Mutex.h,进行了两个类的封装,在实际的使用中更常使用MutexLockGuard类,因为该类可以在析构函数中自动解锁,避免了某些情况忘记解锁. ...

  6. muduo网络库源码学习————线程特定数据

    muduo库线程特定数据源码文件为ThreadLocal.h //线程本地存储 // Use of this source code is governed by a BSD-style licens ...

  7. muduo网络库源码学习————线程本地单例类封装

    muduo库中线程本地单例类封装代码是ThreadLocalSingleton.h 如下所示: //线程本地单例类封装 // Use of this source code is governed b ...

  8. muduo网络库源码学习————无界队列和有界队列

    muduo库里实现了两个队列模板类:无界队列为BlockingQueue.h,有界队列为BoundedBlockingQueue.h,两个测试程序实现了生产者和消费者模型.(这里以无界队列为例,有界队 ...

  9. muduo网络库源码学习————线程安全

    线程安全使用单例模式,保证了每次只创建单个对象,代码如下: Singleton.h // Use of this source code is governed by a BSD-style lice ...

随机推荐

  1. javascript入门 之 ztree(二 标准json数据)

    1.代码 <!DOCTYPE html> <HTML> <HEAD> <TITLE> ZTREE DEMO - Standard Data </T ...

  2. android性能测试--CPU、内存

  3. GO中的逃逸分析

    1.什么是逃逸分析 以前写c/c++代码时,为了提高效率,常常将pass-by-value(传值)“升级”成pass-by-reference,企图避免构造函数的运行,并且直接返回一个指针. 那么这里 ...

  4. FastAPI框架

    目录 FastAPI框架 安装 基本使用 模版渲染 安装jinja2 基本使用 form表单数据交互 基本数据 文件交互 静态文件配置 FastAPI框架 该框架的速度(天然支持异步)比一般的djan ...

  5. Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(八)之Reusing Classes

    The trick is to use the classes without soiling the existing code. 1. composition--simply create obj ...

  6. 获取SVG中g标签的宽度高度及位置坐标

    1. 问题的出现 对于普通的HTML元素,有很多获得其宽度width.高度height.距左left.距顶top等属性的方法: 类似offsetWidth,clientWidth,width之类的,通 ...

  7. go开发包下载,IDE工具下载,基础配置命令

    目录 go语言介绍 go开发包下载 命令介绍 配置 修改配置 golandIDE工具下载 编译并执行命令 命令 go语言介绍 # 1 诞生于 2009年,10年的时间,非常新的语言,天然支持并发,很新 ...

  8. Python-气象-大气科学-可视化绘图系列(三)—— 地图上自动标注省会名称(demo调整中)(代码+示例)

    本文为原创文章 本文链接:https://www.cnblogs.com/zhanling/p/12606990.html # -*- coding: utf-8 -*- ''' Author: He ...

  9. Jmeter接口测试、性能测试详细介绍

    下面主要就是讲一下Jmeter工具的用法,用法非常简单,比起loadrunner不知道简单多少,并且开源免费~~ 1.接口简介 接口定义 接口: 就是数据交互的入口和出口,是一套标准规范. 接口(硬件 ...

  10. pyinstaller打包

    参考 官网:http://www.pyinstaller.org/ pyinstaller参数使用 使用spec文件 安装 Windows依赖pypiwin32,新版的pyinstaller已经包含了 ...