本文为博主原创文章,未经博主允许不得转载。(合作洽谈请联系QQ:1010316426)
缘起:
笔者的项目产品上一般使用C风格的库记录运行日志,日志库的接口是如printf样式。近日,需要引入其他模块(不同途径获取)的代码,但是发现这个模块记录日志是使用C++的流式风格。因代码里巨大,手动修改费时费力(不符合程序员做事风格),于是笔者想到是否可以将流式的log只通过替换其日志宏 从而实现在不修改代码的前提下将流式转为printf式,从而完成log部分的整合。
//printf风格
void write_log(const char* sFormat, ...);
//流式风格
#define write_log /*写日志的宏*/
write_log<<"my log info"<<std::endl;
方案:
笔者想定义从std :: ostream公开派生出的MyOStream,从而实现能够在MyOStream上使用operator <<和write()以及put()并使用我的类的扩展功能。
通过阅读glog的代码,发现实现起来实际上并不那么难。基本上只是对std::ostream和std::streambuf对象进行子类化,并将自身构造为缓冲区。将为发送到流的每个字符调用std :: streambuf中的虚拟overflow()。
一个简单的示例可以如下来做:
struct MyOstream : std::ostream, std::streambuf
{
MyOstream() : std::ostream(this) {}
int overflow(int c)
{
foo(c);
return 0;
}
void foo(char c)
{
std::cout.put(c);
}
};
void test_my_ostream2()
{
MyOstream b;
b << "Look a number:0x" << std::hex << 39 << std::endl;
}
实现:
特别的,glog中的实现比这个要复杂不少,但是原理还是如此。下面是笔者从glog中剥离抽取出的相关核心类代码:
const size_t kMaxLogMessageLen = 30000;
const char* const_basename(const char* filepath) {
const char* base = strrchr(filepath, '/');
if (!base)
base = strrchr(filepath, '\\');
return base ? (base + 1) : filepath;
}
class LogStreamBuf : public std::streambuf {
public:
// REQUIREMENTS: "len" must be >= 2 to account for the '\n' and '\0'.
LogStreamBuf(char* buf, int len) {
setp(buf, buf + len - 2);
}
// This effectively ignores overflow.
virtual int_type overflow(int_type ch) {
return ch;
}
// Legacy public ostrstream method.
size_t pcount() const { return pptr() - pbase(); }
char* pbase() const { return std::streambuf::pbase(); }
};
class LogStream : public std::ostream {
public:
LogStream(char* buf, int len, int ctr)
: std::ostream(NULL),
streambuf_(buf, len),
ctr_(ctr),
self_(this) {
rdbuf(&streambuf_);
}
int ctr() const { return ctr_; }
void set_ctr(int ctr) { ctr_ = ctr; }
LogStream* self() const { return self_; }
// Legacy std::streambuf methods.
size_t pcount() const { return streambuf_.pcount(); }
char* pbase() const { return streambuf_.pbase(); }
char* str() const { return pbase(); }
private:
LogStream(const LogStream&);
LogStream& operator=(const LogStream&);
LogStreamBuf streambuf_;
int ctr_; // Counter hack (for the LOG_EVERY_X() macro)
LogStream* self_; // Consistency check hack
};
struct LogMessageData {
LogMessageData()
: stream_(message_text_, kMaxLogMessageLen, 0) {
};
char message_text_[kMaxLogMessageLen + 1];
LogStream stream_;
char severity_; // What level is this LogMessage logged at?
int line_; // line number where logging call is.
size_t num_chars_to_log_; // # of chars of msg to send to log
const char* basename_; // basename of file that called LOG
const char* fullname_; // fullname of file that called LOG
private:
LogMessageData(const LogMessageData&);
void operator=(const LogMessageData&);
};
class LogMessage {
public:
LogMessage(const char* file, int line, int severity)
{
//Init(file, line, severity, &LogMessage::SendToLog);
data_ = new LogMessageData;
data_->basename_ = const_basename(file);
data_->fullname_ = file;
data_->line_ = line;
data_->severity_ = severity;
}
~LogMessage() {
data_->num_chars_to_log_ = data_->stream_.pcount();
data_->message_text_[data_->num_chars_to_log_+1] = '\0';
std::cout << "hahaha>>" << data_->basename_ << ":" << data_->line_ << " " << data_->message_text_ << "\r\n";
delete data_;
}
std::ostream& stream() {
return data_->stream_;
}
LogMessageData* data_;
private:
LogMessage(const LogMessage&);
void operator=(const LogMessage&);
};
void test_my_ostream()
{
LogMessage(__FILE__, __LINE__, 0).stream() << " i am tester! ";
}
1.在 LogMessage 的构造函数中创建LogMessageData对象;
2.使用LogMessage::stream(),利用operator<< 可以将流式内容缓冲到LogMessageData::message_text_ 之中;
3.在LogMessage析构函数中,笔者使用std::cout将缓冲区打印出来。读者也可以尝试换成其他的方式处理,比如调用fwrite/写log等等。例如,glog中就使用SendToLog/SendToSinkAndLog/SendToSink/SaveOrSendToLog/WriteToStringAndLog等很多种函数。