如何将类似 iostream 的接口写入日志库?
How to write iostream-like interface to logging library?
我想为我非常简单的日志记录库编写一个方便的界面。拿下面两段代码。第一个是我现在做的,第二个是我对直观界面的想法:
std::ostringstream stream;
stream<<"Some text "<<and_variables<<" formated using standard string stream"
logger.log(stream.str()); //then passed to the logger
和
logger.convinient_log()<<"Same text "<<with_variables<<" but passed directly";
我在这个想法背后的思想设计过程是 return 来自 logger.convinient_log()
函数的某种类似字符串流的临时对象。销毁对象(我希望它发生在行尾或类似的、方便的地方)将从自身收集字符串并调用实际的 logger.log()
。关键是我想整体处理它,而不是逐个术语,以便 log() 可以添加例如。整行文本的前缀和后缀。
我很清楚,如果没有一些强大的魔法,这可能是完全不可能或不可能的。如果是这样的话,什么是几乎一样方便的方式来做到这一点以及如何实施它?我打赌会传递一些特殊变量,这些变量会强制执行 collect-call-logger.log()
操作。
如果您不知道确切答案,也欢迎提供有关该主题的资源(例如,扩展 stringstream)。
例如,Boost.Log 就是这样工作的。基本思路很简单:
struct log
{
log() {
uncaught = std::uncaught_exceptions();
}
~log() {
if (uncaught >= std::uncaught_exceptions()) {
std::cout << "prefix: " << stream.str() << " suffix\n";
}
}
std::stringstream stream;
int uncaught;
};
template <typename T>
log& operator<<(log& record, T&& t) {
record.stream << std::forward<T>(t);
return record;
}
template <typename T>
log& operator<<(log&& record, T&& t) {
return record << std::forward<T>(t);
}
// Usage:
log() << "Hello world! " << 42;
std::uncaught_exceptions()
用于避免在中间抛出异常时记录不完整的消息。
创建一个派生自 std::basic_streambuf
的自定义 class 以写入您的记录器,例如:
class LoggerBuf : public std::stringbuf
{
private:
Logger logger;
public:
LoggerBuf(params) : std::stringbuf(), logger(params) {
...
}
virtual int sync() {
int ret = std::stringbuf::sync();
logger.log(str());
return ret;
}
};
然后你可以实例化一个 std::basic_ostream
对象,给它一个指向 LoggerBuf
对象的指针,例如:
LoggerBuf buff(params);
std::ostream stream(&buf);
stream << "Some text " << and_variables << " formated using standard string stream";
stream << std::flush; // only if you need to log before the destructor is called
或者,从 std::basic_ostream
派生自定义 class 来包装您的 LoggerBuf
class,例如:
class logger_ostream : public std::ostream
{
private:
LoggerBuf buff;
public:
logger_ostream(params) : std:ostream(), buff(params)
{
init(&buff);
}
};
std::logger_ostream logger(params);
logger << "Some text " << and_variables << " formated using standard string stream";
logger << std::flush; // only if you need to log before the destructor is called
这是 class 我前一阵子在一起的。听起来你要找的就是这个。我能够在没有任何令人生畏的 ostreams、stream_buf 或其他任何继承的情况下实现它。只要捕获刷新,您就可以写入文件、控制台、套接字或任何您想要的内容。
它不适用于 ostream_iterators 但可以很好地处理所有 io_manip 功能。
用法:
Logger log;
int age = 32;
log << "Hello, I am " << age << " years old" << std::endl;
log << "That's " << std::setbase(16) << age << " years in hex" << std::endl;
log(Logger::ERROR) << "Now I'm logging an error" << std::endl;
log << "However, after a flush/endl, the error will revert to INFO" << std::end;
实施
#include <iostream>
#include <sstream>
#include <string>
class Logger
{
public:
typedef std::ostream& (*ManipFn)(std::ostream&);
typedef std::ios_base& (*FlagsFn)(std::ios_base&);
enum LogLevel
{
INFO,
WARN,
ERROR
};
Logger() : m_logLevel(INFO) {}
template<class T> // int, double, strings, etc
Logger& operator<<(const T& output)
{
m_stream << output;
return *this;
}
Logger& operator<<(ManipFn manip) /// endl, flush, setw, setfill, etc.
{
manip(m_stream);
if (manip == static_cast<ManipFn>(std::flush)
|| manip == static_cast<ManipFn>(std::endl ) )
this->flush();
return *this;
}
Logger& operator<<(FlagsFn manip) /// setiosflags, resetiosflags
{
manip(m_stream);
return *this;
}
Logger& operator()(LogLevel e)
{
m_logLevel = e;
return *this;
}
void flush()
{
/*
m_stream.str() has your full message here.
Good place to prepend time, log-level.
Send to console, file, socket, or whatever you like here.
*/
m_logLevel = INFO;
m_stream.str( std::string() );
m_stream.clear();
}
private:
std::stringstream m_stream;
int m_logLevel;
};
我想为我非常简单的日志记录库编写一个方便的界面。拿下面两段代码。第一个是我现在做的,第二个是我对直观界面的想法:
std::ostringstream stream;
stream<<"Some text "<<and_variables<<" formated using standard string stream"
logger.log(stream.str()); //then passed to the logger
和
logger.convinient_log()<<"Same text "<<with_variables<<" but passed directly";
我在这个想法背后的思想设计过程是 return 来自 logger.convinient_log()
函数的某种类似字符串流的临时对象。销毁对象(我希望它发生在行尾或类似的、方便的地方)将从自身收集字符串并调用实际的 logger.log()
。关键是我想整体处理它,而不是逐个术语,以便 log() 可以添加例如。整行文本的前缀和后缀。
我很清楚,如果没有一些强大的魔法,这可能是完全不可能或不可能的。如果是这样的话,什么是几乎一样方便的方式来做到这一点以及如何实施它?我打赌会传递一些特殊变量,这些变量会强制执行 collect-call-logger.log()
操作。
如果您不知道确切答案,也欢迎提供有关该主题的资源(例如,扩展 stringstream)。
例如,Boost.Log 就是这样工作的。基本思路很简单:
struct log
{
log() {
uncaught = std::uncaught_exceptions();
}
~log() {
if (uncaught >= std::uncaught_exceptions()) {
std::cout << "prefix: " << stream.str() << " suffix\n";
}
}
std::stringstream stream;
int uncaught;
};
template <typename T>
log& operator<<(log& record, T&& t) {
record.stream << std::forward<T>(t);
return record;
}
template <typename T>
log& operator<<(log&& record, T&& t) {
return record << std::forward<T>(t);
}
// Usage:
log() << "Hello world! " << 42;
std::uncaught_exceptions()
用于避免在中间抛出异常时记录不完整的消息。
创建一个派生自 std::basic_streambuf
的自定义 class 以写入您的记录器,例如:
class LoggerBuf : public std::stringbuf
{
private:
Logger logger;
public:
LoggerBuf(params) : std::stringbuf(), logger(params) {
...
}
virtual int sync() {
int ret = std::stringbuf::sync();
logger.log(str());
return ret;
}
};
然后你可以实例化一个 std::basic_ostream
对象,给它一个指向 LoggerBuf
对象的指针,例如:
LoggerBuf buff(params);
std::ostream stream(&buf);
stream << "Some text " << and_variables << " formated using standard string stream";
stream << std::flush; // only if you need to log before the destructor is called
或者,从 std::basic_ostream
派生自定义 class 来包装您的 LoggerBuf
class,例如:
class logger_ostream : public std::ostream
{
private:
LoggerBuf buff;
public:
logger_ostream(params) : std:ostream(), buff(params)
{
init(&buff);
}
};
std::logger_ostream logger(params);
logger << "Some text " << and_variables << " formated using standard string stream";
logger << std::flush; // only if you need to log before the destructor is called
这是 class 我前一阵子在一起的。听起来你要找的就是这个。我能够在没有任何令人生畏的 ostreams、stream_buf 或其他任何继承的情况下实现它。只要捕获刷新,您就可以写入文件、控制台、套接字或任何您想要的内容。
它不适用于 ostream_iterators 但可以很好地处理所有 io_manip 功能。
用法:
Logger log;
int age = 32;
log << "Hello, I am " << age << " years old" << std::endl;
log << "That's " << std::setbase(16) << age << " years in hex" << std::endl;
log(Logger::ERROR) << "Now I'm logging an error" << std::endl;
log << "However, after a flush/endl, the error will revert to INFO" << std::end;
实施
#include <iostream>
#include <sstream>
#include <string>
class Logger
{
public:
typedef std::ostream& (*ManipFn)(std::ostream&);
typedef std::ios_base& (*FlagsFn)(std::ios_base&);
enum LogLevel
{
INFO,
WARN,
ERROR
};
Logger() : m_logLevel(INFO) {}
template<class T> // int, double, strings, etc
Logger& operator<<(const T& output)
{
m_stream << output;
return *this;
}
Logger& operator<<(ManipFn manip) /// endl, flush, setw, setfill, etc.
{
manip(m_stream);
if (manip == static_cast<ManipFn>(std::flush)
|| manip == static_cast<ManipFn>(std::endl ) )
this->flush();
return *this;
}
Logger& operator<<(FlagsFn manip) /// setiosflags, resetiosflags
{
manip(m_stream);
return *this;
}
Logger& operator()(LogLevel e)
{
m_logLevel = e;
return *this;
}
void flush()
{
/*
m_stream.str() has your full message here.
Good place to prepend time, log-level.
Send to console, file, socket, or whatever you like here.
*/
m_logLevel = INFO;
m_stream.str( std::string() );
m_stream.clear();
}
private:
std::stringstream m_stream;
int m_logLevel;
};