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_INFO 或 LogSystem::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,都不用从字符串里反解析。
等级过滤
LogSystem 用 mMinLevel 保存最低日志等级:
#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_size 和 max_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 :生产上可以增加
Block、DropOldest、SyncFallback。 -
sink 注册接口暂未公开 :内部已经是 sink 架构,后续可以公开
addSink(std::unique_ptr<LogSink>)。 -
文件滚动没有压缩和清理策略之外的保留规则:生产环境可以加按日期滚动、压缩历史日志。
-
格式化还固定为文本 :后续可以抽象
LogFormatter,支持文本、JSON、key-value 等格式。