手头在写的几个VCL程序都使用log4cpp 1.0记录日志,其中有一个要求将日志写入Rolling File和一个VCL控件(TRzMemo)。
有两种思路:一是重定向std::cout到VCL控件,二是自己写一个log4cpp的Appender。
将std::cout重定向到文件比较简单:
std::ofstream log("foo.log");
std::streambuf *oldbuf = std::cout.rdbuf(log.rdbuf());
std::cout << "This will output to file" << std::endl;
std::cout.rdbuf(oldbuf); // 还原
将std::cout重定向到VCL控件就比较麻烦,需要将std::basic_streambuf子类化,重写overflow函数,以下代码可以将数据逐行输出到TRzMemo中:
#ifndef MemoBufferH
#define MemoBufferH
#include "RzEdit.hpp"
#include
//---------------------------------------------------------------------------
/*
* 流缓冲子类:逐行输出到TRzMemo
* 将标准输出重定向到TRzMemo的使用方法:
* 程序启动时,写下:
* TMemoBuffer *MemoBuffer = new TMemoBuffer(memMessages);
* streambuf *outbuf = std::cout.rdbuf(MemoBuffer);
* streambuf *errbuf = std::cerr.rdbuf(MemoBuffer);
* 程序退出时,写下:
* std::cout.rdbuf(outbuf);
* std::cerr.rdbuf(errbuf);
*/
class TMemoBuffer : public std::streambuf
{
public:
TMemoBuffer(TRzMemo* mem) : Memo(mem), Pos(0), BufferSize(128)
{
Buffer = new char[BufferSize];
}
~TMemoBuffer()
{
delete [] Buffer;
}
protected:
// central output function
virtual int_type overflow(int_type c)
{
if (c != EOF)
{
// 缓冲区不够用时,自动增长
if(Pos >= BufferSize)
{
BufferSize *= 2;
char* OldBuffer = Buffer;
Buffer = new char[BufferSize];
memcpy(Buffer, OldBuffer, BufferSize / 2);
delete [] OldBuffer;
}
// 遇到行尾时输出到Memo
if(c == '\n')
{
Memo->Lines->Add(AnsiString(Buffer, Pos));
Pos = 0; // Pos指向第一个位置
}
else // 否则,添加到缓冲区
{
Buffer[Pos++] = c;
}
}
return c;
}
private:
TRzMemo* Memo; // 输出到此Memo
char* Buffer; // 行缓冲区
int Pos, BufferSize; // 缓冲区当前位置和长度
};
#endif
现在可以很好地将std::cout重定向到Memo中了。不过与log4cpp配合时却出了问题。
我的log4cpp通过配置文件来驱动,配置文件中有两个Appender:ConsoleAppender和RollingFileAppender。
后者可以正常工作,这里按下不表,前者却没法重定向到Memo中。
查看了PropertyConfiguratorImpl.cpp的内容,在构造ConsoleAppender时实际上是生成了OstreamAppender的实例:
appender = new OstreamAppender(appenderName, &std::cout);
而OstreamAppender在处理输出时调用如下代码:
(*_stream) << _getLayout().format(event);
这里的_stream指向std::cout。
按道理这里做的事情也就是std::cout << “something”;但不知道为什么我的TMemoBuffer::overflow没有被调用。
折腾一阵找不出原因,觉得尝试第二种思路。
编写了一个TMemoAppender : public LayoutAppender,在virtual void _append(const LoggingEvent& event);中将数据输出到TRzMemo中。这么使用TMemoAppender,log4cpp::Category::getRoot().addAppender(new TMemoAppender("name", memPointer));。
编译顺利通过,但程序运行时却怪样迭出:在毫不相关的代码处出现EInvalidPointer异常,或者程序运行到某一步就停住了,程序主窗口也没有显示。
又折腾一阵,还是找不出原因。还是采用第一种思路,但不用配置文件,而是由程序来初始化log4cpp。使用OstreamAppender将数据输出到std::cout。居然成功了,依然找不出原因,代码如下:
// stdout输出重定向到Memo
TMemoBuffer *MemoBuffer = new TMemoBuffer(memMessages);
outbuf = std::cout.rdbuf(MemoBuffer);
errbuf = std::cerr.rdbuf(MemoBuffer);
// 配置Log4cpp,通过OstreamAppender输出到std::cout(进而由TMemoBuffer重定向到Memo),
// 通过RollingFileAppender输出到RollingFile
// 注意:这里不使用配置文件
log4cpp::Category::getRoot().setAdditivity(false);
log4cpp::Category::getRoot().setPriority(log4cpp::Priority::DEBUG);
log4cpp::PatternLayout *Layout = new log4cpp::PatternLayout;
Layout->setConversionPattern("%d [%-5p] %m%n");
log4cpp::OstreamAppender *osAppender = new log4cpp::OstreamAppender("OstreamAppender", &std::cout);
osAppender->setLayout(Layout);
log4cpp::Category::getRoot().setAppender(osAppender);
log4cpp::RollingFileAppender *rfAppender
= new log4cpp::RollingFileAppender(
"RollingFileAppender",
(gBaseDir + "log\jstMockX.log").c_str(),
100000, // maxFileSize
5); // maxBackupIndex
rfAppender->setLayout(Layout);
log4cpp::Category::getRoot().addAppender(rfAppender);
...