C++笔记:偏现代C++日志系统

C++20 异步日志系统实现说明

源码实现

头文件

cpp 复制代码
#pragma once

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <memory>
#include <mutex>
#include <ostream>
#include <queue>
#include <source_location>
#include <sstream>
#include <streambuf>
#include <string>
#include <string_view>
#include <thread>
#include <vector>

/**
 * @brief 日志等级。
 *
 * 等级从低到高排列,便于使用整数比较实现最低日志等级过滤。
 */
enum class LogLevel {
    Debug = 0,
    Info,
    Warn,
    Error,
};

/**
 * @brief 日志系统配置。
 */
struct LogConfig {
    /**
     * @brief 日志文件路径。
     *
     * 默认写入当前工作目录下的 Log.txt。父目录不存在时 FileSink 会自动创建。
     */
    std::filesystem::path file_path{"Log.txt"};

    /**
     * @brief 最低日志等级。
     *
     * 低于该等级的日志会在 LogSystem::log() 中被过滤,不进入异步队列。
     */
    LogLevel min_level{LogLevel::Debug};

    /**
     * @brief 异步队列最大容量。
     *
     * 队列满时新日志会被丢弃并增加 droppedCount()。设置为 0 表示不限制容量。
     */
    std::size_t max_queue_size{8192};

    /**
     * @brief 单个日志文件最大字节数。
     *
     * 超过该大小后 FileSink 会执行滚动。设置为 0 表示不按大小滚动。
     */
    std::uintmax_t max_file_size{10 * 1024 * 1024};

    /**
     * @brief 保留的滚动日志文件数量。
     *
     * 例如值为 3 时,会保留 app.log.1、app.log.2、app.log.3。
     */
    std::size_t max_rotated_files{3};
};

/**
 * @brief 结构化日志记录。
 *
 * 前台线程创建 LogRecord,后台 sink 根据这些字段决定格式化、flush、滚动等输出策略。
 */
struct LogRecord {
    /**
     * @brief 日志等级。
     */
    LogLevel level{LogLevel::Info};

    /**
     * @brief 日志创建时间点。
     */
    std::chrono::system_clock::time_point timestamp{};

    /**
     * @brief 产生日志的线程 id。
     */
    std::thread::id thread_id{};

    /**
     * @brief 日志调用点源码位置。
     */
    std::source_location location{};

    /**
     * @brief 日志正文。
     */
    std::string message;
};

/**
 * @brief 异步日志队列。
 *
 * 该队列是多生产者、单消费者场景下的并发边界。前台线程 push,后台线程 pop。
 */
class LogQueue {
public:
    /**
     * @brief 构造指定容量的日志队列。
     *
     * @param max_queue_size 最大队列容量,0 表示不限制容量。
     */
    explicit LogQueue(std::size_t max_queue_size = 8192);

    /**
     * @brief 向队列写入一条结构化日志。
     *
     * @param record 待入队的日志记录。
     * @return true 日志成功入队。
     * @return false 队列已经关闭或队列已满,日志被丢弃。
     */
    bool push(LogRecord record);

    /**
     * @brief 从队列弹出一条日志。
     *
     * @param record 输出参数,成功时接收一条日志记录。
     * @return true 成功弹出日志。
     * @return false 队列已经关闭且没有剩余日志。
     */
    bool pop(LogRecord& record);

    /**
     * @brief 关闭队列并唤醒等待线程。
     *
     * 关闭后不接受新日志,但已有日志仍可被后台线程消费完。
     */
    void shutdown() noexcept;

    /**
     * @brief 返回当前排队日志条数。
     *
     * @return 队列中尚未被后台线程消费的日志数量。
     */
    [[nodiscard]] std::size_t size() const;

    /**
     * @brief 返回被队列丢弃的日志条数。
     *
     * @return 因关闭或队列满而丢弃的日志数量。
     */
    [[nodiscard]] std::uint64_t droppedCount() const noexcept;

private:
    /**
     * @brief 判断当前队列是否达到容量上限。
     *
     * @return true 队列有容量上限且已经满。
     * @return false 队列仍可接收新日志。
     */
    [[nodiscard]] bool isFullUnlocked() const;

    /**
     * @brief 已入队但尚未写入 sink 的日志记录。
     */
    std::queue<LogRecord> mQueue;

    /**
     * @brief 队列最大容量,0 表示不限制容量。
     */
    std::size_t mMaxQueueSize{0};

    /**
     * @brief 保护 mQueue、mMaxQueueSize 和 mShutdown 的互斥锁。
     */
    mutable std::mutex mMutex;

    /**
     * @brief 后台线程等待新日志或关闭信号的条件变量。
     */
    std::condition_variable mCondVar;

    /**
     * @brief 队列是否已经关闭。
     */
    bool mShutdown{false};

    /**
     * @brief 被丢弃的日志数量。
     */
    std::atomic<std::uint64_t> mDroppedCount{0};
};

/**
 * @brief 日志输出端抽象。
 *
 * LogSystem 只负责调度 LogRecord,具体输出到文件、控制台或网络由 LogSink 子类决定。
 */
class LogSink {
public:
    /**
     * @brief 默认虚析构。
     */
    virtual ~LogSink() = default;

    /**
     * @brief 写入一条日志记录。
     *
     * @param record 结构化日志记录。
     */
    virtual void write(const LogRecord& record) = 0;

    /**
     * @brief 刷新 sink 内部缓冲。
     */
    virtual void flush() = 0;
};

/**
 * @brief 文件日志输出端。
 *
 * FileSink 负责把 LogRecord 格式化成文本行,并按文件大小执行滚动。
 */
class FileSink final : public LogSink {
public:
    /**
     * @brief 构造文件 sink。
     *
     * @param file_path 日志文件路径。
     * @param max_file_size 单个日志文件最大字节数,0 表示不滚动。
     * @param max_rotated_files 保留的滚动文件数量。
     */
    FileSink(
        std::filesystem::path file_path,
        std::uintmax_t max_file_size,
        std::size_t max_rotated_files);

    /**
     * @brief 析构前刷新文件缓冲。
     */
    ~FileSink() override;

    FileSink(const FileSink&) = delete;
    FileSink& operator=(const FileSink&) = delete;

    /**
     * @brief 写入一条日志到文件。
     *
     * @param record 结构化日志记录。
     */
    void write(const LogRecord& record) override;

    /**
     * @brief 刷新文件缓冲。
     */
    void flush() override;

    /**
     * @brief 返回当前日志文件路径。
     *
     * @return const std::filesystem::path& 当前文件路径。
     */
    [[nodiscard]] const std::filesystem::path& filePath() const noexcept;

private:
    /**
     * @brief 打开当前日志文件。
     */
    void openFile();

    /**
     * @brief 根据当前文件大小判断是否需要滚动。
     *
     * @param next_write_size 即将写入的日志行字节数。
     */
    void rotateIfNeeded(std::size_t next_write_size);

    /**
     * @brief 执行日志文件滚动。
     */
    void rotateFiles();

    /**
     * @brief 格式化一条日志记录。
     *
     * @param record 结构化日志记录。
     * @return std::string 可直接写入文件的日志行,不包含换行符。
     */
    [[nodiscard]] std::string formatRecord(const LogRecord& record) const;

    /**
     * @brief 日志等级转字符串。
     *
     * @param level 日志等级。
     * @return std::string_view 等级名称。
     */
    [[nodiscard]] static std::string_view levelToString(LogLevel level) noexcept;

    /**
     * @brief 判断日志等级是否需要立即 flush。
     *
     * @param level 日志等级。
     * @return true 需要立即 flush。
     * @return false 可以依赖文件缓冲。
     */
    [[nodiscard]] static bool shouldFlush(LogLevel level) noexcept;

    /**
     * @brief 当前主日志文件路径。
     */
    std::filesystem::path mFilePath;

    /**
     * @brief 单个日志文件最大字节数,0 表示不滚动。
     */
    std::uintmax_t mMaxFileSize{0};

    /**
     * @brief 保留的滚动文件数量。
     */
    std::size_t mMaxRotatedFiles{0};

    /**
     * @brief 当前日志文件输出流。
     */
    std::ofstream mFile;

    /**
     * @brief 当前主日志文件已知大小。
     *
     * 该值包含已经写入 ofstream 的字节数,避免文件缓冲导致 filesystem::file_size() 滞后。
     */
    std::uintmax_t mCurrentFileSize{0};
};

/**
 * @brief 进程级异步日志系统。
 *
 * 前台线程负责等级过滤和创建 LogRecord,后台线程负责把记录分发给 sink。
 */
