Singleton file logger with concurrent queue (C++)

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