如服务器软件中所述,对于一个需要长期运行的服务器程序,日志功能必不可少。服务器端通用日志库的需求包括:
- 灵活的日志策略
- 因为用于实时程序,需要后台线程写日志
- 对于有界面程序,需要在用户界面显示日志;对于daemon程序,需要通过socket将日志发送给监控程序。
原有的日志库存在以下问题:
- 写文件、控制格式、文件大小等都是自己编码的。日志策略不够灵活,历史久远无人维护;
- 启动线程采用的是beginthread,既与Windows平台绑定,又不是面向对象的;
- 在用户界面日志或socket发送日志功能,和后台写日志文件混在一起。
小鸡射手在重写日志库的工作中得到一些感悟:
1. 尽量不要自己写程序。小鸡射手采用了开源软件log4cpp实现日志灵活性问题,采用ACE的Task Framework实现后台线程功能。其中log4cpp代码很简明:
void
Log4cppListener::Error(
const
char
*
const
message)
... {
assert(message != 0);
get_log4cpp_log().error(message);
}
static inline log4cpp::Category & get_log4cpp_log()
... {
if (g_log == 0)
...{
g_log = & initial_log4cpp();
}
return *g_log;
}
static log4cpp::Category & initial_log4cpp()
... {
try
...{
log4cpp::PropertyConfigurator::configure(DEFAULT_CONF_FILE);
return log4cpp::Category::getRoot();
}
catch(log4cpp::ConfigureFailure& f)
...{
std::cout << "配置文件错误:" << f.what() << std::endl;
return defualt_log();
}
catch(...)
...{
return defualt_log();
}
}
static log4cpp::Category & defualt_log()
... {
log4cpp::Layout* layout = new log4cpp::BasicLayout();
log4cpp::RollingFileAppender* appender = new log4cpp::RollingFileAppender("FileAppender", DEFAULT_LOG_FILE, DEFAULT_LOG_SIZE, DEFAULT_MAX_FILES);
appender->setLayout(layout);
log4cpp::Category& log = log4cpp::Category::getInstance("log4cpp");
log.setAdditivity(false);
log.setAppender(appender);
log.setPriority(log4cpp::Priority::INFO);
return log;
}
... {
assert(message != 0);
get_log4cpp_log().error(message);
}
static inline log4cpp::Category & get_log4cpp_log()
... {
if (g_log == 0)
...{
g_log = & initial_log4cpp();
}
return *g_log;
}
static log4cpp::Category & initial_log4cpp()
... {
try
...{
log4cpp::PropertyConfigurator::configure(DEFAULT_CONF_FILE);
return log4cpp::Category::getRoot();
}
catch(log4cpp::ConfigureFailure& f)
...{
std::cout << "配置文件错误:" << f.what() << std::endl;
return defualt_log();
}
catch(...)
...{
return defualt_log();
}
}
static log4cpp::Category & defualt_log()
... {
log4cpp::Layout* layout = new log4cpp::BasicLayout();
log4cpp::RollingFileAppender* appender = new log4cpp::RollingFileAppender("FileAppender", DEFAULT_LOG_FILE, DEFAULT_LOG_SIZE, DEFAULT_MAX_FILES);
appender->setLayout(layout);
log4cpp::Category& log = log4cpp::Category::getInstance("log4cpp");
log.setAdditivity(false);
log.setAppender(appender);
log.setPriority(log4cpp::Priority::INFO);
return log;
}
2. 设计符合Open-Close原则,即Open for Extension, Close for Modification。小鸡射手设计了ILogListener接口,各类日志功能实现该接口并通过Observer模式组装,具体日志实现和日志框架相分离;
class
ILogListener
... {
public:
virtual ~ILogListener() ...{}
virtual void Error(const char* const message)=0;
virtual void Warning(const char* const message)=0;
virtual void Trace(const char* const message)=0;
virtual void Always(const char* const message)=0;
} ;
... {
public:
virtual ~ILogListener() ...{}
virtual void Error(const char* const message)=0;
virtual void Warning(const char* const message)=0;
virtual void Trace(const char* const message)=0;
virtual void Always(const char* const message)=0;
} ;
3. 针对接口编程,这是《设计模式》倡导的最佳实践。公司这方面做得不错,所以即使日志库发生了翻天覆地的变化,客户端程序只需增加AddListener/RemoveListener。