class LogSystem {
public:
    /**
     * @brief 获取全局日志系统实例。
     *
     * @param log_file_path 可选日志文件路径,仅在单例首次创建时生效。
     * @return LogSystem& 全局日志系统实例。
     */
    static LogSystem& getInstance(std::filesystem::path log_file_path = {});

    /**
     * @brief 设置默认日志配置。
     *
     * @param config 日志配置。
     * @return true 设置成功,后续首次 getInstance() 会使用该配置。
     * @return false 日志系统已经创建,配置没有被修改。
     */
    static bool setDefaultConfig(LogConfig config);

    /**
     * @brief 设置默认日志文件路径。
     *
     * @param log_file_path 日志文件路径。
     * @return true 设置成功,后续首次 getInstance() 会使用该路径。
     * @return false 日志系统已经创建,路径没有被修改。
     */
    static bool setDefaultLogFilePath(std::filesystem::path log_file_path);

    /**
     * @brief 析构日志系统,关闭队列并等待后台线程退出。
     */
    ~LogSystem();

    LogSystem(const LogSystem&) = delete;
    LogSystem& operator=(const LogSystem&) = delete;

    /**
     * @brief 写入一条日志。
     *
     * @param level 日志等级。
     * @param message 日志正文。
     * @param location 调用位置,默认由 std::source_location 自动填充。
     * @return true 日志成功入队。
     * @return false 日志被等级过滤、队列已满或系统已关闭。
     */
    bool log(
        LogLevel level,
        std::string_view message,
        const std::source_location& location = std::source_location::current()) noexcept;

    /**
     * @brief 设置最低日志等级。
     *
     * @param level 新的最低日志等级。
     */
    void setMinLevel(LogLevel level) noexcept;

    /**
     * @brief 判断指定等级是否会被当前配置记录。
     *
     * @param level 日志等级。
     * @return true 该等级会被记录。
     * @return false 该等级会被过滤。
     */
    [[nodiscard]] bool shouldLog(LogLevel level) const noexcept;

    /**
     * @brief 主动关闭日志系统。
     */
    void shutdown() noexcept;

    /**
     * @brief 返回当前等待后台线程消费的日志条数。
     *
     * @return 当前日志队列长度。
     */
    [[nodiscard]] std::size_t pendingCount() const;

    /**
     * @brief 返回被丢弃的日志数量。
     *
     * @return 因队列满或关闭而丢弃的日志数量。
     */
    [[nodiscard]] std::uint64_t droppedCount() const noexcept;

    /**
     * @brief 返回当前日志文件路径。
     *
     * @return const std::filesystem::path& 当前日志文件路径。
     */
    [[nodiscard]] const std::filesystem::path& logFilePath() const noexcept;

private:
    /**
     * @brief 使用指定配置创建日志系统。
     *
     * @param config 日志配置。
     */
    explicit LogSystem(LogConfig config);

    /**
     * @brief 后台线程主循环。
     */
    void writerLoop();

    /**
     * @brief 创建文件 sink。
     *
     * @param config 日志配置。
     */
    void createDefaultSink(const LogConfig& config);

    /**
     * @brief 日志队列。
     */
    LogQueue mLogQueue;

    /**
     * @brief 当前日志系统配置。
     */
    LogConfig mConfig;

    /**
     * @brief 当前注册的输出端列表。
     */
    std::vector<std::unique_ptr<LogSink>> mSinks;

    /**
     * @brief 后台写日志线程。
     */
    std::jthread mWorkerThread;

    /**
     * @brief 保护 mShutdown 和 mSinks 的互斥锁。
     */
    mutable std::mutex mStateMutex;

    /**
     * @brief 日志系统是否已经关闭。
     */
    bool mShutdown{false};

    /**
     * @brief 当前最低日志等级,用整数保存便于原子读写。
     */
    std::atomic<int> mMinLevel{static_cast<int>(LogLevel::Debug)};
};

/**
 * @brief ostream 缓冲区,把流式日志内容转交给 LogSystem。
 */
class LogBuffer : public std::stringbuf {
public:
    /**
     * @brief 构造指定等级和调用位置的日志缓冲区。
     *
     * @param level 日志等级。
     * @param location 宏展开处的源代码位置。
     */
    explicit LogBuffer(LogLevel level, std::source_location location);

    /**
     * @brief 析构时自动提交尚未 flush 的日志内容。
     */
    ~LogBuffer() override;

    LogBuffer(const LogBuffer&) = delete;
    LogBuffer& operator=(const LogBuffer&) = delete;

    /**
     * @brief ostream flush 时提交当前缓冲内容。
     *
     * @return int 成功返回 0。
     */
    int sync() override;

private:
    /**
     * @brief 将当前 stringbuf 内容提交到异步日志系统。
     */
    void putOutput() noexcept;

    /**
     * @brief 当前流对应的日志等级。
     */
    LogLevel mLevel;

    /**
     * @brief 当前流创建时的源代码位置。
     */
    std::source_location mLocation;
};

/**
 * @brief 支持 LOG_INFO << "message" 风格的临时输出流。
 */
class LogStream : public std::ostream {
public:
    /**
     * @brief 构造指定等级和调用位置的日志流。
     *
     * @param level 日志等级。
     * @param location 宏展开处的源代码位置。
     */
    explicit LogStream(
        LogLevel level,
        const std::source_location& location = std::source_location::current());

private:
    /**
     * @brief 日志流内部缓冲区。
     */
    LogBuffer mBuffer;
};

/**
 * @brief Debug 等级日志流。
 */
#define LOG_DEBUG LogStream(LogLevel::Debug, std::source_location::current())

/**
 * @brief Info 等级日志流。
 */
#define LOG_INFO LogStream(LogLevel::Info, std::source_location::current())

/**
 * @brief Warn 等级日志流。
 */
#define LOG_WARN LogStream(LogLevel::Warn, std::source_location::current())

/**
 * @brief Error 等级日志流。
 */
#define LOG_ERR LogStream(LogLevel::Error, std::source_location::current())

源文件

cpp 复制代码
#include "MyLogger.hpp"

#include <chrono>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <utility>

namespace {

/**
 * @brief 将 time_t 转换成本地时间。
 *
 * @param time 从 system_clock 转换得到的秒级时间。
 * @return std::tm 本地时间结构体。
 */
std::tm makeLocalTime(std::time_t time) {
    std::tm local_time{};

#if defined(_WIN32)
    localtime_s(&local_time, &time);
#else
    localtime_r(&time, &local_time);
#endif

    return local_time;
}

/**
 * @brief 保护默认日志配置的互斥锁。
 *
 * @return std::mutex& 配置互斥锁。
 */
std::mutex& logConfigMutex() {
    static std::mutex mutex;
    return mutex;
}

/**
 * @brief 保存单例创建前配置的日志参数。
 *
 * @return LogConfig& 可修改的默认配置。
 */
LogConfig& configuredLogConfig() {
    static LogConfig config;
    return config;
}

/**
 * @brief 标记 LogSystem 单例是否已经开始创建。
 *
 * @return bool& 单例创建标记。
 */
bool& logSystemCreated() {
    static bool created = false;
    return created;
}

/**
 * @brief 解析首次构造 LogSystem 时使用的配置。
 *
 * @param requested_path getInstance() 调用方临时指定的日志路径。
 * @return LogConfig 最终用于构造日志系统的配置。
 */
LogConfig resolveLogConfig(std::filesystem::path requested_path) {
    std::lock_guard lock(logConfigMutex());

    // getInstance(path) 是一次性初始化入口,只有首次构造前传入才生效。
    if (!requested_path.empty()) {
        configuredLogConfig().file_path = std::move(requested_path);
    }

    logSystemCreated() = true;
    return configuredLogConfig();
}

/**
 * @brief 规范化日志配置。
 *
 * @param config 调用方传入的日志配置。
 * @return LogConfig 修正后的日志配置。
 */
LogConfig normalizeConfig(LogConfig config) {
    if (config.file_path.empty()) {
        config.file_path = "Log.txt";
    }
    return config;
}

/**
 * @brief 准备日志文件路径,并在需要时创建父目录。
 *
 * @param file_path 日志文件路径。
 * @return std::filesystem::path 可直接传给 std::ofstream 的路径。
 */
std::filesystem::path prepareFilePath(std::filesystem::path file_path) {
    if (file_path.empty()) {
        file_path = "Log.txt";
    }

    const std::filesystem::path parent_path = file_path.parent_path();
    if (!parent_path.empty()) {
        std::filesystem::create_directories(parent_path);
    }

    return file_path;
}

/**
 * @brief 生成滚动日志文件路径。
 *
 * @param base_path 主日志文件路径。
 * @param index 滚动文件编号。
 * @return std::filesystem::path 滚动文件路径。
 */
std::filesystem::path rotatedPath(const std::filesystem::path& base_path, std::size_t index) {
    return std::filesystem::path(base_path.string() + "." + std::to_string(index));
}

}  // namespace

