我正在为多线程应用程序编写一个日志记录实用程序,我希望能够以类似std :: cout的方式调用它:
主题1:
Logger::log << "First message" << Logger::end;
主题2:
Logger::log << "Second message" << Logger::end;
一旦Logger::end
传递给日志,该消息应该刷新到文件/屏幕/网络/无论日志转到什么。为了处理对日志的并发写入而不混合消息,我的想法是为每个线程创建一个Logger::log
实例,然后这些实例共享对线程安全队列的访问,其中一个工作线程专用于弹出新消息并编写它们文件/屏幕等。
实现的一种方法我想是有一种多单例返回一个实例,具体取决于哪个线程id调用它(从线程id映射到std :: map中存储的日志)。是否有更好和/或更有效的方式?
是否有其他设计而不需要每个线程一个日志实例我忽略了? std :: cout如何处理并发访问?
谢谢!
答案 0 :(得分:3)
你可以使用thread_local单例:
class Logger {
public:
struct Sentinel{};
static thread_local Logger log;
static Sentinel end;
template<class T>
Logger& operator<<(T data) {
stream << data;
return *this;
}
//for endl and so on
Logger& operator<<(std::ostream& (*pf)(std::ostream&)) {
pf(stream);
return *this;
}
private:
Logger(){};
std::stringstream stream;
};
thread_local Logger Logger::log;
Logger::Sentinel Logger::end;
template<>
Logger& Logger::operator<<<Logger::Sentinel>(Logger::Sentinel data) {
stream << std::endl;
std::cout << stream.str();
stream.str("");
return *this;
}
另一种可能的语法:
class Logger_t {
public:
template<class T>
Logger_t& operator<<(T data) {
stream << data;
return *this;
}
//for endl and other stream manipulators
Logger_t& operator<<(std::ostream& (*pf)(std::ostream&)) {
pf(stream);
return *this;
}
void flush() {
std::cout << stream.str();
stream.str("");
}
private:
Logger_t(){};
std::stringstream stream;
friend Logger_t& Logger();
};
Logger_t& Logger() {
thread_local Logger_t logger;
return logger;
}
用法:
int main() {
Logger() << "test1 " << "test2" << std::endl;
Logger() << "test3" << std::endl;
Logger().flush();
Logger() << "test4" << std::endl; // <-- Not flushed
}
输出:
test1 test2
test3
编辑:
我重新回答了我的答案,虽然它展示了一般的想法,但具体的例子有一些注意事项:
std::cout
是 - 默认情况下 - 线程安全,但仍允许对来自operator<<
的多个并行调用的单个字符进行交错。据我所知,这至少不会发生在Ubuntu上的gcc和clang,但要真正可移植,你可能需要保护对std::cout
或任何日志系统使用的任何访问。stream
thread_local
而不是记录器。 答案 1 :(得分:2)
我跳过那个多单身。
拥有全球log
。 log << whatever
生成一个中间日志对象。 intermediate log object << whatever
与其返回值共享中间日志对象的内部状态。
当共享内部状态最终被销毁时,它会以原子方式将其写入注销。
所以每一行(来源)都是原子地发出的。
如果要进行多行日志记录,则必须使用auto&& l = log << whatever
,然后在要添加时使用l << more stuff
。当l
对象被销毁时,它将被发送到日志输出。
对于工业质量:
在log
和intermediate_log
,如果<< X
有效,则ostream << X
应该有效。
intermediate_log
应该存储带有魔术删除器的std::shared_ptr<std::stringstream>
,然后将其输入转发给,然后返回自身的副本(该副本允许使用auto
语法进行有效期延长。)< / p>
魔术删除器应该将stringstream
内容写入实际的输出日志,可能是通过异步队列或其他东西(如果存在大量争用,写入速度很慢)。
答案 2 :(得分:0)
根据直接问题,最合理的方法是在单例内存储类似thread_local
变量的记录器。
但我强烈反对你的想法。您应该直接从消息源线程将日志刷新到文件。否则,当您的程序在消息发送到队列并实际写入光盘之间崩溃时,您可能会丢失一些消息。
答案 3 :(得分:0)
我可能会在这里建议一些简单的解决方案,但我会选择具有线程安全性的常规单例类:
class Logger{
private:
std::mutex mtx;
...
public:
void log (const std::string& logToPrint){
std::lock_guard<std::mutex>(mtx);
//do any logging here..
}
};