[c++] Asynchronous print service log

Advantages of asynchronously printing logs

Log service is a common background service, which usually requires a lot of log output to record various operations and events. During the running of the service, frequently outputting log information will bring a certain performance overhead, reduce the throughput and response speed of the service, and may even cause the service to crash.

Therefore, printing logs asynchronously can solve this problem. The basic idea of asynchronously printing logs is: put the log information to be printed into a buffer queue first, and let another thread read the log information in the queue and write it into the log file. In this way, the process of log output can be asynchronous, avoiding the impact of frequent file operations on service performance.

Specifically, the advantages of asynchronously printing logs include:

  • 1. Reduce interference to the main thread: Printing logs directly in the main thread will reduce the response speed of the service, while printing logs asynchronously can separate the process of log output from the main thread without causing too much interference to the main thread.

  • 2. Improve service throughput: Asynchronous printing of logs can separate the process of log output from the main thread, which can avoid the impact of frequent file operations on service performance, thereby improving service throughput.

  • 3. Increase the robustness of the program: Asynchronous printing of logs can separate the process of log output from the main thread, so that even if an exception occurs during the process of printing logs, it will not affect the operation of the main thread, thereby increasing the robustness of the program.

In short, asynchronously printing logs can improve the performance and stability of the service, and is a very worthwhile optimization solution.

Code sample

#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

class AsyncLogger {<!-- -->
public:
    // Constructor, initialize the log file name and the maximum length of the queue, and open the log file stream
    AsyncLogger(const std::string & log_filename, size_t max_queue_size = 10000) :
        m_max_queue_size(max_queue_size),
        m_log_stream(log_filename, std::ios::app)
    {<!-- -->
        // start worker thread
        m_worker_thread = std::thread( &AsyncLogger::worker_func, this);
    }

    // Destructor, end worker thread
    ~AsyncLogger() {<!-- -->
        // Set the m_done flag to notify the worker thread to exit
        m_done = true;
        // Notify all waiting threads that the worker thread ends
        m_cv. notify_all();
        // wait for the worker thread to exit
        m_worker_thread. join();
    }

    // log function, add log information to the queue
    void log(const std::string & amp; message) {<!-- -->
        std::unique_lock<std::mutex> lock(m_mutex);
        // If the queue is full, wait for a space in the queue
        while (m_log_queue. size() >= m_max_queue_size) {<!-- -->
            m_cv.wait(lock);
        }
        // Add log information to the queue
        m_log_queue. push(message);
        // Notify the worker thread that there is new log information
        m_cv. notify_one();
    }

private:
    // The worker thread function continuously takes log information from the queue and writes it to the log file
    void worker_func() {<!-- -->
        while (!m_done) {<!-- -->
            std::unique_lock<std::mutex> lock(m_mutex);
            // If the queue is empty, wait for new log information
            while (m_log_queue.empty() & amp; & amp; !m_done) {<!-- -->
                m_cv.wait(lock);
            }

            // Process all log information in the queue
            while (!m_log_queue.empty()) {<!-- -->
                const auto & message = m_log_queue. front();
                m_log_stream << message << std::endl;
                m_log_queue. pop();
            }
            // Notify all waiting threads that there is space in the queue
            m_cv. notify_all();
        }
    }

    std::queue<std::string> m_log_queue; // log information queue
    const size_t m_max_queue_size; // maximum length of the queue
    std::ofstream m_log_stream; // log file stream
    std::mutex m_mutex; // mutex
    std::condition_variable m_cv; // condition variable for thread synchronization
    std::thread m_worker_thread; // worker thread
    bool m_done = false; // worker thread end flag
};

int main() {<!-- -->
    AsyncLogger logger("test. log");

    // Add 10000 log messages to the log queue
    for (int i = 0; i < 10000; + + i) {<!-- -->
        logger.log("log message " + std::to_string(i));
    }
    
    return 0;
}

Screenshot of running results

windows programming code

#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

const int MAX_LOG_QUEUE_SIZE = 1000;

// log message structure
struct LogMessage {<!-- -->
    std::string message;
    int level;
};

// log queue class
class LogQueue {<!-- -->
public:
    LogQueue() : m_stop(false) {<!-- -->}
    
    // add log message to queue
    void push(const LogMessage & amp; message) {<!-- -->
        std::unique_lock<std::mutex> lock(m_mutex);
        m_queue.push(message);
        m_cond. notify_one();
    }
    
    // Stop log queue processing
    void stop() {<!-- -->
        std::unique_lock<std::mutex> lock(m_mutex);
        m_stop = true;
        m_cond. notify_all();
    }
    
    // Pop a message from the log queue, wait if the queue is empty
    bool try_pop(LogMessage & amp; message) {<!-- -->
        std::unique_lock<std::mutex> lock(m_mutex);
        while (m_queue.empty() & amp; & amp; !m_stop) {<!-- -->
            m_cond.wait(lock);
        }
        if (m_queue.empty()) {<!-- -->
            return false;
        }
        message = m_queue. front();
        m_queue. pop();
        return true;
    }
    
private:
    std::queue<LogMessage> m_queue;
    std::mutex m_mutex;
    std::condition_variable m_cond;
    bool m_stop;
};

// log class
class Logger {<!-- -->
public:
    Logger(const std::string & amp; logFile) : m_logFile(logFile), m_logLevel(0), m_queueThread( & amp;Logger::processQueue, this) {<!-- -->}
    
    // Destructor, stop log queue processing and wait for thread to end
    ~Logger() {<!-- -->
        m_queue. stop();
        m_queueThread. join();
    }
    
    // set log level
    void setLogLevel(int level) {<!-- -->
        m_logLevel = level;
    }
    
    // add a log message to the queue
    void log(int level, const std::string & amp; message) {<!-- -->
        if (level >= m_logLevel) {<!-- -->
LogMessage tmp;
tmp.level = level;
tmp. message = message;
            m_queue.push(tmp);
        }
    }
    
private:
    // Process the messages in the log queue and write them to the log file
    void processQueue() {<!-- -->
        std::ofstream logStream(m_logFile);
        if (!logStream.is_open()) {<!-- -->
            std::cerr << "Error opening log file " << m_logFile << std::endl;
            return;
        }
        while (true) {<!-- -->
            LogMessage message;
            if (!m_queue.try_pop(message)) {<!-- -->
                if (m_queueStopped) {<!-- -->
                    break;
                } else {<!-- -->
                    continue;
                }
            }
            logStream << "[" << message.level << "] " << message.message << std::endl;
        }
        logStream. close();
    }
    
    std::string m_logFile; // log file name
    int m_logLevel; // log level
    LogQueue m_queue; // log message queue
    bool m_queueStopped; // Whether the log queue is stopped
    std::thread m_queueThread; // log queue processing thread
};


int main() {<!-- -->
    Logger logger("log.txt");
    logger.setLogLevel(1);
    
    logger.log(0, "This message should not be logged");
    logger.log(1, "This message should be logged with level 1");
    
    return 0;
}

Run result

log.txt

[1] This message should be logged with level 1