LogQueue::LogQueue(std::size_t max_queue_size)
    : mMaxQueueSize(max_queue_size) {
}

bool LogQueue::push(LogRecord record) {
    {
        std::lock_guard lock(mMutex);
        if (mShutdown || isFullUnlocked()) {
            mDroppedCount.fetch_add(1, std::memory_order_relaxed);
            return false;
        }

        mQueue.push(std::move(record));
    }

    // 新日志入队后只需要唤醒一个后台写线程。
    mCondVar.notify_one();
    return true;
}

bool LogQueue::pop(LogRecord& record) {
    std::unique_lock lock(mMutex);

    // 等到有日志可写,或者队列进入关闭状态。
    mCondVar.wait(lock, [this] {
        return !mQueue.empty() || mShutdown;
    });

    // shutdown 后仍然先消费完已有日志,只有队列为空才真正退出。
    if (mQueue.empty()) {
        return false;
    }

    record = std::move(mQueue.front());
    mQueue.pop();
    return true;
}

void LogQueue::shutdown() noexcept {
    {
        std::lock_guard lock(mMutex);
        mShutdown = true;
    }

    // 唤醒后台线程,让它把剩余日志 drain 完后退出。
    mCondVar.notify_all();
}

std::size_t LogQueue::size() const {
    std::lock_guard lock(mMutex);
    return mQueue.size();
}

std::uint64_t LogQueue::droppedCount() const noexcept {
    return mDroppedCount.load(std::memory_order_relaxed);
}

bool LogQueue::isFullUnlocked() const {
    return mMaxQueueSize != 0 && mQueue.size() >= mMaxQueueSize;
}

FileSink::FileSink(
    std::filesystem::path file_path,
    std::uintmax_t max_file_size,
    std::size_t max_rotated_files)
    : mFilePath(prepareFilePath(std::move(file_path))),
      mMaxFileSize(max_file_size),
      mMaxRotatedFiles(max_rotated_files) {
    openFile();
}

FileSink::~FileSink() {
    flush();
}

void FileSink::write(const LogRecord& record) {
    const std::string line = formatRecord(record);
    rotateIfNeeded(line.size() + 1);

    mFile << line << '\n';
    mCurrentFileSize += line.size() + 1;
    if (shouldFlush(record.level)) {
        mFile.flush();
    }
}

void FileSink::flush() {
    if (mFile.is_open()) {
        mFile.flush();
    }
}

const std::filesystem::path& FileSink::filePath() const noexcept {
    return mFilePath;
}

void FileSink::openFile() {
    mFile.open(mFilePath, std::ios::out | std::ios::app);
    if (!mFile.is_open()) {
        throw std::runtime_error("Failed to open log file: " + mFilePath.string());
    }

    mCurrentFileSize = 0;
    if (std::filesystem::exists(mFilePath)) {
        mCurrentFileSize = std::filesystem::file_size(mFilePath);
    }
}

void FileSink::rotateIfNeeded(std::size_t next_write_size) {
    if (mMaxFileSize == 0 || mMaxRotatedFiles == 0) {
        return;
    }

    if (mCurrentFileSize + next_write_size <= mMaxFileSize) {
        return;
    }

    rotateFiles();
}

void FileSink::rotateFiles() {
    if (mFile.is_open()) {
        mFile.flush();
        mFile.close();
    }

    // 删除最老的滚动文件,为后续 rename 腾出位置。
    const std::filesystem::path oldest_path = rotatedPath(mFilePath, mMaxRotatedFiles);
    if (std::filesystem::exists(oldest_path)) {
        std::filesystem::remove(oldest_path);
    }

    // 从后往前移动,避免 app.log.1 被 app.log 覆盖前丢失。
    for (std::size_t index = mMaxRotatedFiles; index > 1; --index) {
        const std::filesystem::path from = rotatedPath(mFilePath, index - 1);
        const std::filesystem::path to = rotatedPath(mFilePath, index);
        if (std::filesystem::exists(from)) {
            std::filesystem::rename(from, to);
        }
    }

    if (std::filesystem::exists(mFilePath)) {
        std::filesystem::rename(mFilePath, rotatedPath(mFilePath, 1));
    }

    openFile();
}

std::string FileSink::formatRecord(const LogRecord& record) const {
    const std::time_t now_time = std::chrono::system_clock::to_time_t(record.timestamp);
    const std::tm local_time = makeLocalTime(now_time);

    std::ostringstream output;
    output << '[' << std::put_time(&local_time, "%Y-%m-%d %H:%M:%S") << ']'
           << '[' << levelToString(record.level) << ']'
           << "[tid=" << record.thread_id << ']'
           << '[' << record.location.file_name() << ':' << record.location.line() << ']'
           << ' ' << record.message;
    return output.str();
}

std::string_view FileSink::levelToString(LogLevel level) noexcept {
    switch (level) {
        case LogLevel::Debug:
            return "DEBUG";
        case LogLevel::Info:
            return "INFO";
        case LogLevel::Warn:
            return "WARN";
        case LogLevel::Error:
            return "ERROR";
    }

    return "UNKNOWN";
}

bool FileSink::shouldFlush(LogLevel level) noexcept {
    return level == LogLevel::Error;
}

LogSystem& LogSystem::getInstance(std::filesystem::path log_file_path) {
    static LogSystem instance(resolveLogConfig(std::move(log_file_path)));
    return instance;
}

bool LogSystem::setDefaultConfig(LogConfig config) {
    std::lock_guard lock(logConfigMutex());
    if (logSystemCreated()) {
        return false;
    }

    configuredLogConfig() = normalizeConfig(std::move(config));
    return true;
}

bool LogSystem::setDefaultLogFilePath(std::filesystem::path log_file_path) {
    std::lock_guard lock(logConfigMutex());
    if (logSystemCreated()) {
        return false;
    }

    configuredLogConfig().file_path = std::move(log_file_path);
    return true;
}

LogSystem::LogSystem(LogConfig config)
    : mLogQueue(config.max_queue_size),
      mConfig(normalizeConfig(std::move(config))),
      mMinLevel(static_cast<int>(mConfig.min_level)) {
    createDefaultSink(mConfig);

    // sink 创建完成后再启动后台线程,避免线程看到半初始化状态。
    mWorkerThread = std::jthread([this](std::stop_token) {
        writerLoop();
    });
}

LogSystem::~LogSystem() {
    shutdown();
}

bool LogSystem::log(
    LogLevel level,
    std::string_view message,
    const std::source_location& location) noexcept {
    if (!shouldLog(level)) {
        return false;
    }

    {
        std::lock_guard lock(mStateMutex);
        if (mShutdown) {
            return false;
        }
    }

    try {
        LogRecord record;
        record.level = level;
        record.timestamp = std::chrono::system_clock::now();
        record.thread_id = std::this_thread::get_id();
        record.location = location;
        record.message = std::string(message);
        return mLogQueue.push(std::move(record));
    } catch (const std::exception& error) {
        // 日志系统自身失败时不向业务代码抛异常,降级写入 stderr。
        std::cerr << "[LOGGER][ERROR] " << error.what() << '\n';
        return false;
    } catch (...) {
        std::cerr << "[LOGGER][ERROR] unknown logger failure\n";
        return false;
    }
}

void LogSystem::setMinLevel(LogLevel level) noexcept {
    mMinLevel.store(static_cast<int>(level), std::memory_order_relaxed);
}

bool LogSystem::shouldLog(LogLevel level) const noexcept {
    return static_cast<int>(level) >= mMinLevel.load(std::memory_order_relaxed);
}

void LogSystem::shutdown() noexcept {
    {
        std::lock_guard lock(mStateMutex);
        if (mShutdown) {
            return;
        }
        mShutdown = true;
    }

    // 先关闭队列,后台线程会继续消费队列中已有日志直到 pop() 返回 false。
    mLogQueue.shutdown();

    if (mWorkerThread.joinable()) {
        mWorkerThread.join();
    }

    std::lock_guard lock(mStateMutex);
    for (auto& sink : mSinks) {
        sink->flush();
    }
}

std::size_t LogSystem::pendingCount() const {
    return mLogQueue.size();
}

std::uint64_t LogSystem::droppedCount() const noexcept {
    return mLogQueue.droppedCount();
}

const std::filesystem::path& LogSystem::logFilePath() const noexcept {
    return mConfig.file_path;
}

void LogSystem::writerLoop() {
    LogRecord record;
    while (mLogQueue.pop(record)) {
        std::lock_guard lock(mStateMutex);
        for (auto& sink : mSinks) {
            sink->write(record);
        }
    }
}

