【C++】【Linux】从零实现日志库:LogLib 设计与实现详解

从零实现 C++ 日志库:LogLib 设计与实现详解

本文将深入剖析一个高性能 C++ 日志库的完整实现,涵盖无锁队列、内存池、异步日志、设计模式等核心技术。适合想要深入理解 C++ 高性能编程的开发者。


文章目录

  • [从零实现 C++ 日志库:LogLib 设计与实现详解](#从零实现 C++ 日志库:LogLib 设计与实现详解)
    • [1. 项目概述](#1. 项目概述)
      • [1.1 为什么要自己实现日志库?](#1.1 为什么要自己实现日志库?)
      • [1.2 设计目标](#1.2 设计目标)
      • [1.3 技术栈](#1.3 技术栈)
      • [1.4 实现效果](#1.4 实现效果)
    • [2. 整体架构设计](#2. 整体架构设计)
      • [2.1 分层架构](#2.1 分层架构)
      • [2.2 数据流](#2.2 数据流)
      • [2.3 文件结构](#2.3 文件结构)
    • [3. 核心数据结构](#3. 核心数据结构)
      • [3.1 无锁环形缓冲区](#3.1 无锁环形缓冲区)
        • [3.1.1 设计原理](#3.1.1 设计原理)
        • [3.1.2 核心实现](#3.1.2 核心实现)
        • [3.1.3 关键技术点](#3.1.3 关键技术点)
      • [3.2 内存池](#3.2 内存池)
        • [3.2.1 分级设计](#3.2.1 分级设计)
        • [3.2.2 固定大小块池](#3.2.2 固定大小块池)
        • [3.2.3 PooledString](#3.2.3 PooledString)
      • [3.3 栈上缓冲区](#3.3 栈上缓冲区)
    • [4. 日志系统核心](#4. 日志系统核心)
      • [4.1 日志消息结构](#4.1 日志消息结构)
      • [4.2 同步日志器](#4.2 同步日志器)
      • [4.3 异步日志器](#4.3 异步日志器)
    • [5. 输出端(Sink)设计](#5. 输出端(Sink)设计)
      • [5.1 Sink 接口](#5.1 Sink 接口)
      • [5.2 控制台 Sink](#5.2 控制台 Sink)
      • [5.3 文件 Sink(支持轮转)](#5.3 文件 Sink(支持轮转))
      • [5.4 每日轮转 Sink](#5.4 每日轮转 Sink)
    • [6. 格式化器设计](#6. 格式化器设计)
      • [6.1 Formatter 接口](#6.1 Formatter 接口)
      • [6.2 默认格式化器](#6.2 默认格式化器)
      • [6.3 JSON 格式化器](#6.3 JSON 格式化器)
      • [6.4 模式格式化器](#6.4 模式格式化器)
    • [7. 设计模式应用](#7. 设计模式应用)
      • [7.1 单例模式](#7.1 单例模式)
      • [7.2 工厂模式](#7.2 工厂模式)
      • [7.3 策略模式](#7.3 策略模式)
      • [7.4 建造者模式](#7.4 建造者模式)
    • [8. 性能优化技巧](#8. 性能优化技巧)
      • [8.1 编译期日志级别过滤](#8.1 编译期日志级别过滤)
      • [8.2 快速路径检查](#8.2 快速路径检查)
      • [8.3 避免字符串拷贝](#8.3 避免字符串拷贝)
      • [8.4 线程局部缓存](#8.4 线程局部缓存)
      • [8.5 批量写入](#8.5 批量写入)
    • [9. 线程安全保证](#9. 线程安全保证)
      • [9.1 原子操作](#9.1 原子操作)
      • [9.2 互斥锁保护](#9.2 互斥锁保护)
      • [9.3 无锁队列](#9.3 无锁队列)
    • [10. 使用示例](#10. 使用示例)
      • [10.1 基本使用](#10.1 基本使用)
      • [10.2 文件日志](#10.2 文件日志)
      • [10.3 异步日志](#10.3 异步日志)
      • [10.4 自定义 Sink](#10.4 自定义 Sink)
    • [11. 性能测试](#11. 性能测试)
      • [11.1 测试环境](#11.1 测试环境)
      • [11.2 测试结果](#11.2 测试结果)
      • [11.3 性能分析](#11.3 性能分析)
    • [12. 总结与展望](#12. 总结与展望)
      • [12.1 项目亮点](#12.1 项目亮点)
      • [12.2 不足之处](#12.2 不足之处)
      • [12.3 改进方向](#12.3 改进方向)
      • [12.4 学习价值](#12.4 学习价值)

1. 项目概述

1.1 为什么要自己实现日志库?

在 C++ 生态中,已经有 spdlog、glog、log4cpp 等优秀的日志库。那为什么还要自己实现一个呢?

  1. 学习价值:日志库是学习高性能 C++ 编程的绝佳项目,涉及多线程、内存管理、设计模式等核心技术
  2. 定制需求:某些场景需要特殊的日志格式或输出方式
  3. 依赖控制:减少第三方依赖,便于项目维护
  4. 性能极致:针对特定场景进行极致优化

1.2 设计目标

LogLib 的设计目标是:

目标 描述
高性能 百万级 logs/s 吞吐量,纳秒级延迟
线程安全 支持多线程并发写入
零开销 禁用的日志级别完全零开销
易用性 Header-only,开箱即用
可扩展 支持自定义 Sink 和 Formatter

1.3 技术栈

复制代码
┌─────────────────────────────────────────────────────────┐
│                      LogLib 技术栈                       │
├─────────────────────────────────────────────────────────┤
│  语言标准    │  C++17                                    │
│  构建系统    │  CMake 3.16+                              │
│  并发模型    │  MPSC 无锁队列 + 后台线程                  │
│  内存管理    │  分级内存池 + 栈上缓冲区                   │
│  设计模式    │  单例、工厂、策略、建造者                  │
│  测试框架    │  自定义 + Google Test                     │
└─────────────────────────────────────────────────────────┘

1.4 实现效果

编译:

bash 复制代码
mkdir build && cd build

cmake -DLOGLIB_BUILD_EXAMPLES=ON \
      -DLOGLIB_BUILD_TESTS=ON \
      -DLOGLIB_BUILD_BENCHMARKS=ON \
      -DLOGLIB_BUILD_GTEST=ON \
      ..

make -j$(nproc)

测试:

可用示例:

程序 源文件 说明
basic_usage examples/basic_usage.cpp 基本日志使用
async_logging examples/async_logging.cpp 异步日志示例
file_logging examples/file_logging.cpp 文件日志示例
custom_sink examples/custom_sink.cpp 自定义 Sink
config_example examples/config_example.cpp 配置文件使用
memory_pool_example examples/memory_pool_example.cpp 内存池示例

2. 整体架构设计

2.1 分层架构

LogLib 采用经典的分层架构:

复制代码
┌─────────────────────────────────────────────────────────┐
│                     用户接口层                           │
│  LOG_INFO() / ALOG_INFO() / CLOG_INFO()                │
├─────────────────────────────────────────────────────────┤
│                     日志器层                             │
│  Logger (同步) / AsyncLogger (异步)                     │
├─────────────────────────────────────────────────────────┤
│                     格式化层                             │
│  DefaultFormatter / JsonFormatter / PatternFormatter    │
├─────────────────────────────────────────────────────────┤
│                     输出层 (Sink)                        │
│  ConsoleSink / FileSink / DailyFileSink / SyslogSink   │
├─────────────────────────────────────────────────────────┤
│                     基础设施层                           │
│  RingBuffer / MemoryPool / StackBuffer / Format        │
└─────────────────────────────────────────────────────────┘

2.2 数据流

同步日志的数据流:

复制代码
用户调用 LOG_INFO("message")
         │
         ▼
    格式化消息 (StackBuffer)
         │
         ▼
    创建 LogMessage 结构
         │
         ▼
    遍历所有 Sink
         │
         ▼
    Formatter 格式化输出
         │
         ▼
    写入目标 (控制台/文件/...)

异步日志的数据流:

复制代码
用户调用 ALOG_INFO("message")
         │
         ▼
    格式化消息 (StackBuffer)
         │
         ▼
    创建 LogMessage 结构
         │
         ▼
    推入无锁队列 ◄──────────────┐
         │                      │
         ▼                      │
    立即返回                 后台线程
                                │
                           批量取出消息
                                │
                           遍历所有 Sink
                                │
                           写入目标

2.3 文件结构

复制代码
loglib/
├── include/loglib/
│   ├── loglib.h        # 主入口,包含所有头文件
│   ├── log_level.h     # 日志级别定义
│   ├── format.h        # 格式化工具
│   ├── ring_buffer.h   # 无锁环形缓冲区
│   ├── memory_pool.h   # 内存池
│   ├── sink.h          # Sink 接口和实现
│   ├── syslog_sink.h   # Syslog 支持
│   ├── pattern.h       # 模式格式化器
│   ├── logger.h        # Logger 核心实现
│   ├── config.h        # 配置文件解析
│   └── factory.h       # 工厂和建造者
├── examples/           # 示例代码
├── tests/              # 测试代码
├── benchmarks/         # 性能测试
└── docs/               # 文档

3. 核心数据结构

3.1 无锁环形缓冲区

无锁环形缓冲区是异步日志的核心,它允许多个生产者线程并发写入,单个消费者线程读取。

3.1.1 设计原理
复制代码
        head (生产者写入位置)
           │
           ▼
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │  capacity = 8
└───┴───┴───┴───┴───┴───┴───┴───┘
               ▲
               │
        tail (消费者读取位置)

- 当 head == tail 时,队列为空
- 当 (head + 1) % capacity == tail 时,队列满
- 使用 sequence 号实现无锁同步
3.1.2 核心实现
cpp 复制代码
template<typename T, size_t Capacity>
class RingBuffer {
    // 容量必须是 2 的幂,便于取模运算优化
    static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be power of 2");
    
public:
    // 多生产者安全的 push 操作
    template<typename U>
    bool try_push(U&& item) {
        size_type pos = head_.load(std::memory_order_relaxed);
        
        for (;;) {
            Cell& cell = buffer_[pos & mask()];
            size_type seq = cell.sequence.load(std::memory_order_acquire);
            intptr_t diff = static_cast<intptr_t>(seq) - static_cast<intptr_t>(pos);
            
            if (diff == 0) {
                // 槽位可用,尝试 CAS 获取
                if (head_.compare_exchange_weak(pos, pos + 1, 
                    std::memory_order_relaxed, std::memory_order_relaxed)) {
                    // 成功获取槽位,写入数据
                    cell.data = std::forward<U>(item);
                    // 更新 sequence,通知消费者数据已就绪
                    cell.sequence.store(pos + 1, std::memory_order_release);
                    return true;
                }
            } else if (diff < 0) {
                // 队列满
                return false;
            } else {
                // 其他生产者正在操作,重新读取 head
                pos = head_.load(std::memory_order_relaxed);
            }
        }
    }
    
    // 单消费者的 pop 操作
    bool try_pop(T& item) {
        size_type pos = tail_.load(std::memory_order_relaxed);
        
        for (;;) {
            Cell& cell = buffer_[pos & mask()];
            size_type seq = cell.sequence.load(std::memory_order_acquire);
            intptr_t diff = static_cast<intptr_t>(seq) - static_cast<intptr_t>(pos + 1);
            
            if (diff == 0) {
                // 数据已就绪
                if (tail_.compare_exchange_weak(pos, pos + 1,
                    std::memory_order_relaxed, std::memory_order_relaxed)) {
                    item = std::move(cell.data);
                    // 更新 sequence,释放槽位给生产者
                    cell.sequence.store(pos + Capacity, std::memory_order_release);
                    return true;
                }
            } else if (diff < 0) {
                // 队列空
                return false;
            } else {
                pos = tail_.load(std::memory_order_relaxed);
            }
        }
    }

private:
    // 缓存行对齐,避免伪共享
    struct alignas(64) Cell {
        std::atomic<size_type> sequence;
        T data;
    };
    
    alignas(64) std::atomic<size_type> head_;  // 生产者使用
    alignas(64) std::atomic<size_type> tail_;  // 消费者使用
    Cell buffer_[Capacity];
};
3.1.3 关键技术点

1. Sequence 号机制

每个槽位有一个 sequence 号,用于协调生产者和消费者:

  • 初始化时,sequence[i] = i
  • 生产者写入后,sequence = pos + 1
  • 消费者读取后,sequence = pos + Capacity

2. 缓存行对齐

cpp 复制代码
alignas(64) std::atomic<size_type> head_;
alignas(64) std::atomic<size_type> tail_;

head_tail_ 分别被不同的线程频繁访问,如果它们在同一缓存行,会导致伪共享(False Sharing),严重影响性能。

3. 内存序

  • memory_order_relaxed:用于 CAS 操作,只保证原子性
  • memory_order_acquire:读取 sequence 时,确保看到之前的写入
  • memory_order_release:写入 sequence 时,确保之前的写入对其他线程可见

3.2 内存池

频繁的 malloc/free 是性能杀手,内存池通过预分配和复用内存块来优化。

3.2.1 分级设计
cpp 复制代码
class LogBufferPool {
public:
    static constexpr size_t SMALL_SIZE = 256;    // 小消息
    static constexpr size_t MEDIUM_SIZE = 1024;  // 中等消息
    static constexpr size_t LARGE_SIZE = 4096;   // 大消息
    
    void* allocate(size_t size) {
        if (size <= SMALL_SIZE) {
            return small_pool_.allocate();
        } else if (size <= MEDIUM_SIZE) {
            return medium_pool_.allocate();
        } else if (size <= LARGE_SIZE) {
            return large_pool_.allocate();
        } else {
            // 超大消息,回退到堆分配
            return ::operator new(size);
        }
    }
    
private:
    FixedBlockPool<SMALL_SIZE, 2048> small_pool_;   // 2048 个小块
    FixedBlockPool<MEDIUM_SIZE, 512> medium_pool_;  // 512 个中块
    FixedBlockPool<LARGE_SIZE, 128> large_pool_;    // 128 个大块
};
3.2.2 固定大小块池
cpp 复制代码
template<size_t BlockSize, size_t InitialBlocks = 1024>
class FixedBlockPool {
public:
    void* allocate() {
        std::lock_guard<std::mutex> lock(mutex_);
        
        if (head_ == nullptr) {
            expand(InitialBlocks);  // 按需扩展
        }
        
        Node* node = head_;
        head_ = head_->next;
        return node;
    }
    
    void deallocate(void* ptr) {
        std::lock_guard<std::mutex> lock(mutex_);
        Node* node = static_cast<Node*>(ptr);
        node->next = head_;
        head_ = node;  // 头插法,O(1) 复杂度
    }

private:
    struct Node {
        Node* next;
    };
    
    void expand(size_t count) {
        // 分配一大块内存,切分成多个小块
        char* chunk = static_cast<char*>(::operator new(BlockSize * count));
        chunks_.push_back(chunk);
        
        for (size_t i = 0; i < count; ++i) {
            Node* node = reinterpret_cast<Node*>(chunk + i * BlockSize);
            node->next = head_;
            head_ = node;
        }
    }
    
    Node* head_ = nullptr;
    std::mutex mutex_;
    std::vector<char*> chunks_;  // 保存所有分配的大块,用于析构时释放
};
3.2.3 PooledString

使用内存池的字符串类:

cpp 复制代码
class PooledString {
public:
    void assign(std::string_view sv) {
        size_t new_size = sv.size();
        
        if (new_size > capacity_) {
            release();
            capacity_ = select_capacity(new_size);
            data_ = static_cast<char*>(LogBufferPool::instance().allocate(capacity_));
        }
        
        std::memcpy(data_, sv.data(), new_size);
        size_ = new_size;
        data_[size_] = '\0';
    }
    
private:
    static size_t select_capacity(size_t needed) {
        // 选择合适的容量级别
        if (needed < 256) return 256;
        if (needed < 1024) return 1024;
        if (needed < 4096) return 4096;
        return needed + 1;
    }
    
    char* data_ = nullptr;
    size_t size_ = 0;
    size_t capacity_ = 0;
};

3.3 栈上缓冲区

对于临时的格式化操作,使用栈上缓冲区避免堆分配:

cpp 复制代码
template<size_t N>
class StackBuffer {
public:
    void append(char c) {
        if (size_ < N - 1) {
            data_[size_++] = c;
            data_[size_] = '\0';
        }
    }
    
    void append(const char* str, size_t len) {
        size_t to_copy = std::min(len, N - 1 - size_);
        std::memcpy(data_ + size_, str, to_copy);
        size_ += to_copy;
        data_[size_] = '\0';
    }
    
    std::string_view view() const { 
        return std::string_view(data_, size_); 
    }

private:
    char data_[N];  // 栈上分配,无堆开销
    size_t size_ = 0;
};

使用场景

cpp 复制代码
void log_fmt(LogLevel level, std::string_view fmt, Args&&... args) {
    StackBuffer<4096> buf;  // 4KB 栈上缓冲区
    format_to(buf, fmt, args...);
    log(level, buf.view());
}

4. 日志系统核心

4.1 日志消息结构

cpp 复制代码
struct LogMessage {
    std::chrono::system_clock::time_point timestamp;  // 时间戳
    LogLevel level;                                    // 日志级别
    uint64_t thread_id;                                // 线程ID
    PooledString logger_name;                          // Logger名称
    PooledString file;                                 // 源文件名
    int line;                                          // 行号
    PooledString function;                             // 函数名
    PooledString message;                              // 日志消息
};

设计考量

  • 使用 PooledString 而非 std::string,减少内存分配
  • 包含完整的源码位置信息,便于调试
  • 时间戳使用 system_clock,精度到毫秒

4.2 同步日志器

cpp 复制代码
class Logger {
public:
    explicit Logger(std::string name = "")
        : name_(std::move(name)), level_(LogLevel::Info) {}
    
    // 设置日志级别
    void set_level(LogLevel level) {
        level_.store(level, std::memory_order_relaxed);
    }
    
    // 快速检查,避免不必要的格式化
    bool should_log(LogLevel level) const {
        return level >= level_.load(std::memory_order_relaxed);
    }
    
    // 添加输出端
    void add_sink(std::shared_ptr<Sink> sink) {
        std::lock_guard<std::mutex> lock(sinks_mutex_);
        sinks_.push_back(std::move(sink));
    }
    
    // 记录日志
    void log(LogLevel level, std::string_view message,
             const char* file = "", int line = 0, const char* function = "") {
        if (!should_log(level)) return;
        
        LogMessage msg;
        msg.timestamp = std::chrono::system_clock::now();
        msg.level = level;
        msg.thread_id = current_thread_id();
        msg.logger_name = name_;
        msg.file = file;
        msg.line = line;
        msg.function = function;
        msg.message = message;
        
        write_to_sinks(msg);
    }
    
    // 格式化日志
    template<typename... Args>
    void log_fmt(LogLevel level, SourceLocation loc, 
                 std::string_view fmt, Args&&... args) {
        if (!should_log(level)) return;
        
        StackBuffer<4096> buf;
        if constexpr (sizeof...(args) > 0) {
            FormatArg arg_array[] = { FormatArg(std::forward<Args>(args))... };
            format_to(buf, fmt, arg_array, sizeof...(args));
        } else {
            buf.append(fmt);
        }
        
        log(level, buf.view(), loc.file, loc.line, loc.function);
    }

protected:
    void write_to_sinks(const LogMessage& msg) {
        std::lock_guard<std::mutex> lock(sinks_mutex_);
        for (auto& sink : sinks_) {
            sink->write(msg);
        }
    }
    
    std::string name_;
    std::atomic<LogLevel> level_;
    std::vector<std::shared_ptr<Sink>> sinks_;
    std::mutex sinks_mutex_;
};

4.3 异步日志器

异步日志器继承自 Logger,使用后台线程处理日志写入:

cpp 复制代码
class AsyncLogger : public Logger {
public:
    struct Config {
        size_t queue_size = 8192;                          // 队列大小
        std::chrono::milliseconds flush_interval{100};     // 刷新间隔
        bool discard_on_full = false;                      // 队列满时是否丢弃
        size_t max_flush_batch = 256;                      // 批量处理大小
    };
    
    explicit AsyncLogger(std::string name = "", Config config = Config())
        : Logger(std::move(name))
        , config_(config)
        , queue_(config.queue_size)
        , running_(true)
        , pending_count_(0) {
        // 启动后台线程
        worker_thread_ = std::thread(&AsyncLogger::worker_loop, this);
    }
    
    ~AsyncLogger() {
        stop();
    }
    
    // 重写 log 方法,改为异步
    void log(LogLevel level, std::string_view message,
             const char* file = "", int line = 0, const char* function = "") override {
        if (!should_log(level)) return;
        
        LogMessage msg;
        msg.timestamp = std::chrono::system_clock::now();
        msg.level = level;
        msg.thread_id = current_thread_id();
        msg.logger_name = name_;
        msg.file = file;
        msg.line = line;
        msg.function = function;
        msg.message = message;
        
        enqueue(std::move(msg));
    }
    
    // 刷新:等待队列清空
    void flush() override {
        while (pending_count_.load(std::memory_order_acquire) > 0) {
            cv_.notify_one();
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
        }
        Logger::flush();
    }

private:
    void enqueue(LogMessage&& msg) {
        if (queue_.try_push(std::move(msg))) {
            pending_count_.fetch_add(1, std::memory_order_relaxed);
            cv_.notify_one();
        } else if (!config_.discard_on_full) {
            // 队列满,同步写入(降级处理)
            write_to_sinks(msg);
        }
        // 如果 discard_on_full 为 true,则丢弃
    }
    
    void worker_loop() {
        LogMessage msg;
        std::vector<LogMessage> batch;
        batch.reserve(config_.max_flush_batch);
        
        while (running_.load(std::memory_order_relaxed)) {
            // 等待有数据或超时
            {
                std::unique_lock<std::mutex> lock(cv_mutex_);
                cv_.wait_for(lock, config_.flush_interval, [this] {
                    return !running_.load(std::memory_order_relaxed) ||
                           pending_count_.load(std::memory_order_relaxed) > 0;
                });
            }
            
            // 批量处理
            batch.clear();
            while (batch.size() < config_.max_flush_batch && queue_.try_pop(msg)) {
                batch.push_back(std::move(msg));
            }
            
            if (!batch.empty()) {
                for (const auto& m : batch) {
                    write_to_sinks(m);
                }
                pending_count_.fetch_sub(batch.size(), std::memory_order_relaxed);
            }
        }
    }
    
    Config config_;
    DynamicRingBuffer<LogMessage> queue_;
    std::atomic<bool> running_;
    std::atomic<size_t> pending_count_;
    std::thread worker_thread_;
    std::mutex cv_mutex_;
    std::condition_variable cv_;
};

关键设计点

  1. 批量处理:一次取出多条日志,减少锁竞争和系统调用
  2. 降级策略:队列满时可以选择同步写入或丢弃
  3. 优雅关闭flush() 等待队列清空,stop() 处理剩余日志

5. 输出端(Sink)设计

5.1 Sink 接口

cpp 复制代码
class Sink {
public:
    virtual ~Sink() = default;
    
    // 写入日志
    virtual void write(const LogMessage& msg) = 0;
    
    // 刷新缓冲区
    virtual void flush() = 0;
    
    // 级别过滤
    void set_level(LogLevel level) {
        level_.store(level, std::memory_order_relaxed);
    }
    
    bool should_log(LogLevel level) const {
        return level >= level_.load(std::memory_order_relaxed);
    }

protected:
    std::atomic<LogLevel> level_{LogLevel::Trace};
};

5.2 控制台 Sink

cpp 复制代码
class ConsoleSink : public Sink {
public:
    explicit ConsoleSink(bool use_stderr = false, bool colored = true)
        : stream_(use_stderr ? &std::cerr : &std::cout)
        , formatter_(std::make_unique<DefaultFormatter>(colored, true)) {}
    
    void write(const LogMessage& msg) override {
        if (!should_log(msg.level)) return;
        
        std::string formatted = formatter_->format(msg);
        
        std::lock_guard<std::mutex> lock(mutex_);
        stream_->write(formatted.data(), formatted.size());
    }
    
    void flush() override {
        std::lock_guard<std::mutex> lock(mutex_);
        stream_->flush();
    }

private:
    std::ostream* stream_;
    std::unique_ptr<Formatter> formatter_;
    std::mutex mutex_;
};

5.3 文件 Sink(支持轮转)

cpp 复制代码
class FileSink : public Sink {
public:
    struct Config {
        std::string filename;
        size_t max_file_size = 10 * 1024 * 1024;  // 10MB
        size_t max_files = 5;
        bool rotate_on_open = false;
        bool truncate = false;
    };
    
    void write(const LogMessage& msg) override {
        if (!should_log(msg.level)) return;
        
        std::string formatted = formatter_->format(msg);
        
        std::lock_guard<std::mutex> lock(mutex_);
        
        // 检查是否需要轮转
        if (current_size_ + formatted.size() > config_.max_file_size) {
            rotate();
        }
        
        file_.write(formatted.data(), formatted.size());
        current_size_ += formatted.size();
    }

private:
    void rotate() {
        file_.close();
        
        // 删除最老的文件
        std::string oldest = config_.filename + "." + std::to_string(config_.max_files);
        if (std::filesystem::exists(oldest)) {
            std::filesystem::remove(oldest);
        }
        
        // 依次重命名: app.log.4 -> app.log.5, ..., app.log -> app.log.1
        for (size_t i = config_.max_files - 1; i >= 1; --i) {
            std::string src = config_.filename + "." + std::to_string(i);
            std::string dst = config_.filename + "." + std::to_string(i + 1);
            if (std::filesystem::exists(src)) {
                std::filesystem::rename(src, dst);
            }
        }
        
        // 重命名当前文件
        std::filesystem::rename(config_.filename, config_.filename + ".1");
        
        // 打开新文件
        file_.open(config_.filename, std::ios::out | std::ios::trunc);
        current_size_ = 0;
    }
    
    Config config_;
    std::ofstream file_;
    size_t current_size_;
    std::mutex mutex_;
};

5.4 每日轮转 Sink

cpp 复制代码
class DailyFileSink : public Sink {
public:
    struct Config {
        std::string base_filename;
        int rotation_hour = 0;    // 轮转小时 (0-23)
        int rotation_minute = 0;  // 轮转分钟 (0-59)
        size_t max_files = 7;     // 保留天数
    };
    
    void write(const LogMessage& msg) override {
        if (!should_log(msg.level)) return;
        
        std::lock_guard<std::mutex> lock(mutex_);
        
        // 检查是否需要轮转
        auto now = std::chrono::system_clock::now();
        if (now >= next_rotation_time_) {
            rotate();
        }
        
        file_.write(formatter_->format(msg));
    }

private:
    void update_filename() {
        // 生成文件名: base_filename.2024-01-15.log
        auto now = std::chrono::system_clock::now();
        auto time_t_now = std::chrono::system_clock::to_time_t(now);
        std::tm tm_now;
        localtime_r(&time_t_now, &tm_now);
        
        char date_buf[32];
        std::snprintf(date_buf, sizeof(date_buf), "%04d-%02d-%02d",
            tm_now.tm_year + 1900, tm_now.tm_mon + 1, tm_now.tm_mday);
        
        current_filename_ = config_.base_filename + "." + date_buf + ".log";
        
        // 计算下次轮转时间
        tm_now.tm_hour = config_.rotation_hour;
        tm_now.tm_min = config_.rotation_minute;
        tm_now.tm_sec = 0;
        tm_now.tm_mday += 1;  // 明天
        next_rotation_time_ = std::chrono::system_clock::from_time_t(std::mktime(&tm_now));
    }
};

6. 格式化器设计

6.1 Formatter 接口

cpp 复制代码
class Formatter {
public:
    virtual ~Formatter() = default;
    virtual std::string format(const LogMessage& msg) = 0;
};

6.2 默认格式化器

输出格式:[2024-01-15 10:30:45.123] [INFO ] [12345] [logger] message (file.cpp:42)

cpp 复制代码
class DefaultFormatter : public Formatter {
public:
    explicit DefaultFormatter(bool with_color = false, bool with_source = true)
        : with_color_(with_color), with_source_(with_source) {}
    
    std::string format(const LogMessage& msg) override {
        StackBuffer<4096> buf;
        
        // 时间戳
        buf.append('[');
        char ts_buf[32];
        size_t ts_len = format_timestamp(ts_buf, sizeof(ts_buf), msg.timestamp);
        buf.append(ts_buf, ts_len);
        buf.append("] ");
        
        // 日志级别(可选颜色)
        buf.append('[');
        if (with_color_) buf.append(level_color(msg.level));
        buf.append(to_string(msg.level));
        if (with_color_) buf.append(color_reset());
        buf.append("] ");
        
        // 线程ID
        buf.append('[');
        char tid_buf[24];
        size_t tid_len = format_int(tid_buf, sizeof(tid_buf), msg.thread_id);
        buf.append(tid_buf, tid_len);
        buf.append("] ");
        
        // Logger名称
        if (!msg.logger_name.empty()) {
            buf.append('[');
            buf.append(msg.logger_name);
            buf.append("] ");
        }
        
        // 消息
        buf.append(msg.message);
        
        // 源码位置
        if (with_source_ && !msg.file.empty()) {
            buf.append(" (");
            buf.append(msg.file);
            buf.append(':');
            char line_buf[16];
            size_t line_len = format_int(line_buf, sizeof(line_buf), msg.line);
            buf.append(line_buf, line_len);
            buf.append(')');
        }
        
        buf.append('\n');
        return buf.str();
    }

private:
    bool with_color_;
    bool with_source_;
};

6.3 JSON 格式化器

cpp 复制代码
class JsonFormatter : public Formatter {
public:
    std::string format(const LogMessage& msg) override {
        StackBuffer<4096> buf;
        
        buf.append("{\"timestamp\":\"");
        // ... 时间戳
        buf.append("\",\"level\":\"");
        buf.append(to_string(msg.level));
        buf.append("\",\"thread\":");
        // ... 线程ID
        buf.append(",\"message\":\"");
        // 转义 JSON 特殊字符
        for (char c : msg.message.view()) {
            switch (c) {
                case '"': buf.append("\\\""); break;
                case '\\': buf.append("\\\\"); break;
                case '\n': buf.append("\\n"); break;
                default: buf.append(c);
            }
        }
        buf.append("\"}\n");
        
        return buf.str();
    }
};

6.4 模式格式化器

支持类似 spdlog 的格式模式:

cpp 复制代码
class PatternFormatter : public Formatter {
public:
    explicit PatternFormatter(const std::string& pattern = 
        "[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v")
        : pattern_(pattern) {
        compile();  // 预编译模式
    }
    
    std::string format(const LogMessage& msg) override {
        StackBuffer<4096> buf;
        for (const auto& item : compiled_) {
            item(buf, msg);
        }
        buf.append('\n');
        return buf.str();
    }

private:
    using FormatFunc = std::function<void(StackBuffer<4096>&, const LogMessage&)>;
    
    void compile() {
        // 解析模式字符串,生成格式化函数列表
        for (size_t i = 0; i < pattern_.size(); ++i) {
            if (pattern_[i] == '%' && i + 1 < pattern_.size()) {
                compiled_.push_back(get_formatter(pattern_[++i]));
            } else {
                char c = pattern_[i];
                compiled_.push_back([c](StackBuffer<4096>& buf, const LogMessage&) {
                    buf.append(c);
                });
            }
        }
    }
    
    FormatFunc get_formatter(char spec) {
        switch (spec) {
            case 'Y': return /* 年 */;
            case 'm': return /* 月 */;
            case 'd': return /* 日 */;
            case 'H': return /* 时 */;
            case 'M': return /* 分 */;
            case 'S': return /* 秒 */;
            case 'e': return /* 毫秒 */;
            case 'l': return /* 日志级别 */;
            case 't': return /* 线程ID */;
            case 'n': return /* Logger名称 */;
            case 'v': return /* 消息 */;
            case 's': return /* 源文件 */;
            case '#': return /* 行号 */;
            // ...
        }
    }
    
    std::string pattern_;
    std::vector<FormatFunc> compiled_;
};

7. 设计模式应用

7.1 单例模式

用于全局唯一的管理器:

cpp 复制代码
class LogManager {
public:
    static LogManager& instance() {
        static LogManager instance;  // C++11 保证线程安全
        return instance;
    }
    
    // 禁止拷贝和移动
    LogManager(const LogManager&) = delete;
    LogManager& operator=(const LogManager&) = delete;

private:
    LogManager() = default;
    ~LogManager() { shutdown(); }
};

7.2 工厂模式

提供便捷的创建函数:

cpp 复制代码
namespace sinks {
    inline std::shared_ptr<ConsoleSink> console(bool use_stderr = false, bool colored = true) {
        return std::make_shared<ConsoleSink>(use_stderr, colored);
    }
    
    inline std::shared_ptr<FileSink> file(const std::string& filename, 
                                          size_t max_size = 10*1024*1024,
                                          size_t max_files = 5) {
        FileSink::Config config;
        config.filename = filename;
        config.max_file_size = max_size;
        config.max_files = max_files;
        return std::make_shared<FileSink>(config);
    }
}

namespace formatters {
    inline std::unique_ptr<DefaultFormatter> standard(bool colored = false) {
        return std::make_unique<DefaultFormatter>(colored, true);
    }
    
    inline std::unique_ptr<JsonFormatter> json() {
        return std::make_unique<JsonFormatter>();
    }
}

7.3 策略模式

Sink 和 Formatter 都是策略模式的应用:

cpp 复制代码
// Sink 策略:决定日志输出到哪里
class Sink { virtual void write(const LogMessage&) = 0; };
class ConsoleSink : public Sink { /* 输出到控制台 */ };
class FileSink : public Sink { /* 输出到文件 */ };

// Formatter 策略:决定日志的格式
class Formatter { virtual std::string format(const LogMessage&) = 0; };
class DefaultFormatter : public Formatter { /* 标准格式 */ };
class JsonFormatter : public Formatter { /* JSON格式 */ };

7.4 建造者模式

流式 API 创建 Logger:

cpp 复制代码
class LoggerBuilder {
public:
    LoggerBuilder& name(const std::string& n) { name_ = n; return *this; }
    LoggerBuilder& level(LogLevel l) { level_ = l; return *this; }
    LoggerBuilder& console(bool colored = true) { 
        sinks_.push_back(sinks::console(false, colored)); 
        return *this; 
    }
    LoggerBuilder& file(const std::string& path, size_t max_size = 10*1024*1024) {
        sinks_.push_back(sinks::file(path, max_size));
        return *this;
    }
    LoggerBuilder& async(bool enable = true, size_t queue_size = 8192) {
        async_ = enable;
        queue_size_ = queue_size;
        return *this;
    }
    
    std::shared_ptr<Logger> build() {
        auto logger = async_ 
            ? std::make_shared<AsyncLogger>(name_, AsyncLogger::Config{queue_size_})
            : std::make_shared<Logger>(name_);
        logger->set_level(level_);
        for (auto& sink : sinks_) {
            logger->add_sink(sink);
        }
        return logger;
    }

private:
    std::string name_;
    LogLevel level_ = LogLevel::Info;
    bool async_ = false;
    size_t queue_size_ = 8192;
    std::vector<std::shared_ptr<Sink>> sinks_;
};

// 使用
auto logger = loglib::logger("myapp")
    .level(LogLevel::Debug)
    .console(true)
    .file("app.log")
    .async(true)
    .build();

8. 性能优化技巧

8.1 编译期日志级别过滤

cpp 复制代码
// 编译时定义 LOGLIB_MIN_LEVEL=2,禁用 TRACE 和 DEBUG
#ifndef LOGLIB_MIN_LEVEL
#define LOGLIB_MIN_LEVEL 0
#endif

#if LOGLIB_MIN_LEVEL > 0
#undef LOG_TRACE
#define LOG_TRACE(...) ((void)0)  // 完全零开销
#endif

#if LOGLIB_MIN_LEVEL > 1
#undef LOG_DEBUG
#define LOG_DEBUG(...) ((void)0)
#endif

8.2 快速路径检查

cpp 复制代码
#define LOGLIB_LOGGER_CALL(logger, level, ...) \
    do { \
        if ((logger).should_log(level)) {  /* 快速检查 */ \
            (logger).log_fmt(level, LOGLIB_SOURCE_LOC(), __VA_ARGS__); \
        } \
    } while (0)

8.3 避免字符串拷贝

cpp 复制代码
// 使用 string_view 避免拷贝
void log(LogLevel level, std::string_view message);

// 使用 PooledString 复用内存
struct LogMessage {
    PooledString message;  // 而非 std::string
};

8.4 线程局部缓存

cpp 复制代码
inline uint64_t current_thread_id() {
    // 缓存线程ID,避免每次调用系统函数
    thread_local uint64_t tid = 
        std::hash<std::thread::id>{}(std::this_thread::get_id());
    return tid;
}

8.5 批量写入

cpp 复制代码
void worker_loop() {
    std::vector<LogMessage> batch;
    batch.reserve(256);  // 预分配
    
    while (running_) {
        // 一次取出多条日志
        while (batch.size() < 256 && queue_.try_pop(msg)) {
            batch.push_back(std::move(msg));
        }
        
        // 批量写入
        for (const auto& m : batch) {
            write_to_sinks(m);
        }
    }
}

9. 线程安全保证

9.1 原子操作

cpp 复制代码
class Logger {
    std::atomic<LogLevel> level_;  // 无锁读写
    
    bool should_log(LogLevel level) const {
        return level >= level_.load(std::memory_order_relaxed);
    }
};

9.2 互斥锁保护

cpp 复制代码
class Logger {
    std::vector<std::shared_ptr<Sink>> sinks_;
    std::mutex sinks_mutex_;
    
    void add_sink(std::shared_ptr<Sink> sink) {
        std::lock_guard<std::mutex> lock(sinks_mutex_);
        sinks_.push_back(std::move(sink));
    }
    
    void write_to_sinks(const LogMessage& msg) {
        std::lock_guard<std::mutex> lock(sinks_mutex_);
        for (auto& sink : sinks_) {
            sink->write(msg);
        }
    }
};

9.3 无锁队列

cpp 复制代码
class AsyncLogger {
    DynamicRingBuffer<LogMessage> queue_;  // 无锁 MPSC 队列
    
    void log(...) {
        // 多个线程可以并发调用
        queue_.try_push(std::move(msg));
    }
};

10. 使用示例

10.1 基本使用

cpp 复制代码
#include <loglib/loglib.h>

int main() {
    // 最简单的使用方式
    LOG_INFO("Hello, LogLib!");
    LOG_DEBUG("Debug value: {}", 42);
    LOG_WARN("Warning message");
    LOG_ERROR("Error: {}", "something wrong");
    
    return 0;
}

10.2 文件日志

cpp 复制代码
#include <loglib/loglib.h>

int main() {
    // 初始化文件日志
    loglib::init_with_file("app.log", 10*1024*1024, 5);
    
    LOG_INFO("日志会同时输出到控制台和文件");
    
    loglib::shutdown();
    return 0;
}

10.3 异步日志

cpp 复制代码
#include <loglib/loglib.h>
#include <thread>
#include <vector>

int main() {
    auto logger = loglib::logger("async")
        .level(loglib::LogLevel::Debug)
        .console(true)
        .async(true, 16384)
        .build_async();
    
    // 多线程并发写入
    std::vector<std::thread> threads;
    for (int t = 0; t < 4; ++t) {
        threads.emplace_back([&logger, t]() {
            for (int i = 0; i < 10000; ++i) {
                CLOG_INFO(*logger, "Thread {} log {}", t, i);
            }
        });
    }
    
    for (auto& th : threads) {
        th.join();
    }
    
    logger->flush();
    return 0;
}

10.4 自定义 Sink

cpp 复制代码
#include <loglib/loglib.h>

class NetworkSink : public loglib::Sink {
public:
    NetworkSink(const std::string& host, int port) 
        : host_(host), port_(port) {}
    
    void write(const loglib::LogMessage& msg) override {
        if (!should_log(msg.level)) return;
        // 发送到远程服务器
        send_to_server(msg.message.str());
    }
    
    void flush() override {
        // 刷新网络缓冲区
    }

private:
    std::string host_;
    int port_;
};

int main() {
    auto network_sink = std::make_shared<NetworkSink>("log.example.com", 5000);
    
    loglib::Logger logger("network");
    logger.add_sink(network_sink);
    
    CLOG_INFO(logger, "发送到远程服务器");
    
    return 0;
}

11. 性能测试

11.1 测试环境

  • CPU: 32 核
  • 内存: 64GB
  • 操作系统: Linux
  • 编译器: GCC 9.4
  • 编译选项: -O3 -DNDEBUG

11.2 测试结果

测试场景 吞吐量 平均延迟
格式化开销 5.9M ops/s 169 ns
时间戳格式化 16.6M ops/s 60 ns
同步日志 (单线程) 5.7M logs/s 175 ns
同步日志 (8线程) 357K logs/s 2.8 μs
异步日志 (单线程) 510K logs/s 1.96 μs
异步日志 (4线程) 1.25M logs/s 801 ns
文件写入 (同步) 1.29M logs/s 777 ns
文件写入 (异步) 468K logs/s 2.1 μs

11.3 性能分析

  1. 同步单线程最快:无锁竞争,直接写入
  2. 同步多线程下降明显:Sink 的 mutex 成为瓶颈
  3. 异步多线程表现稳定:无锁队列有效减少竞争
  4. 文件写入受 I/O 限制:磁盘成为瓶颈

12. 总结与展望

12.1 项目亮点

  1. 高性能:无锁队列 + 内存池 + 批量处理
  2. 易用性:Header-only + 流式 API
  3. 可扩展:策略模式支持自定义 Sink/Formatter
  4. 代码质量:完整测试 + 详细文档

12.2 不足之处

  1. 平台限制:仅支持 Linux/POSIX
  2. 格式化 :自实现,不如 {fmt} 库强大
  3. 内存池:使用 mutex,非完全无锁

12.3 改进方向

  1. 跨平台:支持 Windows
  2. 集成 fmt :使用 {fmt} 库替代自实现
  3. 无锁内存池:使用 lock-free 栈
  4. 结构化日志:支持 OpenTelemetry
  5. 远程日志:支持 Kafka、Elasticsearch 等

12.4 学习价值

通过这个项目,你可以学到:

  • 并发编程:无锁队列、原子操作、内存序
  • 内存管理:内存池、对象池、栈上分配
  • 设计模式:单例、工厂、策略、建造者
  • 现代 C++:C++17 特性、RAII、智能指针
  • 软件工程:模块化设计、测试、文档
相关推荐
大闲在人2 小时前
Trae builder 实战: 让 C++ 函数像 Python 一样返回多个值
c++·python·ai编程
txinyu的博客2 小时前
手写 C++ 高性能 Reactor 网络服务器
服务器·网络·c++
枫叶丹42 小时前
【Qt开发】Qt系统(八)-> Qt UDP Socket
c语言·开发语言·c++·qt·udp
一颗青果2 小时前
c++的异常机制
java·jvm·c++
程序猿编码2 小时前
无状态TCP技术:DNS代理的轻量级实现逻辑与核心原理(C/C++代码实现)
c语言·网络·c++·tcp/ip·dns
期货资管源码3 小时前
智星期货资管子账户软件pc端开发技术栈的选择
c语言·数据结构·c++·vue
Dream it possible!3 小时前
蓝桥杯_工作时长_C++
c++·蓝桥杯·竞赛
讳疾忌医丶3 小时前
C++中虚函数调用慢5倍?深入理解vtable和性能开销
开发语言·c++
kklovecode3 小时前
数据结构---顺序表
c语言·开发语言·数据结构·c++·算法