目前log4cpp的原始项目主页在 http://log4cpp.sourceforge.net/
最新的更新在2017年,且更新的频率相对较慢
在github上有一个orocos-toolchain维护的版本,https://github.com/orocos-toolchain/log4cpp
此处以原始项目的代码为蓝本。
公司的使用习惯为文本配置,使用log4cpp::PropertyConfigurator::configure(_cfg_file); 来获取配置文件,并解析
包装类的git地址为:https://github.com/zhoutong12589/log4cpp_pack
此项目包括了一个使用的demo
1、配置解析
配置解析与初始化主要在PropertyConfiguratorImpl::doConfigure(std::istream& in)
void PropertyConfiguratorImpl::doConfigure(std::istream& in) {
// parse the file to get all of the configuration
_properties.load(in);
instantiateAllAppenders();
// get categories
std::vector<std::string> catList;
getCategories(catList);
// configure each category
for(std::vector<std::string>::const_iterator iter = catList.begin();
iter != catList.end(); ++iter) {
configureCategory(*iter);
}
}
_properties.load(in)
读取配置文件,按照行来进行读取,
// strip off the "log4j" or "log4cpp"
length = leftSide.find('.');
if (leftSide.substr(0, length) == "log4j" ||
leftSide.substr(0, length) == "log4cpp")
leftSide = leftSide.substr(length + 1);
代码说明,可以以log4j或者log4cpp或者直接使用配置
instantiateAllAppenders();
按照顺序初始化各个appender
在该函数中调用PropertyConfiguratorImpl::instantiateAppender(const std::string& appenderName)
显示log4cpp支持的appender种类为ConsoleAppender、FileAppender、RollingFileAppender、DailyRollingFileAppender、SyslogAppender、LocalSyslogAppender、AbortAppender、Win32DebugAppender、NTEventLogAppender
PropertyConfiguratorImpl::setLayout(Appender* appender, const std::string& appenderName)
表明log4cpp支持三种layout
BasicLayout、SimpleLayout、PatternLayout
void PropertyConfiguratorImpl::getCategories(std::vector<std::string>& categories) const {
categories.clear();
// add the root category first
categories.push_back(std::string("rootCategory"));
// then look for "category."
std::string prefix("category");
Properties::const_iterator from = _properties.lower_bound(prefix + '.');
Properties::const_iterator to = _properties.lower_bound(prefix + (char)('.' + 1));
for (Properties::const_iterator iter = from; iter != to; iter++) {
categories.push_back((*iter).first.substr(prefix.size() + 1));
}
}
必须包含rootCategory,程序中默认就直接将该选项包含
可以包含子category
PropertyConfiguratorImpl::configureCategory(const std::string& categoryName)
第一步获取日志级别
Priority::Value priority = Priority::NOTSET;
if (i != iEnd) {
std::string priorityName = StringUtil::trim(*i++);
try {
if (priorityName != "") {
priority = Priority::getPriorityValue(priorityName);
}
} catch(std::invalid_argument& e) {
throw ConfigureFailure(std::string(e.what()) +
" for category '" + categoryName + "'");
}
}
try {
category.setPriority(priority);
} catch (std::invalid_argument& e) {
throw ConfigureFailure(std::string(e.what()) +
" for category '" + categoryName + "'");
}
第二部,将appender加入category中
for(/**/; i != iEnd; ++i) {
std::string appenderName = StringUtil::trim(*i);
AppenderMap::const_iterator appIt =
_allAppenders.find(appenderName);
if (appIt == _allAppenders.end()) {
// appender not found;
throw ConfigureFailure(std::string("Appender '") +
appenderName + "' not found for category '" + categoryName + "'");
} else {
/* pass by reference, i.e. don't transfer ownership
*/
category.addAppender(*((*appIt).second));
}
}
2、写日志
如info级别
void Category::info(const char* stringFormat, ...) throw() {
if (isPriorityEnabled(Priority::INFO)) {
va_list va;
va_start(va,stringFormat);
_logUnconditionally(Priority::INFO, stringFormat, va);
va_end(va);
}
}
void Category::info(const std::string& message) throw() {
if (isPriorityEnabled(Priority::INFO))
_logUnconditionally2(Priority::INFO, message);
}
表明是支持可变参数的,首先判断是否达到相应的写日志解级别,然后写日志
创建日志事件,并调用相应的appender
void Category::_logUnconditionally2(Priority::Value priority,
const std::string& message) throw() {
LoggingEvent event(getName(), message, NDC::get(), priority);
callAppenders(event);
}
此处有线程锁,确保线程安全,但additivity的具体作用暂时未知
void Category::callAppenders(const LoggingEvent& event) throw() {
threading::ScopedLock lock(_appenderSetMutex);
{
if (!_appender.empty()) {
for(AppenderSet::const_iterator i = _appender.begin();
i != _appender.end(); i++) {
(*i)->doAppend(event);
}
}
}
if (getAdditivity() && (getParent() != NULL)) {
getParent()->callAppenders(event);
}
}
这里的doAppend是接口函数,AppenderSkeleton基类拥有虚函数。
virtual void _append(const LoggingEvent& event) = 0;
class LOG4CPP_EXPORT FileAppender : public LayoutAppender
class LOG4CPP_EXPORT LayoutAppender : public AppenderSkeleton
通过类的继承来实现最终调用相应的appender,比如上面的fileAppender
FileAppender直接调用write来写文件,直到卸完成才返回。
void FileAppender::_append(const LoggingEvent& event) {
std::string message(_getLayout().format(event));
if (!::write(_fd, message.data(), message.length())) {
// XXX help! help!
}
}
RollingFileAppender通过每次调用都获取文件的大下,然后调用FileAppender来实现固定大小文件的回滚
void RollingFileAppender::_append(const LoggingEvent& event) {
FileAppender::_append(event);
off_t offset = ::lseek(_fd, 0, SEEK_END);
if (offset < 0) {
// XXX we got an error, ignore for now
} else {
if(static_cast<size_t>(offset) >= _maxFileSize) {
rollOver();
}
}
}
每次文件名的改写都是一个过程
void RollingFileAppender::rollOver() {
::close(_fd);
if (_maxBackupIndex > 0) {
std::ostringstream filename_stream;
filename_stream << _fileName << "." << std::setw( _maxBackupIndexWidth ) << std::setfill( '0' ) << _maxBackupIndex << std::ends;
// remove the very last (oldest) file
std::string last_log_filename = filename_stream.str();
// std::cout << last_log_filename << std::endl; // removed by request on sf.net #140
::remove(last_log_filename.c_str());
// rename each existing file to the consequent one
for(unsigned int i = _maxBackupIndex; i > 1; i--) {
filename_stream.str(std::string());
filename_stream << _fileName << '.' << std::setw( _maxBackupIndexWidth ) << std::setfill( '0' ) << i - 1 << std::ends; // set padding so the files are listed in order
::rename(filename_stream.str().c_str(), last_log_filename.c_str());
last_log_filename = filename_stream.str();
}
// new file will be numbered 1
::rename(_fileName.c_str(), last_log_filename.c_str());
}
_fd = ::open(_fileName.c_str(), _flags, _mode);
}
DailyRollingFileAppender根据时间来进行切割,这里使用localtime_s是因为localtime是一个不可重入的函数,具体可以参考“man localtime”
void DailyRollingFileAppender::_append(const log4cpp::LoggingEvent &event)
{
struct tm now;
time_t t = time(NULL);
#ifndef WIN32 // only available on Win32
bool timeok = localtime_r(&t, &now) != NULL;
#else
bool timeok = localtime_s(&now, &t) == 0;
#endif
if (timeok) {
if ((now.tm_mday != _logsTime.tm_mday) ||
(now.tm_mon != _logsTime.tm_mon) ||
(now.tm_year != _logsTime.tm_year)) {
rollOver();
_logsTime = now;
}
}
log4cpp::FileAppender::_append(event);
}
由此可见,DailyRollingFileAppender是按照天来切割,年、月、日,有一个不同,则需要进行rollOver,然后调用FileAppender进行写入。
如果强调日志不影响执行,则建议给log4cpp加入缓冲队列来使用,或者通过改造log4cpp的代码来完成。
接下来的计划是分析ACE的Task的原理,并剥离出来,和log4cpp结合使用,再使用C11中的线程和各种互斥、原子操作来实现相关的队列和写日志,可参考glog来改造log4cpp来优化日志的速度。