void LogSystem::createDefaultSink(const LogConfig& config) {
    mSinks.push_back(std::make_unique<FileSink>(
        config.file_path,
        config.max_file_size,
        config.max_rotated_files));
}

LogBuffer::LogBuffer(LogLevel level, std::source_location location)
    : mLevel(level),
      mLocation(std::move(location)) {
}

LogBuffer::~LogBuffer() {
    if (pbase() != pptr()) {
        putOutput();
    }
}

int LogBuffer::sync() {
    putOutput();
    return 0;
}

void LogBuffer::putOutput() noexcept {
    const std::string message = str();
    if (message.empty()) {
        return;
    }

    // 清空缓冲区要在 log() 前后都安全;log() 本身保证不向外抛异常。
    str("");
    try {
        LogSystem::getInstance().log(mLevel, message, mLocation);
    } catch (...) {
        // 日志流析构期间不能把异常继续抛给业务代码。
    }
}

LogStream::LogStream(LogLevel level, const std::source_location& location)
    : std::ostream(nullptr),
      mBuffer(level, location) {
    rdbuf(&mBuffer);
}

核心目的

这个日志系统已经从简单的异步文件写入,演化成更接近生产结构的 logger:

  • 结构化日志记录 :前台线程创建 LogRecord,记录等级、时间、线程 id、源码位置和正文。

  • 有界异步队列LogQueue 限制最大容量,队列满时丢弃新日志并统计 dropped count。

  • 等级过滤LogSystem 支持 minLevel,低于最低等级的日志不会进入队列。

  • sink 架构LogSystem 只负责调度,具体输出由 LogSink 子类完成。

  • 文件 sink :当前实现 FileSink,负责格式化文本、写文件、ERROR 级别立即 flush、按文件大小滚动。

当前使用方式

默认写入 Log.txt

cpp 复制代码
LOG_INFO << "hello logger";

LOG_WARN << "something happened";

LOG_ERR << "fatal error";

程序启动阶段可以配置日志:

cpp 复制代码
LogConfig config;

config.file_path = "logs/app.log";

config.min_level = LogLevel::Info;

config.max_queue_size = 8192;

config.max_file_size = 10 * 1024 * 1024;

config.max_rotated_files = 3;

  

LogSystem::setDefaultConfig(config);

路径也可以单独设置:

cpp 复制代码
LogSystem::setDefaultLogFilePath("logs/app.log");

这些配置只在 第一次创建 LogSystem 生效。第一次 LOG_INFOLogSystem::getInstance() 后,单例已经创建,再修改默认配置会返回 false

架构概览

#mermaid-svg-48YsCLuBQRzQf6dm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-48YsCLuBQRzQf6dm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-48YsCLuBQRzQf6dm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-48YsCLuBQRzQf6dm .error-icon{fill:#552222;}#mermaid-svg-48YsCLuBQRzQf6dm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-48YsCLuBQRzQf6dm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-48YsCLuBQRzQf6dm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-48YsCLuBQRzQf6dm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-48YsCLuBQRzQf6dm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-48YsCLuBQRzQf6dm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-48YsCLuBQRzQf6dm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-48YsCLuBQRzQf6dm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-48YsCLuBQRzQf6dm .marker.cross{stroke:#333333;}#mermaid-svg-48YsCLuBQRzQf6dm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-48YsCLuBQRzQf6dm p{margin:0;}#mermaid-svg-48YsCLuBQRzQf6dm g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-48YsCLuBQRzQf6dm g.classGroup text .title{font-weight:bolder;}#mermaid-svg-48YsCLuBQRzQf6dm .cluster-label text{fill:#333;}#mermaid-svg-48YsCLuBQRzQf6dm .cluster-label span{color:#333;}#mermaid-svg-48YsCLuBQRzQf6dm .cluster-label span p{background-color:transparent;}#mermaid-svg-48YsCLuBQRzQf6dm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-48YsCLuBQRzQf6dm .cluster text{fill:#333;}#mermaid-svg-48YsCLuBQRzQf6dm .cluster span{color:#333;}#mermaid-svg-48YsCLuBQRzQf6dm .nodeLabel,#mermaid-svg-48YsCLuBQRzQf6dm .edgeLabel{color:#131300;}#mermaid-svg-48YsCLuBQRzQf6dm .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-48YsCLuBQRzQf6dm .label text{fill:#131300;}#mermaid-svg-48YsCLuBQRzQf6dm .labelBkg{background:#ECECFF;}#mermaid-svg-48YsCLuBQRzQf6dm .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-48YsCLuBQRzQf6dm .classTitle{font-weight:bolder;}#mermaid-svg-48YsCLuBQRzQf6dm .node rect,#mermaid-svg-48YsCLuBQRzQf6dm .node circle,#mermaid-svg-48YsCLuBQRzQf6dm .node ellipse,#mermaid-svg-48YsCLuBQRzQf6dm .node polygon,#mermaid-svg-48YsCLuBQRzQf6dm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-48YsCLuBQRzQf6dm .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm g.clickable{cursor:pointer;}#mermaid-svg-48YsCLuBQRzQf6dm g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-48YsCLuBQRzQf6dm g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-48YsCLuBQRzQf6dm .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-48YsCLuBQRzQf6dm .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-48YsCLuBQRzQf6dm .dashed-line{stroke-dasharray:3;}#mermaid-svg-48YsCLuBQRzQf6dm .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-48YsCLuBQRzQf6dm #compositionStart,#mermaid-svg-48YsCLuBQRzQf6dm .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm #compositionEnd,#mermaid-svg-48YsCLuBQRzQf6dm .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm #dependencyStart,#mermaid-svg-48YsCLuBQRzQf6dm .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm #dependencyStart,#mermaid-svg-48YsCLuBQRzQf6dm .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm #extensionStart,#mermaid-svg-48YsCLuBQRzQf6dm .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm #extensionEnd,#mermaid-svg-48YsCLuBQRzQf6dm .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm #aggregationStart,#mermaid-svg-48YsCLuBQRzQf6dm .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm #aggregationEnd,#mermaid-svg-48YsCLuBQRzQf6dm .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm #lollipopStart,#mermaid-svg-48YsCLuBQRzQf6dm .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm #lollipopEnd,#mermaid-svg-48YsCLuBQRzQf6dm .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-48YsCLuBQRzQf6dm .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-48YsCLuBQRzQf6dm .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-48YsCLuBQRzQf6dm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-48YsCLuBQRzQf6dm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-48YsCLuBQRzQf6dm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} <<enum>>
LogLevel
Debug
Info
Warn
Error
LogConfig
+path file_path
+LogLevel min_level
+size_t max_queue_size
+uintmax_t max_file_size
+size_t max_rotated_files
LogRecord
+LogLevel level
+time_point timestamp
+thread_id thread_id
+source_location location
+string message
LogQueue
-queue<LogRecord> mQueue
-size_t mMaxQueueSize
-mutex mMutex
-condition_variable mCondVar
-bool mShutdown
-atomic<uint64_t> mDroppedCount
+push(LogRecord) : bool
+pop(LogRecord&) : bool
+shutdown() : void
+size() : size_t
+droppedCount() : uint64_t
<<interface>>
LogSink
+write(LogRecord) : void
+flush() : void
FileSink
-path mFilePath
-uintmax_t mMaxFileSize
-size_t mMaxRotatedFiles
-ofstream mFile
+write(LogRecord) : void
+flush() : void
-rotateIfNeeded(size_t) : void
-rotateFiles() : void
-formatRecord(LogRecord) : string
LogSystem
-LogQueue mLogQueue
-LogConfig mConfig
-vector<unique_ptr<LogSink>> mSinks
-jthread mWorkerThread
-mutex mStateMutex
-bool mShutdown
-atomic<int> mMinLevel
+getInstance(path) : LogSystem&
+setDefaultConfig(LogConfig) : bool
+log(LogLevel, string_view, source_location) : bool
+setMinLevel(LogLevel) : void
+pendingCount() : size_t
+droppedCount() : uint64_t
-writerLoop() : void
LogBuffer
-LogLevel mLevel
-source_location mLocation
+sync() : int
-putOutput() : void
LogStream
-LogBuffer mBuffer
ConsoleSink

这张类图里有三条主线:

  • 前台写入链路LOG_INFO 创建 LogStream,内部 LogBuffer 收集流式内容。

  • 异步调度链路LogSystem 把日志变成 LogRecord,写入 LogQueue

  • 输出链路 :后台线程从队列取出 LogRecord,分发给 LogSink,当前只有 FileSink

从宏到文件的数据流

