C++によるログモジュールの実装

概要

本記事では、C++でスレッドセーフなログモジュールを実装する方法を紹介します。STLのqueueにロックを追加し、複数のスレッドからのログ出力を安全に処理できるようにします。ログの書き込みは専用スレッドが担当し、非同期でファイルに書き込む仕組みとなっています。

スレッドセーフなキューの実装

複数のスレッドから同時にアクセスされるキューを実現するため、std::mutexstd::condition_variableを使用して排他制御を行います。


#pragma once
#include <queue>
#include <mutex>
#include <condition_variable>

template <typename T>
class ThreadSafeQueue {
public:
    void enqueue(const T& item) {
        std::unique_lock<std::mutex> lock(mutex_);
        queue_.push(item);
        lock.unlock();
        condition_.notify_one();
    }

    T dequeue() {
        std::unique_lock<std::mutex> lock(mutex_);
        while (queue_.empty()) {
            condition_.wait(lock);
        }
        T item = queue_.front();
        queue_.pop();
        return item;
    }

private:
    std::queue<T> queue_;
    std::mutex mutex_;
    std::condition_variable condition_;
};

ログレベルの定義

ログの重要度を区別するために、2つのログレベルを定義します。


enum class LogLevel {
    INFORMATION,
    CRITICAL
};

ログクラスの実装

シングルトンパターンを使用して、アプリケーション全体で1つのログインスタンスだけが使用されるようにします。


class Logger {
public:
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
    ~Logger() = default;

    static Logger& instance();
    void set_level(LogLevel level);
    void write_log(const std::string& message);

private:
    LogLevel current_level_;
    ThreadSafeQueue<std::string> log_queue_;

    Logger();
};

ログ書き込みスレッドの起動

コンストラクタ内で永続的に動作するスレッドを生成し、キューからログを取得してファイルに書き込みます。


Logger::Logger() {
    std::thread writer([this]() {
        while (true) {
            auto now = std::chrono::system_clock::now();
            std::time_t current_time = std::chrono::system_clock::to_time_t(now);
            std::tm* local_time = std::localtime(&current_time);

            std::stringstream date_stream, time_stream;
            date_stream << std::put_time(local_time, "%Y-%m-%d");
            time_stream << std::put_time(local_time, "[%H:%M:%S]");

            std::string filename = date_stream.str() + ".log";
            FILE* file = fopen(filename.c_str(), "a+");
            if (!file) {
                continue;
            }

            std::string log_entry = time_stream.str() + log_queue_.dequeue() + "\n";
            fputs(log_entry.c_str(), file);
            fclose(file);
        }
    });
    writer.detach();
}

マクロによるログ出力インターフェース

使いやすさのために、マクロを使用してファイル名と行番号を自動挿入するインターフェースを提供します。


#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define LOG(level, message, ...) \
    do { \
        auto& logger = Logger::instance(); \
        logger.set_level(level); \
        char buffer[1024] = {0}; \
        snprintf(buffer, sizeof(buffer), \
            (std::string("[") + #level + "][" + FILENAME + ":" + \
             std::to_string(__LINE__) + "] " + message).c_str(), ##__VA_ARGS__); \
        logger.write_log(buffer); \
    } while (0)

#define INFO_LOG(message, ...) LOG(LogLevel::INFORMATION, message, ##__VA_ARGS__)
#define ERROR_LOG(message, ...) LOG(LogLevel::CRITICAL, message, ##__VA_ARGS__)

使用例

以下はログモジュールの使用例です:


#include "logger.h"

int main() {
    while (true) {
        std::string input;
        std::getline(std::cin, input);
        INFO_LOG("%s", input.c_str());
    }
}

タグ: C++ Multi-threading logging STL thread-safety

6月16日 21:27 投稿