如何在C++中重载<<线程安全日志记录的运算符?
How to overload << operator for thread-safe logging in C++?
我正在写一个简单的线程安全日志记录库的草图,我想到了一些事情。代码如下:
#ifndef SimpleLogger_H
#define SimpleLogger_H
#include <iostream>
#include <mutex>
class SimpleLogger
{
public:
template <typename T>
static void log(T message)
{
mutex.lock();
std::cout << message;
mutex.unlock();
}
private:
static std::mutex mutex;
}LOG;
template <typename T>
SimpleLogger &operator<<(SimpleLogger &simpleLogger, T message)
{
simpleLogger.log(message);
return simpleLogger;
}
#endif //SimpleLogger_H
我的想法是像这样使用它:
LOG << "hello" << " world " << 8 << " I can mix integers and strings";
我知道上面的行如下所示:
auto a1 = LOG.operator<<("hello");
auto a2 = a1.operator<<(" world ");
//Another thread may want to use LOG here, and would print in the middle of my message
auto a3 = a2.operator<<(8);
auto a4 = a3.operator<<(" I can mix integers and strings");
如您所见,由于<<
被分解为几个函数调用,因此线程可能会在我的消息中间使用LOG
对象(我认为消息是一行<<
的整个级联(
另外,有没有办法为最后一次<<
呼叫自动添加std::endl
?我想不出一种方法来做到这一点,但我看到一些日志记录库具有此功能
如何解决这两个问题?
我知道最好使用日志记录库,但我想在一个简单的库中混合 android、桌面和 ios 日志记录,而无需高性能,而且我也对如何克服我在编写自己的时遇到的困难感到困惑
正如其他人已经提到的,在发送到日志文件之前,您需要一个本地缓冲区来收集消息。在下面的示例中,SimpleLoggerBuffer 对象被设计为仅用作临时变量。即它在表达式结束时被破坏。析构函数将缓冲区刷新到日志中,这样您就不必显式调用刷新函数(如果您愿意,也可以在那里添加 endl(
#include <iostream>
#include <sstream>
#include <mutex>
using namespace std;
class SimpleLogger
{
public:
template <typename T>
static void log(T& message)
{
mutex.lock();
std::cout << message.str();
message.flush();
mutex.unlock();
}
private:
static std::mutex mutex;
}LOG;
std::mutex SimpleLogger::mutex;
struct SimpleLoggerBuffer{
stringstream ss;
SimpleLoggerBuffer() = default;
SimpleLoggerBuffer(const SimpleLoggerBuffer&) = delete;
SimpleLoggerBuffer& operator=(const SimpleLoggerBuffer&) = delete;
SimpleLoggerBuffer& operator=(SimpleLoggerBuffer&&) = delete;
SimpleLoggerBuffer(SimpleLoggerBuffer&& buf): ss(move(buf.ss)) {
}
template <typename T>
SimpleLoggerBuffer& operator<<(T&& message)
{
ss << std::forward<T>(message);
return *this;
}
~SimpleLoggerBuffer() {
LOG.log(ss);
}
};
template <typename T>
SimpleLoggerBuffer operator<<(SimpleLogger &simpleLogger, T&& message)
{
SimpleLoggerBuffer buf;
buf.ss << std::forward<T>(message);
return buf;
}
int main() {
LOG << "hello" << " world " << 8 << " I can mix integers and strings";
}
您可以创建一个帮助程序类,该类收集所有输出并在销毁时打印。大纲:
#include <string>
#include <iostream>
struct Msg;
struct Log {
void print(const Msg &m);
};
struct Msg {
std::string m;
Log &l;
Msg(Log &l) : l(l) {}
~Msg() {
// Print the message on destruction
l.print(*this);
}
};
void Log::print(const Msg &m) {
// Logger specific printing... here, append newline
std::cout << m.m << std::endl;
}
Msg &&operator << (Msg &&m, const std::string &s) {
// Append operator
m.m += s;
return std::move(m);
}
// Helper to log on a specific logger. Just creates the initial message
Msg log(Log &l) { return Msg(l); }
int main()
{
Log l;
log(l) << "a" << "b" << "c";
return 0;
}
由于消息是本地的,因此其他线程不会干扰它。任何必要的锁定都可以在Log.print
方法中完成,该方法将接收完整的消息
一个简单的解决方案是写入文件而不是标准输出,特别是每个线程的单独文件。这样就不需要锁定或任何其他同步。如果行具有可解析的格式,则可以稍后合并这些文件。
另一种解决方案是从单个线程异步写入日志,并最初将消息存储在线程安全(可能无锁(队列中。
另外,有没有办法为最后<<呼叫自动添加 std::endl?
除非我误解,否则你可以简单地做stream << message << std::endl
.
我认为你可以简单地使用std::clog。它是线程安全的,与 std::cout 相反,旨在立即输出以进行日志记录。 从参考页面:
除非已发出sync_with_stdio(假(,否则可以安全地 同时从多个线程访问这些对象 格式化和未格式化输出。
我向你推荐这个杰森·特纳关于cout,堵塞和错误的视频。
最简单的方法是从第一个<<
返回一个临时代理 - 这可以记录您的流(并在销毁时解锁(,或者只是构建一个本地 ostringstream 并在单个调用中刷新它(再次,在销毁时(。
在日志记录时保持锁定对性能不是很好(尽管它比现有的日志方法更好,后者应使用std::lock_guard
以确保异常安全(。
构建和丢弃临时 ostringstream 可能更好,但如果你关心性能,则需要进行基准测试,并且最终可能需要更复杂的东西(每线程循环缓冲区、mmap 文件或其他东西(。
- 请解释这句话(cout<<1+int((a<b)^((b-a)&1) )<<endl
- 呼叫运营商<<临时
- 将两个数组中的差异记录在第三个数组中
- 如何防止clang格式在流运算符调用之间添加换行符<<
- <<操作员在下面的行中工作
- 禁止在控制台上记录谷神星
- 创建 Spdlog 异步文件记录器时遇到困难
- fastrtps:如何在发布/订阅级别使用 DDS 历史记录?
- 如何将自定义记录器与websocketpp一起使用?
- C++结构到德尔福记录dll调用
- 如何判断SSL_read是否已经接收并处理了来自单个消息的所有记录
- 如何实现具有多个平台__FILE__和__LINE__信息的 C/C++ 可变参数日志记录宏?
- 我是否可以使用 win32 句柄以编程方式记录发送到/接收到 USB/COM 的内容
- 使用 Doxygen 记录枚举类值,而不启用EXTRACT_ALL
- 在C++中隐藏键盘记录器的控制台窗口
- 堆栈上的最大激活记录数
- 记录Doxygen中的命名
- 为什么Qt Creator的应用程序输出不能从spdlog记录器打印
- 在SQLITE数据库中写入记录需要花费大量时间.如何提高刀片操作效率?
- Qt 错误:QSqlQuery::value:尝试从表中检索统计信息时未定位在有效记录上 (QComboBox)