#mermaid-svg-U7igmvsWCKlZmXWn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-U7igmvsWCKlZmXWn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-U7igmvsWCKlZmXWn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-U7igmvsWCKlZmXWn .error-icon{fill:#552222;}#mermaid-svg-U7igmvsWCKlZmXWn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-U7igmvsWCKlZmXWn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-U7igmvsWCKlZmXWn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-U7igmvsWCKlZmXWn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-U7igmvsWCKlZmXWn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-U7igmvsWCKlZmXWn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-U7igmvsWCKlZmXWn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-U7igmvsWCKlZmXWn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-U7igmvsWCKlZmXWn .marker.cross{stroke:#333333;}#mermaid-svg-U7igmvsWCKlZmXWn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-U7igmvsWCKlZmXWn p{margin:0;}#mermaid-svg-U7igmvsWCKlZmXWn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-U7igmvsWCKlZmXWn .cluster-label text{fill:#333;}#mermaid-svg-U7igmvsWCKlZmXWn .cluster-label span{color:#333;}#mermaid-svg-U7igmvsWCKlZmXWn .cluster-label span p{background-color:transparent;}#mermaid-svg-U7igmvsWCKlZmXWn .label text,#mermaid-svg-U7igmvsWCKlZmXWn span{fill:#333;color:#333;}#mermaid-svg-U7igmvsWCKlZmXWn .node rect,#mermaid-svg-U7igmvsWCKlZmXWn .node circle,#mermaid-svg-U7igmvsWCKlZmXWn .node ellipse,#mermaid-svg-U7igmvsWCKlZmXWn .node polygon,#mermaid-svg-U7igmvsWCKlZmXWn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-U7igmvsWCKlZmXWn .rough-node .label text,#mermaid-svg-U7igmvsWCKlZmXWn .node .label text,#mermaid-svg-U7igmvsWCKlZmXWn .image-shape .label,#mermaid-svg-U7igmvsWCKlZmXWn .icon-shape .label{text-anchor:middle;}#mermaid-svg-U7igmvsWCKlZmXWn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-U7igmvsWCKlZmXWn .rough-node .label,#mermaid-svg-U7igmvsWCKlZmXWn .node .label,#mermaid-svg-U7igmvsWCKlZmXWn .image-shape .label,#mermaid-svg-U7igmvsWCKlZmXWn .icon-shape .label{text-align:center;}#mermaid-svg-U7igmvsWCKlZmXWn .node.clickable{cursor:pointer;}#mermaid-svg-U7igmvsWCKlZmXWn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-U7igmvsWCKlZmXWn .arrowheadPath{fill:#333333;}#mermaid-svg-U7igmvsWCKlZmXWn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-U7igmvsWCKlZmXWn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-U7igmvsWCKlZmXWn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U7igmvsWCKlZmXWn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-U7igmvsWCKlZmXWn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U7igmvsWCKlZmXWn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-U7igmvsWCKlZmXWn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-U7igmvsWCKlZmXWn .cluster text{fill:#333;}#mermaid-svg-U7igmvsWCKlZmXWn .cluster span{color:#333;}#mermaid-svg-U7igmvsWCKlZmXWn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-U7igmvsWCKlZmXWn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-U7igmvsWCKlZmXWn rect.text{fill:none;stroke-width:0;}#mermaid-svg-U7igmvsWCKlZmXWn .icon-shape,#mermaid-svg-U7igmvsWCKlZmXWn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U7igmvsWCKlZmXWn .icon-shape p,#mermaid-svg-U7igmvsWCKlZmXWn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-U7igmvsWCKlZmXWn .icon-shape .label rect,#mermaid-svg-U7igmvsWCKlZmXWn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U7igmvsWCKlZmXWn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-U7igmvsWCKlZmXWn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-U7igmvsWCKlZmXWn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否



LOG_INFO 宏
构造临时 LogStream
LogBuffer 收集 operator<< 内容
语句结束或 flush
LogBuffer::putOutput
LogSystem::log
level >= minLevel
过滤 不入队
创建 LogRecord
LogQueue 是否未满
丢弃并增加 droppedCount
push 到 LogQueue
唤醒后台 writerLoop
pop LogRecord
分发给 FileSink
格式化并写入文件

这条链路的重点是:业务线程只走到队列入队。文件格式化、滚动和磁盘写入都在后台线程里完成。

结构化 LogRecord

LogRecord 是系统升级后的核心。它把原来的一整条字符串拆成可决策的字段:

  • level:用于等级过滤、ERROR flush、未来按等级输出到不同 sink。

  • timestamp:日志创建时间,避免后台线程延迟导致时间不准。

  • thread_id:记录生产日志的业务线程,便于排查并发问题。

  • location :由 std::source_location 捕获文件名和行号。

  • message:流式日志正文。

#mermaid-svg-xLeusAe9BEKkI8qP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xLeusAe9BEKkI8qP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xLeusAe9BEKkI8qP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xLeusAe9BEKkI8qP .error-icon{fill:#552222;}#mermaid-svg-xLeusAe9BEKkI8qP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xLeusAe9BEKkI8qP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xLeusAe9BEKkI8qP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xLeusAe9BEKkI8qP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xLeusAe9BEKkI8qP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xLeusAe9BEKkI8qP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xLeusAe9BEKkI8qP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xLeusAe9BEKkI8qP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xLeusAe9BEKkI8qP .marker.cross{stroke:#333333;}#mermaid-svg-xLeusAe9BEKkI8qP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xLeusAe9BEKkI8qP p{margin:0;}#mermaid-svg-xLeusAe9BEKkI8qP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xLeusAe9BEKkI8qP .cluster-label text{fill:#333;}#mermaid-svg-xLeusAe9BEKkI8qP .cluster-label span{color:#333;}#mermaid-svg-xLeusAe9BEKkI8qP .cluster-label span p{background-color:transparent;}#mermaid-svg-xLeusAe9BEKkI8qP .label text,#mermaid-svg-xLeusAe9BEKkI8qP span{fill:#333;color:#333;}#mermaid-svg-xLeusAe9BEKkI8qP .node rect,#mermaid-svg-xLeusAe9BEKkI8qP .node circle,#mermaid-svg-xLeusAe9BEKkI8qP .node ellipse,#mermaid-svg-xLeusAe9BEKkI8qP .node polygon,#mermaid-svg-xLeusAe9BEKkI8qP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xLeusAe9BEKkI8qP .rough-node .label text,#mermaid-svg-xLeusAe9BEKkI8qP .node .label text,#mermaid-svg-xLeusAe9BEKkI8qP .image-shape .label,#mermaid-svg-xLeusAe9BEKkI8qP .icon-shape .label{text-anchor:middle;}#mermaid-svg-xLeusAe9BEKkI8qP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xLeusAe9BEKkI8qP .rough-node .label,#mermaid-svg-xLeusAe9BEKkI8qP .node .label,#mermaid-svg-xLeusAe9BEKkI8qP .image-shape .label,#mermaid-svg-xLeusAe9BEKkI8qP .icon-shape .label{text-align:center;}#mermaid-svg-xLeusAe9BEKkI8qP .node.clickable{cursor:pointer;}#mermaid-svg-xLeusAe9BEKkI8qP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xLeusAe9BEKkI8qP .arrowheadPath{fill:#333333;}#mermaid-svg-xLeusAe9BEKkI8qP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xLeusAe9BEKkI8qP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xLeusAe9BEKkI8qP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xLeusAe9BEKkI8qP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xLeusAe9BEKkI8qP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xLeusAe9BEKkI8qP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xLeusAe9BEKkI8qP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xLeusAe9BEKkI8qP .cluster text{fill:#333;}#mermaid-svg-xLeusAe9BEKkI8qP .cluster span{color:#333;}#mermaid-svg-xLeusAe9BEKkI8qP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xLeusAe9BEKkI8qP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xLeusAe9BEKkI8qP rect.text{fill:none;stroke-width:0;}#mermaid-svg-xLeusAe9BEKkI8qP .icon-shape,#mermaid-svg-xLeusAe9BEKkI8qP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xLeusAe9BEKkI8qP .icon-shape p,#mermaid-svg-xLeusAe9BEKkI8qP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xLeusAe9BEKkI8qP .icon-shape .label rect,#mermaid-svg-xLeusAe9BEKkI8qP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xLeusAe9BEKkI8qP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xLeusAe9BEKkI8qP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xLeusAe9BEKkI8qP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LogBuffer message
LogRecord
LogLevel
system_clock::now
this_thread::get_id
source_location

结构化记录的好处是:后续做 JSON、过滤、多个 sink、采样、按等级 flush,都不用从字符串里反解析。

等级过滤

