Advantages
- … of a singleton is that you don’t have to pass it into every class you want to log something.
- … of a concurrent_queue is that you don’t block the calling thread with logging into the file.
Code
#include "tbb/concurrent_queue.h"
#include <atomic>
#include <condition_variable>
#include <fstream>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
#define FLOG(msg) FileLogger::instance().log(msg)
class FileLogger
{
public:
static FileLogger& instance(const std::string& filename = "")
{
static FileLogger instance(filename); // thread-safe since C++11
return instance;
}
void log(const std::string& line)
{
queue_.push(line);
cv_.notify_one();
}
private:
FileLogger(const std::string& filename)
{
if (file_.is_open())
{
std::cout << "File already opened: " << filename << "\n";
return;
}
file_.open(filename, std::ios::out | std::ios::trunc);
if (!file_.is_open())
{
std::cout << "Can't open file: " << filename << "\n";
return;
}
running_.store(true);
thread_ = std::thread([this]
{
while (running_.load())
{
std::unique_lock<std::mutex> cv_lock(cv_mutex_);
cv_.wait(cv_lock, [this] {
return !queue_.empty() || !running_.load();
});
std::string line;
while (running_.load() && queue_.try_pop(line))
{
file_ << line << "\n";
}
}
});
}
~FileLogger()
{
if (!running_.load())
{
std::cout << "No logging happening\n";
}
else
{
running_.store(false);
cv_.notify_all();
if (thread_.joinable())
{
thread_.join();
}
std::string line;
while (queue_.try_pop(line))
{
file_ << line << "\n";
}
}
if (!file_.is_open())
{
std::cout << "No file opened\n";
return;
}
file_.close();
}
FileLogger(const FileLogger&) = delete;
FileLogger& operator=(const FileLogger&) = delete;
private:
std::ofstream file_;
std::atomic<bool> running_{};
std::thread thread_;
std::mutex cv_mutex_;
std::condition_variable cv_;
tbb::concurrent_queue<std::string> queue_;
};
int main()
{
FileLogger::instance("log.txt");
FLOG("Log message 1");
FLOG("Log message 2");
return 0;
}
$ g++ main.cpp -ltbb -lpthread