从零实现 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 等优秀的日志库。那为什么还要自己实现一个呢?
- 学习价值:日志库是学习高性能 C++ 编程的绝佳项目,涉及多线程、内存管理、设计模式等核心技术
- 定制需求:某些场景需要特殊的日志格式或输出方式
- 依赖控制:减少第三方依赖,便于项目维护
- 性能极致:针对特定场景进行极致优化
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_;
};
关键设计点:
- 批量处理:一次取出多条日志,减少锁竞争和系统调用
- 降级策略:队列满时可以选择同步写入或丢弃
- 优雅关闭 :
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 性能分析
- 同步单线程最快:无锁竞争,直接写入
- 同步多线程下降明显:Sink 的 mutex 成为瓶颈
- 异步多线程表现稳定:无锁队列有效减少竞争
- 文件写入受 I/O 限制:磁盘成为瓶颈
12. 总结与展望
12.1 项目亮点
- 高性能:无锁队列 + 内存池 + 批量处理
- 易用性:Header-only + 流式 API
- 可扩展:策略模式支持自定义 Sink/Formatter
- 代码质量:完整测试 + 详细文档
12.2 不足之处
- 平台限制:仅支持 Linux/POSIX
- 格式化 :自实现,不如
{fmt}库强大 - 内存池:使用 mutex,非完全无锁
12.3 改进方向
- 跨平台:支持 Windows
- 集成 fmt :使用
{fmt}库替代自实现 - 无锁内存池:使用 lock-free 栈
- 结构化日志:支持 OpenTelemetry
- 远程日志:支持 Kafka、Elasticsearch 等
12.4 学习价值
通过这个项目,你可以学到:
- 并发编程:无锁队列、原子操作、内存序
- 内存管理:内存池、对象池、栈上分配
- 设计模式:单例、工厂、策略、建造者
- 现代 C++:C++17 特性、RAII、智能指针
- 软件工程:模块化设计、测试、文档