LogSystemmMinLevel 保存最低日志等级:
#mermaid-svg-yYoSV5KR1d90zziY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yYoSV5KR1d90zziY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yYoSV5KR1d90zziY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yYoSV5KR1d90zziY .error-icon{fill:#552222;}#mermaid-svg-yYoSV5KR1d90zziY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yYoSV5KR1d90zziY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yYoSV5KR1d90zziY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yYoSV5KR1d90zziY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yYoSV5KR1d90zziY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yYoSV5KR1d90zziY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yYoSV5KR1d90zziY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yYoSV5KR1d90zziY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yYoSV5KR1d90zziY .marker.cross{stroke:#333333;}#mermaid-svg-yYoSV5KR1d90zziY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yYoSV5KR1d90zziY p{margin:0;}#mermaid-svg-yYoSV5KR1d90zziY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yYoSV5KR1d90zziY .cluster-label text{fill:#333;}#mermaid-svg-yYoSV5KR1d90zziY .cluster-label span{color:#333;}#mermaid-svg-yYoSV5KR1d90zziY .cluster-label span p{background-color:transparent;}#mermaid-svg-yYoSV5KR1d90zziY .label text,#mermaid-svg-yYoSV5KR1d90zziY span{fill:#333;color:#333;}#mermaid-svg-yYoSV5KR1d90zziY .node rect,#mermaid-svg-yYoSV5KR1d90zziY .node circle,#mermaid-svg-yYoSV5KR1d90zziY .node ellipse,#mermaid-svg-yYoSV5KR1d90zziY .node polygon,#mermaid-svg-yYoSV5KR1d90zziY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yYoSV5KR1d90zziY .rough-node .label text,#mermaid-svg-yYoSV5KR1d90zziY .node .label text,#mermaid-svg-yYoSV5KR1d90zziY .image-shape .label,#mermaid-svg-yYoSV5KR1d90zziY .icon-shape .label{text-anchor:middle;}#mermaid-svg-yYoSV5KR1d90zziY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yYoSV5KR1d90zziY .rough-node .label,#mermaid-svg-yYoSV5KR1d90zziY .node .label,#mermaid-svg-yYoSV5KR1d90zziY .image-shape .label,#mermaid-svg-yYoSV5KR1d90zziY .icon-shape .label{text-align:center;}#mermaid-svg-yYoSV5KR1d90zziY .node.clickable{cursor:pointer;}#mermaid-svg-yYoSV5KR1d90zziY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yYoSV5KR1d90zziY .arrowheadPath{fill:#333333;}#mermaid-svg-yYoSV5KR1d90zziY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yYoSV5KR1d90zziY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yYoSV5KR1d90zziY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yYoSV5KR1d90zziY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yYoSV5KR1d90zziY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yYoSV5KR1d90zziY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yYoSV5KR1d90zziY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yYoSV5KR1d90zziY .cluster text{fill:#333;}#mermaid-svg-yYoSV5KR1d90zziY .cluster span{color:#333;}#mermaid-svg-yYoSV5KR1d90zziY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yYoSV5KR1d90zziY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yYoSV5KR1d90zziY rect.text{fill:none;stroke-width:0;}#mermaid-svg-yYoSV5KR1d90zziY .icon-shape,#mermaid-svg-yYoSV5KR1d90zziY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yYoSV5KR1d90zziY .icon-shape p,#mermaid-svg-yYoSV5KR1d90zziY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yYoSV5KR1d90zziY .icon-shape .label rect,#mermaid-svg-yYoSV5KR1d90zziY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yYoSV5KR1d90zziY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yYoSV5KR1d90zziY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yYoSV5KR1d90zziY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否

log(level,message)
读取 mMinLevel
level >= minLevel
直接返回 false
创建 LogRecord
写入队列

当前过滤发生在 LogSystem::log() 内。也就是说,LOG_DEBUG << ... 的流式拼接已经发生,但不会进入队列。后续如果要进一步减少 DEBUG 开销,可以把宏改成"等级关闭时不构造 LogStream"的短路宏。

有界队列和丢弃计数

LogQueue 支持 max_queue_size

  • max_queue_size == 0:不限制队列长度。

  • max_queue_size > 0:队列达到容量后,新日志会被丢弃。

  • droppedCount():返回因为队列满或 shutdown 后写入失败而丢弃的日志数量。

#mermaid-svg-B0VeX50WwT9t6lkF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-B0VeX50WwT9t6lkF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-B0VeX50WwT9t6lkF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-B0VeX50WwT9t6lkF .error-icon{fill:#552222;}#mermaid-svg-B0VeX50WwT9t6lkF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B0VeX50WwT9t6lkF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-B0VeX50WwT9t6lkF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B0VeX50WwT9t6lkF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B0VeX50WwT9t6lkF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-B0VeX50WwT9t6lkF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B0VeX50WwT9t6lkF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B0VeX50WwT9t6lkF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B0VeX50WwT9t6lkF .marker.cross{stroke:#333333;}#mermaid-svg-B0VeX50WwT9t6lkF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B0VeX50WwT9t6lkF p{margin:0;}#mermaid-svg-B0VeX50WwT9t6lkF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-B0VeX50WwT9t6lkF .cluster-label text{fill:#333;}#mermaid-svg-B0VeX50WwT9t6lkF .cluster-label span{color:#333;}#mermaid-svg-B0VeX50WwT9t6lkF .cluster-label span p{background-color:transparent;}#mermaid-svg-B0VeX50WwT9t6lkF .label text,#mermaid-svg-B0VeX50WwT9t6lkF span{fill:#333;color:#333;}#mermaid-svg-B0VeX50WwT9t6lkF .node rect,#mermaid-svg-B0VeX50WwT9t6lkF .node circle,#mermaid-svg-B0VeX50WwT9t6lkF .node ellipse,#mermaid-svg-B0VeX50WwT9t6lkF .node polygon,#mermaid-svg-B0VeX50WwT9t6lkF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-B0VeX50WwT9t6lkF .rough-node .label text,#mermaid-svg-B0VeX50WwT9t6lkF .node .label text,#mermaid-svg-B0VeX50WwT9t6lkF .image-shape .label,#mermaid-svg-B0VeX50WwT9t6lkF .icon-shape .label{text-anchor:middle;}#mermaid-svg-B0VeX50WwT9t6lkF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-B0VeX50WwT9t6lkF .rough-node .label,#mermaid-svg-B0VeX50WwT9t6lkF .node .label,#mermaid-svg-B0VeX50WwT9t6lkF .image-shape .label,#mermaid-svg-B0VeX50WwT9t6lkF .icon-shape .label{text-align:center;}#mermaid-svg-B0VeX50WwT9t6lkF .node.clickable{cursor:pointer;}#mermaid-svg-B0VeX50WwT9t6lkF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-B0VeX50WwT9t6lkF .arrowheadPath{fill:#333333;}#mermaid-svg-B0VeX50WwT9t6lkF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-B0VeX50WwT9t6lkF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-B0VeX50WwT9t6lkF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B0VeX50WwT9t6lkF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-B0VeX50WwT9t6lkF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B0VeX50WwT9t6lkF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-B0VeX50WwT9t6lkF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-B0VeX50WwT9t6lkF .cluster text{fill:#333;}#mermaid-svg-B0VeX50WwT9t6lkF .cluster span{color:#333;}#mermaid-svg-B0VeX50WwT9t6lkF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-B0VeX50WwT9t6lkF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-B0VeX50WwT9t6lkF rect.text{fill:none;stroke-width:0;}#mermaid-svg-B0VeX50WwT9t6lkF .icon-shape,#mermaid-svg-B0VeX50WwT9t6lkF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-B0VeX50WwT9t6lkF .icon-shape p,#mermaid-svg-B0VeX50WwT9t6lkF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-B0VeX50WwT9t6lkF .icon-shape .label rect,#mermaid-svg-B0VeX50WwT9t6lkF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-B0VeX50WwT9t6lkF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-B0VeX50WwT9t6lkF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-B0VeX50WwT9t6lkF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



LogQueue::push
加锁 mMutex
mShutdown?
droppedCount++ 返回 false
队列是否已满
push LogRecord
解锁
notify_one 唤醒后台线程

有界队列是生产系统里很重要的保护:当磁盘很慢或者日志爆发时,logger 不应该无限吃内存。

多生产者单消费者模型

