如服务器软件中所述,对于一个需要长期运行的服务器程序,日志功能必不可少。服务器端通用日志库的需求包括:
- 灵活的日志策略
- 因为用于实时程序,需要后台线程写日志
- 对于有界面程序,需要在用户界面显示日志;对于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;}
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;} ;
3. 针对接口编程,这是《设计模式》倡导的最佳实践。公司这方面做得不错,所以即使日志库发生了翻天覆地的变化,客户端程序只需增加AddListener/RemoveListener。