#mermaid-svg-QLYZLAXJUVXWHO1y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QLYZLAXJUVXWHO1y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QLYZLAXJUVXWHO1y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QLYZLAXJUVXWHO1y .error-icon{fill:#552222;}#mermaid-svg-QLYZLAXJUVXWHO1y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QLYZLAXJUVXWHO1y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QLYZLAXJUVXWHO1y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QLYZLAXJUVXWHO1y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QLYZLAXJUVXWHO1y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QLYZLAXJUVXWHO1y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QLYZLAXJUVXWHO1y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QLYZLAXJUVXWHO1y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QLYZLAXJUVXWHO1y .marker.cross{stroke:#333333;}#mermaid-svg-QLYZLAXJUVXWHO1y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QLYZLAXJUVXWHO1y p{margin:0;}#mermaid-svg-QLYZLAXJUVXWHO1y .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QLYZLAXJUVXWHO1y .cluster-label text{fill:#333;}#mermaid-svg-QLYZLAXJUVXWHO1y .cluster-label span{color:#333;}#mermaid-svg-QLYZLAXJUVXWHO1y .cluster-label span p{background-color:transparent;}#mermaid-svg-QLYZLAXJUVXWHO1y .label text,#mermaid-svg-QLYZLAXJUVXWHO1y span{fill:#333;color:#333;}#mermaid-svg-QLYZLAXJUVXWHO1y .node rect,#mermaid-svg-QLYZLAXJUVXWHO1y .node circle,#mermaid-svg-QLYZLAXJUVXWHO1y .node ellipse,#mermaid-svg-QLYZLAXJUVXWHO1y .node polygon,#mermaid-svg-QLYZLAXJUVXWHO1y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QLYZLAXJUVXWHO1y .rough-node .label text,#mermaid-svg-QLYZLAXJUVXWHO1y .node .label text,#mermaid-svg-QLYZLAXJUVXWHO1y .image-shape .label,#mermaid-svg-QLYZLAXJUVXWHO1y .icon-shape .label{text-anchor:middle;}#mermaid-svg-QLYZLAXJUVXWHO1y .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QLYZLAXJUVXWHO1y .rough-node .label,#mermaid-svg-QLYZLAXJUVXWHO1y .node .label,#mermaid-svg-QLYZLAXJUVXWHO1y .image-shape .label,#mermaid-svg-QLYZLAXJUVXWHO1y .icon-shape .label{text-align:center;}#mermaid-svg-QLYZLAXJUVXWHO1y .node.clickable{cursor:pointer;}#mermaid-svg-QLYZLAXJUVXWHO1y .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QLYZLAXJUVXWHO1y .arrowheadPath{fill:#333333;}#mermaid-svg-QLYZLAXJUVXWHO1y .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QLYZLAXJUVXWHO1y .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QLYZLAXJUVXWHO1y .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QLYZLAXJUVXWHO1y .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QLYZLAXJUVXWHO1y .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QLYZLAXJUVXWHO1y .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QLYZLAXJUVXWHO1y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QLYZLAXJUVXWHO1y .cluster text{fill:#333;}#mermaid-svg-QLYZLAXJUVXWHO1y .cluster span{color:#333;}#mermaid-svg-QLYZLAXJUVXWHO1y div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QLYZLAXJUVXWHO1y .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QLYZLAXJUVXWHO1y rect.text{fill:none;stroke-width:0;}#mermaid-svg-QLYZLAXJUVXWHO1y .icon-shape,#mermaid-svg-QLYZLAXJUVXWHO1y .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QLYZLAXJUVXWHO1y .icon-shape p,#mermaid-svg-QLYZLAXJUVXWHO1y .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QLYZLAXJUVXWHO1y .icon-shape .label rect,#mermaid-svg-QLYZLAXJUVXWHO1y .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QLYZLAXJUVXWHO1y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QLYZLAXJUVXWHO1y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QLYZLAXJUVXWHO1y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后台线程
LogQueue
业务线程
保护 push/pop
等待和唤醒
队列满或关闭
Thread A
Thread B
Thread C
queue
mMutex
mCondVar
droppedCount
writerLoop
LogSink 列表
FileSink

并发边界集中在 LogQueue

  • 生产者 :多个业务线程同时 push(),由 mMutex 串行保护队列。

  • 消费者 :一个后台线程 pop(),没有日志时阻塞在 mCondVar

  • 关闭语义:shutdown 后不接受新日志,但已入队日志会继续 drain。

Sink 输出架构

LogSink 是抽象输出端:
#mermaid-svg-pIcp7gMjBrjquUR3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pIcp7gMjBrjquUR3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pIcp7gMjBrjquUR3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pIcp7gMjBrjquUR3 .error-icon{fill:#552222;}#mermaid-svg-pIcp7gMjBrjquUR3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pIcp7gMjBrjquUR3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pIcp7gMjBrjquUR3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pIcp7gMjBrjquUR3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pIcp7gMjBrjquUR3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pIcp7gMjBrjquUR3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pIcp7gMjBrjquUR3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pIcp7gMjBrjquUR3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pIcp7gMjBrjquUR3 .marker.cross{stroke:#333333;}#mermaid-svg-pIcp7gMjBrjquUR3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pIcp7gMjBrjquUR3 p{margin:0;}#mermaid-svg-pIcp7gMjBrjquUR3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pIcp7gMjBrjquUR3 .cluster-label text{fill:#333;}#mermaid-svg-pIcp7gMjBrjquUR3 .cluster-label span{color:#333;}#mermaid-svg-pIcp7gMjBrjquUR3 .cluster-label span p{background-color:transparent;}#mermaid-svg-pIcp7gMjBrjquUR3 .label text,#mermaid-svg-pIcp7gMjBrjquUR3 span{fill:#333;color:#333;}#mermaid-svg-pIcp7gMjBrjquUR3 .node rect,#mermaid-svg-pIcp7gMjBrjquUR3 .node circle,#mermaid-svg-pIcp7gMjBrjquUR3 .node ellipse,#mermaid-svg-pIcp7gMjBrjquUR3 .node polygon,#mermaid-svg-pIcp7gMjBrjquUR3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pIcp7gMjBrjquUR3 .rough-node .label text,#mermaid-svg-pIcp7gMjBrjquUR3 .node .label text,#mermaid-svg-pIcp7gMjBrjquUR3 .image-shape .label,#mermaid-svg-pIcp7gMjBrjquUR3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-pIcp7gMjBrjquUR3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pIcp7gMjBrjquUR3 .rough-node .label,#mermaid-svg-pIcp7gMjBrjquUR3 .node .label,#mermaid-svg-pIcp7gMjBrjquUR3 .image-shape .label,#mermaid-svg-pIcp7gMjBrjquUR3 .icon-shape .label{text-align:center;}#mermaid-svg-pIcp7gMjBrjquUR3 .node.clickable{cursor:pointer;}#mermaid-svg-pIcp7gMjBrjquUR3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pIcp7gMjBrjquUR3 .arrowheadPath{fill:#333333;}#mermaid-svg-pIcp7gMjBrjquUR3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pIcp7gMjBrjquUR3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pIcp7gMjBrjquUR3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pIcp7gMjBrjquUR3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pIcp7gMjBrjquUR3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pIcp7gMjBrjquUR3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pIcp7gMjBrjquUR3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pIcp7gMjBrjquUR3 .cluster text{fill:#333;}#mermaid-svg-pIcp7gMjBrjquUR3 .cluster span{color:#333;}#mermaid-svg-pIcp7gMjBrjquUR3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pIcp7gMjBrjquUR3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pIcp7gMjBrjquUR3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-pIcp7gMjBrjquUR3 .icon-shape,#mermaid-svg-pIcp7gMjBrjquUR3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pIcp7gMjBrjquUR3 .icon-shape p,#mermaid-svg-pIcp7gMjBrjquUR3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pIcp7gMjBrjquUR3 .icon-shape .label rect,#mermaid-svg-pIcp7gMjBrjquUR3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pIcp7gMjBrjquUR3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pIcp7gMjBrjquUR3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pIcp7gMjBrjquUR3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

writerLoop pop LogRecord
遍历 mSinks
LogSink::write
FileSink::write
formatRecord
rotateIfNeeded
ofstream 写入
level == Error
flush
依赖文件缓冲

当前只实现了 FileSink,但 LogSystem 已经不再绑定具体输出方式。后续可以自然增加:

  • ConsoleSink:输出到 stdout/stderr。

  • JsonFileSink:写 JSON Lines。

  • NetworkSink:发送到日志服务。

  • MultiFileSink:不同等级写不同文件。

FileSink 文件滚动

FileSink 根据 max_file_sizemax_rotated_files 执行滚动:
#mermaid-svg-Ig8wVAXwQJApsCs8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ig8wVAXwQJApsCs8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ig8wVAXwQJApsCs8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ig8wVAXwQJApsCs8 .error-icon{fill:#552222;}#mermaid-svg-Ig8wVAXwQJApsCs8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ig8wVAXwQJApsCs8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ig8wVAXwQJApsCs8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ig8wVAXwQJApsCs8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ig8wVAXwQJApsCs8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ig8wVAXwQJApsCs8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ig8wVAXwQJApsCs8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ig8wVAXwQJApsCs8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ig8wVAXwQJApsCs8 .marker.cross{stroke:#333333;}#mermaid-svg-Ig8wVAXwQJApsCs8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ig8wVAXwQJApsCs8 p{margin:0;}#mermaid-svg-Ig8wVAXwQJApsCs8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ig8wVAXwQJApsCs8 .cluster-label text{fill:#333;}#mermaid-svg-Ig8wVAXwQJApsCs8 .cluster-label span{color:#333;}#mermaid-svg-Ig8wVAXwQJApsCs8 .cluster-label span p{background-color:transparent;}#mermaid-svg-Ig8wVAXwQJApsCs8 .label text,#mermaid-svg-Ig8wVAXwQJApsCs8 span{fill:#333;color:#333;}#mermaid-svg-Ig8wVAXwQJApsCs8 .node rect,#mermaid-svg-Ig8wVAXwQJApsCs8 .node circle,#mermaid-svg-Ig8wVAXwQJApsCs8 .node ellipse,#mermaid-svg-Ig8wVAXwQJApsCs8 .node polygon,#mermaid-svg-Ig8wVAXwQJApsCs8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ig8wVAXwQJApsCs8 .rough-node .label text,#mermaid-svg-Ig8wVAXwQJApsCs8 .node .label text,#mermaid-svg-Ig8wVAXwQJApsCs8 .image-shape .label,#mermaid-svg-Ig8wVAXwQJApsCs8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ig8wVAXwQJApsCs8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ig8wVAXwQJApsCs8 .rough-node .label,#mermaid-svg-Ig8wVAXwQJApsCs8 .node .label,#mermaid-svg-Ig8wVAXwQJApsCs8 .image-shape .label,#mermaid-svg-Ig8wVAXwQJApsCs8 .icon-shape .label{text-align:center;}#mermaid-svg-Ig8wVAXwQJApsCs8 .node.clickable{cursor:pointer;}#mermaid-svg-Ig8wVAXwQJApsCs8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ig8wVAXwQJApsCs8 .arrowheadPath{fill:#333333;}#mermaid-svg-Ig8wVAXwQJApsCs8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ig8wVAXwQJApsCs8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ig8wVAXwQJApsCs8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ig8wVAXwQJApsCs8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ig8wVAXwQJApsCs8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ig8wVAXwQJApsCs8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ig8wVAXwQJApsCs8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ig8wVAXwQJApsCs8 .cluster text{fill:#333;}#mermaid-svg-Ig8wVAXwQJApsCs8 .cluster span{color:#333;}#mermaid-svg-Ig8wVAXwQJApsCs8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ig8wVAXwQJApsCs8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ig8wVAXwQJApsCs8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ig8wVAXwQJApsCs8 .icon-shape,#mermaid-svg-Ig8wVAXwQJApsCs8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ig8wVAXwQJApsCs8 .icon-shape p,#mermaid-svg-Ig8wVAXwQJApsCs8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ig8wVAXwQJApsCs8 .icon-shape .label rect,#mermaid-svg-Ig8wVAXwQJApsCs8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ig8wVAXwQJApsCs8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ig8wVAXwQJApsCs8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ig8wVAXwQJApsCs8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否



准备写入一条日志
计算 next_write_size
是否启用滚动
直接写入
当前大小 + 新日志 <= max_file_size
flush 并关闭当前文件
删除最老文件 app.log.N
从后往前 rename app.log.i
app.log -> app.log.1
重新打开 app.log

如果配置为:

cpp 复制代码
config.file_path = "logs/app.log";

config.max_file_size = 10 * 1024 * 1024;

config.max_rotated_files = 3;

滚动后会保留:

  • logs/app.log

  • logs/app.log.1

  • logs/app.log.2

  • logs/app.log.3

shutdown 流程

#mermaid-svg-tlIJ1Rl31ItbqtgN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tlIJ1Rl31ItbqtgN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tlIJ1Rl31ItbqtgN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tlIJ1Rl31ItbqtgN .error-icon{fill:#552222;}#mermaid-svg-tlIJ1Rl31ItbqtgN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tlIJ1Rl31ItbqtgN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tlIJ1Rl31ItbqtgN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tlIJ1Rl31ItbqtgN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tlIJ1Rl31ItbqtgN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tlIJ1Rl31ItbqtgN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tlIJ1Rl31ItbqtgN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tlIJ1Rl31ItbqtgN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tlIJ1Rl31ItbqtgN .marker.cross{stroke:#333333;}#mermaid-svg-tlIJ1Rl31ItbqtgN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tlIJ1Rl31ItbqtgN p{margin:0;}#mermaid-svg-tlIJ1Rl31ItbqtgN defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-tlIJ1Rl31ItbqtgN g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-tlIJ1Rl31ItbqtgN g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-tlIJ1Rl31ItbqtgN g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-tlIJ1Rl31ItbqtgN g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-tlIJ1Rl31ItbqtgN g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-tlIJ1Rl31ItbqtgN .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-tlIJ1Rl31ItbqtgN .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-tlIJ1Rl31ItbqtgN .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-tlIJ1Rl31ItbqtgN .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tlIJ1Rl31ItbqtgN .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-tlIJ1Rl31ItbqtgN .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-tlIJ1Rl31ItbqtgN .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-tlIJ1Rl31ItbqtgN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tlIJ1Rl31ItbqtgN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tlIJ1Rl31ItbqtgN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tlIJ1Rl31ItbqtgN .edgeLabel .label text{fill:#333;}#mermaid-svg-tlIJ1Rl31ItbqtgN .label div .edgeLabel{color:#333;}#mermaid-svg-tlIJ1Rl31ItbqtgN .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-tlIJ1Rl31ItbqtgN .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-tlIJ1Rl31ItbqtgN .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-tlIJ1Rl31ItbqtgN .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-tlIJ1Rl31ItbqtgN .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-tlIJ1Rl31ItbqtgN .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tlIJ1Rl31ItbqtgN .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tlIJ1Rl31ItbqtgN #statediagram-barbEnd{fill:#333333;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tlIJ1Rl31ItbqtgN .cluster-label,#mermaid-svg-tlIJ1Rl31ItbqtgN .nodeLabel{color:#131300;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-tlIJ1Rl31ItbqtgN .note-edge{stroke-dasharray:5;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-note text{fill:black;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram-note .nodeLabel{color:black;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagram .edgeLabel{color:red;}#mermaid-svg-tlIJ1Rl31ItbqtgN #dependencyStart,#mermaid-svg-tlIJ1Rl31ItbqtgN #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-tlIJ1Rl31ItbqtgN .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tlIJ1Rl31ItbqtgN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} writerLoop 继续 pop
队列为空且已关闭
join 后 flush sink
Running
LogSystem::shutdown()
LogQueue::shutdown()
Draining
WriterExit
FlushingSinks
Closed

关闭流程采用 drain shutdown

  • 先设置 LogSystem::mShutdown:新日志不再接受。

  • 关闭 LogQueue:唤醒后台线程。

  • 后台线程继续消费旧日志:队列为空后退出。

  • join 后 flush sink:确保文件缓冲尽量落盘。

当前限制和下一步方向

  • 等级过滤还没有宏级短路 :低等级日志不会入队,但流式表达式仍会执行。后续可以设计 LOG_DEBUG 在关闭时不构造 LogStream

  • 队列满策略目前只有 DropNewest :生产上可以增加 BlockDropOldestSyncFallback

  • sink 注册接口暂未公开 :内部已经是 sink 架构,后续可以公开 addSink(std::unique_ptr<LogSink>)

  • 文件滚动没有压缩和清理策略之外的保留规则:生产环境可以加按日期滚动、压缩历史日志。

  • 格式化还固定为文本 :后续可以抽象 LogFormatter,支持文本、JSON、key-value 等格式。

相关推荐
猪脚饭还是好吃的1 小时前
【分享】C4droid 安卓C++编译器 手机编程超便捷
android·c++·智能手机
草莓熊Lotso1 小时前
【Linux网络】深入理解传输层 UDP 协议:从底层原理到实战应用
linux·运维·服务器·c语言·网络·c++·udp
小欣加油1 小时前
leetcode542 01矩阵
数据结构·c++·算法·leetcode·矩阵·bfs
原来是猿1 小时前
理解 C++ 哈希表的原理与工程实践
开发语言·c++·散列表
JdSnE27zv1 小时前
EF Code First学习笔记:数据库创建
数据库·笔记·学习
xian_wwq1 小时前
【学习笔记】「大模型安全:攻击面演化史」第 06 篇-红队方法论
笔记·学习·ai安全
很楠爱上1 小时前
Vue3 快速上手 — 精华笔记
笔记
牢姐与蒯1 小时前
c++数据结构之c++11(二)
开发语言·c++
凉、介1 小时前
深入理解 ARMv8-A|Application Binary Interface (ABI)
c语言·笔记·学习·嵌入式·arm