【C++日志库】启程者团队开源:轻量级高性能VoyLog日志库完全指南
本文由启程者团队(Voyages)发布,介绍我们自研的C++日志库VoyLog
为什么需要日志库?
在软件开发中,日志就像程序的"黑匣子" - 它记录着程序的运行状态、错误信息和调试数据。没有良好的日志系统,排查问题就像在黑暗中摸索。今天,我们启程者团队开源了自研的轻量级C++日志库:VoyLog。
VoyLog是什么?
VoyLog(名称来源于Voyager的前三个字母+Log)是我们团队在多年项目开发中沉淀出来的高性能C++日志库。它具有以下核心特性:
🚀 轻量高效:单例模式设计,资源占用极小
🎨 彩色输出:控制台支持彩色日志,一目了然
📁 双输出:同时支持控制台和文件输出
🔒 线程安全:多线程环境下稳定运行
🎯 多级别:DEBUG/INFO/WARN/ERROR四级日志
📝 格式丰富:支持类似printf的格式化输出
快速上手
基础使用(最简单的方式)
#include "VoyLog.h"int main() {// 直接使用,无需初始化!VOY_LOG_INFO("程序启动成功!");VOY_LOG_DEBUG("用户 %s 登录", "张三");VOY_LOG_WARN("内存使用率: %d%", 85);VOY_LOG_ERROR("连接服务器失败!");return 0;
}输出效果:
[2024-01-15 10:30:25] [INFO ] [main.cpp:5:main] 程序启动成功!
[2024-01-15 10:30:25] [DEBUG] [main.cpp:6:main] 用户 张三 登录
[2024-01-15 10:30:25] [WARN ] [main.cpp:7:main] 内存使用率: 85%
[2024-01-15 10:30:25] [ERROR] [main.cpp:8:main] 连接服务器失败!进阶配置
#include "VoyLog.h"void initLogSystem() {// 初始化日志系统VoyLog::initialize(LogLevel::DEBUG, true);  // DEBUG级别 + 控制台输出// 设置日志文件if (!VoyLog::getInstance()->setLogFile("myapp.log")) {VOY_LOG_ERROR("创建日志文件失败!");}VOY_LOG_INFO("日志系统初始化完成");
}核心功能详解
1. 四种日志级别
// 调试信息 - 开发阶段使用
VOY_LOG_DEBUG("变量值: x=%d, y=%f", x, y);// 普通信息 - 正常运行日志
VOY_LOG_INFO("用户 %s 执行了 %s 操作", username, action);// 警告信息 - 需要注意但不影响运行
VOY_LOG_WARN("数据库连接缓慢,耗时 %dms", elapsed);// 错误信息 - 需要立即处理的问题
VOY_LOG_ERROR("文件 %s 不存在,使用默认配置", configFile);2. 灵活的配置选项
auto logger = VoyLog::getInstance();// 动态调整日志级别
logger->setLogLevel(LogLevel::WARN);  // 只记录WARN和ERROR// 开关控制台输出
logger->setConsoleOutput(false);  // 关闭控制台,只输出到文件// 切换日志文件
logger->setLogFile("new_log.log");3. 彩色控制台输出
VoyLog在支持ANSI颜色的终端中会自动显示彩色:
🔴 红色:ERROR级别 - 立即关注
🟡 黄色:WARN级别 - 需要注意
🟢 绿色:INFO级别 - 正常运行
🔵 青色:DEBUG级别 - 调试信息
实际应用场景
网络编程中的使用
class NetworkManager {
public:bool connect(const std::string& host, int port) {VOY_LOG_DEBUG("尝试连接 %s:%d", host.c_str(), port);if (!validateAddress(host)) {VOY_LOG_ERROR("无效的主机地址: %s", host.c_str());return false;}// 连接逻辑...if (connectToServer(host, port)) {VOY_LOG_INFO("成功连接到服务器 %s:%d", host.c_str(), port);return true;} else {VOY_LOG_WARN("连接服务器 %s:%d 失败,准备重试", host.c_str(), port);return false;}}
};多线程环境
void workerThread(int id) {VOY_LOG_INFO("工作线程 %d 启动", id);for (int i = 0; i < 10; i++) {VOY_LOG_DEBUG("线程 %d 处理第 %d 个任务", id, i);// 处理任务...std::this_thread::sleep_for(std::chrono::milliseconds(100));}VOY_LOG_INFO("工作线程 %d 结束", id);
}// 启动多个线程
std::vector<std::thread> threads;
for (int i = 0; i < 5; i++) {threads.emplace_back(workerThread, i);
}最佳实践建议
1. 初始化时机
// 在main函数开始处初始化
int main(int argc, char* argv[]) {// 推荐:程序启动立即初始化日志VoyLog::initialize(LogLevel::INFO, true);VoyLog::getInstance()->setLogFile("application.log");VOY_LOG_INFO("=== 应用程序启动 ===");VOY_LOG_INFO("命令行参数: %d 个", argc);// 程序主要逻辑...return 0;
}2. 环境区分配置
void setupLogging() {
#ifdef DEBUG// 开发环境:详细日志VoyLog::initialize(LogLevel::DEBUG, true);
#else// 生产环境:重要日志 + 文件输出VoyLog::initialize(LogLevel::INFO, false);VoyLog::getInstance()->setLogFile("/var/log/myapp.log");
#endif
}3. 性能敏感场景
void processHighFrequencyData() {// 高频数据处理时,避免过多的DEBUG日志// VOY_LOG_DEBUG("处理数据..."); // 注释掉以减少开销// 只在必要时记录if (errorOccurred) {VOY_LOG_ERROR("数据处理错误: %s", errorMsg);}
}为什么选择VoyLog?
🆚 与其他日志库对比
| 特性 | VoyLog | spdlog | glog | 
|---|---|---|---|
| 头文件大小 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | 
| 学习成本 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | 
| 性能 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 
| 功能完备性 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 
✨ VoyLog的优势
即插即用:包含两个文件,直接加入项目即可使用
零依赖:只使用C++标准库,无需额外依赖
易于理解:代码简洁,接口直观,新手也能快速掌握
团队验证:已在启程者团队多个项目中稳定运行
类代码
VoyLog.h
#ifndef VOYLOG_H
#define VOYLOG_H#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <memory>
#include <mutex>
#include <cstdarg>
#include <ctime>
#include <iomanip>
#include <thread>
#include <vector>// 日志级别枚举
enum class LogLevel {VoyDEBUG,VoyINFO,VoyWARN,VoyERROR
};class VoyLog {
private:std::ofstream log_file_;LogLevel current_level_;std::mutex mutex_;bool console_output_;// 单例实例static std::shared_ptr<VoyLog> instance_;static std::mutex instance_mutex_;// 私有构造函数VoyLog(LogLevel level = LogLevel::VoyINFO, bool console = true);// 将日志级别转换为字符串const char* levelToString(LogLevel level);// 获取当前时间字符串std::string getCurrentTime();// 格式化可变参数std::string formatString(const char* format, va_list args);// 核心日志记录实现void logImpl(LogLevel level, const char* file, int line, const char* function, const char* format, va_list args);// 提取短文件名(不含路径)std::string getShortFileName(const char* file);public:// 禁止拷贝和赋值VoyLog(const VoyLog&) = delete;VoyLog& operator=(const VoyLog&) = delete;// 析构函数~VoyLog();// 获取单例实例static std::shared_ptr<VoyLog> getInstance();// 初始化单例(可选配置)static void initialize(LogLevel level = LogLevel::VoyINFO, bool console = true);// 设置日志文件bool setLogFile(const std::string& filename);// 设置日志级别void setLogLevel(LogLevel level);// 设置控制台输出void setConsoleOutput(bool enable);// 公有静态日志接口 - 支持类似printf的可变参数static void Log_Debug(const char* file, int line, const char* function, const char* format, ...);static void Log_Info(const char* file, int line, const char* function, const char* format, ...);static void Log_Warn(const char* file, int line, const char* function, const char* format, ...);static void Log_Error(const char* file, int line, const char* function, const char* format, ...);
};// 便捷宏定义 - 使用静态接口
#define VOY_LOG_DEBUG(format, ...) VoyLog::Log_Debug(__FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)
#define VOY_LOG_INFO(format, ...)  VoyLog::Log_Info(__FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)
#define VOY_LOG_WARN(format, ...)  VoyLog::Log_Warn(__FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)
#define VOY_LOG_ERROR(format, ...) VoyLog::Log_Error(__FILE__, __LINE__, __FUNCTION__, format, ##__VA_ARGS__)#endif // VOYLOG_HVoyLog.cpp
#include "VoyLog.h"// 静态成员初始化
std::shared_ptr<VoyLog> VoyLog::instance_ = nullptr;
std::mutex VoyLog::instance_mutex_;// 私有构造函数
VoyLog::VoyLog(LogLevel level, bool console): current_level_(level), console_output_(console) {
}// 析构函数
VoyLog::~VoyLog() {if (log_file_.is_open()) {log_file_.close();}
}// 获取单例实例
std::shared_ptr<VoyLog> VoyLog::getInstance() {std::lock_guard<std::mutex> lock(instance_mutex_);if (!instance_) {instance_ = std::shared_ptr<VoyLog>(new VoyLog());}return instance_;
}// 初始化单例
void VoyLog::initialize(LogLevel level, bool console) {std::lock_guard<std::mutex> lock(instance_mutex_);if (!instance_) {instance_ = std::shared_ptr<VoyLog>(new VoyLog(level, console));}else {instance_->setLogLevel(level);instance_->setConsoleOutput(console);}
}// 将日志级别转换为字符串
const char* VoyLog::levelToString(LogLevel level) {switch (level) {case LogLevel::VoyDEBUG: return "DEBUG";case LogLevel::VoyINFO:  return "INFO ";case LogLevel::VoyWARN:  return "WARN ";case LogLevel::VoyERROR: return "ERROR";default: return "UNKNOWN";}
}// 获取当前时间字符串
std::string VoyLog::getCurrentTime() {std::time_t now = std::time(nullptr);char time_str[64];std::strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", std::localtime(&now));return std::string(time_str);
}// 格式化可变参数
std::string VoyLog::formatString(const char* format, va_list args) {char buffer[2048];  // 增加缓冲区大小vsnprintf(buffer, sizeof(buffer), format, args);return std::string(buffer);
}// 提取短文件名(不含路径)
std::string VoyLog::getShortFileName(const char* file) {std::string filename = file;size_t last_slash = filename.find_last_of("/\\");if (last_slash != std::string::npos) {filename = filename.substr(last_slash + 1);}return filename;
}// 核心日志记录实现
void VoyLog::logImpl(LogLevel level, const char* file, int line, const char* function, const char* format, va_list args) {// 检查日志级别if (level < current_level_) {return;}std::lock_guard<std::mutex> lock(mutex_);// 获取格式化后的消息std::string message = formatString(format, args);// 提取短文件名std::string filename = getShortFileName(file);// 构建完整的日志条目 - 使用固定宽度和对齐std::ostringstream log_entry;log_entry << "[" << getCurrentTime() << "] "                    // 时间戳固定19字符<< "[" << levelToString(level) << "] "               // 级别固定6字符<< "[" << std::setw(20) << std::left << filename     // 文件名固定20字符左对齐<< ":" << std::setw(4) << std::right << line         // 行号固定4字符右对齐<< ":" << std::setw(25) << std::left << function     // 函数名固定25字符左对齐<< "] " << message;                                  // 消息std::string final_log = log_entry.str();// 输出到控制台if (console_output_) {// 为不同级别添加颜色if (level == LogLevel::VoyERROR) {std::cout << "\033[1;31m" << final_log << "\033[0m" << std::endl; // 红色}else if (level == LogLevel::VoyWARN) {std::cout << "\033[1;33m" << final_log << "\033[0m" << std::endl; // 黄色}else if (level == LogLevel::VoyINFO) {std::cout << "\033[1;32m" << final_log << "\033[0m" << std::endl; // 绿色}else if (level == LogLevel::VoyDEBUG) {std::cout << "\033[1;36m" << final_log << "\033[0m" << std::endl; // 青色}else {std::cout << final_log << std::endl;}}// 输出到文件(无颜色)if (log_file_.is_open()) {log_file_ << final_log << std::endl;log_file_.flush(); // 确保立即写入}
}// 设置日志文件
bool VoyLog::setLogFile(const std::string& filename) {std::lock_guard<std::mutex> lock(mutex_);if (log_file_.is_open()) {log_file_.close();}log_file_.open(filename, std::ios::out | std::ios::app);return log_file_.is_open();
}// 设置日志级别
void VoyLog::setLogLevel(LogLevel level) {std::lock_guard<std::mutex> lock(mutex_);current_level_ = level;
}// 设置控制台输出
void VoyLog::setConsoleOutput(bool enable) {std::lock_guard<std::mutex> lock(mutex_);console_output_ = enable;
}// 静态日志接口实现
void VoyLog::Log_Debug(const char* file, int line, const char* function, const char* format, ...) {va_list args;va_start(args, format);getInstance()->logImpl(LogLevel::VoyDEBUG, file, line, function, format, args);va_end(args);
}void VoyLog::Log_Info(const char* file, int line, const char* function, const char* format, ...) {va_list args;va_start(args, format);getInstance()->logImpl(LogLevel::VoyINFO, file, line, function, format, args);va_end(args);
}void VoyLog::Log_Warn(const char* file, int line, const char* function, const char* format, ...) {va_list args;va_start(args, format);getInstance()->logImpl(LogLevel::VoyWARN, file, line, function, format, args);va_end(args);
}void VoyLog::Log_Error(const char* file, int line, const char* function, const char* format, ...) {va_list args;va_start(args, format);getInstance()->logImpl(LogLevel::VoyERROR, file, line, function, format, args);va_end(args);
}完整示例
#include "VoyLog.h"// 模拟一些业务函数来测试静态日志接口
void initializeSystem() {VOY_LOG_INFO("系统初始化开始");// 模拟一些初始化步骤VOY_LOG_DEBUG("加载配置文件 config.json...");VOY_LOG_DEBUG("初始化数据库连接池,大小: %d", 10);VOY_LOG_DEBUG("启动网络监听器,端口: %d", 8080);VOY_LOG_INFO("系统初始化完成,所有组件就绪");
}void processUserRequest(int user_id, const std::string& action, double processing_time) {VOY_LOG_INFO("处理用户请求 - 用户ID: %d, 动作: %s, 处理时间: %.3f秒",user_id, action.c_str(), processing_time);// 模拟业务逻辑if (user_id <= 0) {VOY_LOG_WARN("检测到无效的用户ID: %d,使用默认用户", user_id);}if (action == "delete") {VOY_LOG_WARN("用户 %d 执行了敏感删除操作", user_id);}if (processing_time > 1.0) {VOY_LOG_WARN("请求处理时间过长: %.3f秒,用户ID: %d", processing_time, user_id);}VOY_LOG_DEBUG("用户请求处理完成,结果状态: %s", "SUCCESS");
}void handleErrorScenario() {VOY_LOG_ERROR("发生了一个模拟错误场景");VOY_LOG_ERROR("文件操作失败,路径: %s, 错误: %s", "/data/config.json", "permission denied");VOY_LOG_ERROR("网络连接超时,地址: %s, 端口: %d, 重试次数: %d", "192.168.1.100", 3306, 3);
}void performDatabaseOperation(const std::string& table, int record_count) {VOY_LOG_DEBUG("开始数据库操作 - 表名: %s, 记录数: %d", table.c_str(), record_count);// 模拟数据库操作if (record_count > 10000) {VOY_LOG_WARN("大表操作警告 - 表: %s, 记录数: %d", table.c_str(), record_count);}VOY_LOG_INFO("数据库操作完成 - 表: %s, 影响行数: %d", table.c_str(), record_count);
}// 多线程测试函数
void threadFunction(int thread_id) {for (int i = 0; i < 5; ++i) {VOY_LOG_INFO("线程执行进度 - 线程ID: %d, 进度: %d/%d", thread_id, i + 1, 5);std::this_thread::sleep_for(std::chrono::milliseconds(50));// 模拟一些工作if (i == 2 && thread_id == 3) {VOY_LOG_WARN("线程 %d 遇到警告情况,迭代: %d", thread_id, i);}}
}// 测试直接使用静态接口(不通过宏)
void testDirectStaticInterface() {VoyLog::Log_Info(__FILE__, __LINE__, __FUNCTION__,"直接调用静态接口测试 - 数值: %d, 字符串: %s, 浮点数: %.2f",42, "test_string", 3.14159);
}// 模拟一个较长的函数名来测试对齐
void thisIsAVeryLongFunctionNameForTestingAlignment() {VOY_LOG_DEBUG("这是一个测试函数,用于验证长函数名的对齐效果");VOY_LOG_INFO("当前状态: %s, 计数器: %d", "运行中", 123);
}int main() {std::cout << "=== VoyLog 优化格式测试程序开始 ===" << std::endl;// 1. 初始化日志系统VoyLog::initialize(LogLevel::DEBUG, true);// 2. 设置日志文件auto logger = VoyLog::getInstance();logger->setLogFile("voylog_aligned_test.log");VOY_LOG_INFO("=== VoyLog 优化格式测试开始 ===");// 3. 测试不同日志级别initializeSystem();// 4. 测试带参数的日志和对齐processUserRequest(12345, "login", 0.125);processUserRequest(0, "delete", 2.345); // 触发警告processUserRequest(67890, "update", 0.056);// 5. 测试错误场景handleErrorScenario();// 6. 测试数据库操作performDatabaseOperation("users", 1500);performDatabaseOperation("logs", 25000); // 大表警告// 7. 测试变量和复杂格式double memory_usage = 75.5;int active_connections = 42;std::string server_status = "running";long long total_requests = 123456789;VOY_LOG_INFO("系统状态报告 - 内存使用: %6.2f%%, 活跃连接: %4d, 状态: %-10s, 总请求: %12lld",memory_usage, active_connections, server_status.c_str(), total_requests);// 8. 多线程测试VOY_LOG_INFO("开始多线程并发测试");std::vector<std::thread> threads;for (int i = 0; i < 4; ++i) {threads.emplace_back(threadFunction, i + 1);}for (auto& t : threads) {t.join();}VOY_LOG_INFO("多线程并发测试完成");// 9. 测试直接静态接口调用testDirectStaticInterface();// 10. 测试长函数名对齐thisIsAVeryLongFunctionNameForTestingAlignment();// 11. 测试日志级别过滤VOY_LOG_INFO("开始日志级别过滤测试");logger->setLogLevel(LogLevel::WARN); // 只显示WARN和ERRORVOY_LOG_DEBUG("这条DEBUG日志不应该显示在控制台");VOY_LOG_INFO("这条INFO日志不应该显示在控制台");VOY_LOG_WARN("这条WARN日志应该显示在控制台 - 过滤测试");VOY_LOG_ERROR("这条ERROR日志应该显示在控制台 - 过滤测试");// 恢复日志级别logger->setLogLevel(LogLevel::INFO);VOY_LOG_INFO("日志级别已恢复为INFO,所有INFO及以上级别日志将正常显示");// 12. 测试控制台输出开关VOY_LOG_INFO("开始控制台输出开关测试");logger->setConsoleOutput(false);VOY_LOG_INFO("这条日志只写入文件,不在控制台显示");logger->setConsoleOutput(true);VOY_LOG_INFO("控制台输出已恢复,后续日志将同时在控制台和文件中显示");VOY_LOG_INFO("=== VoyLog 优化格式测试结束 ===");std::cout << "=== VoyLog 测试程序结束 ===" << std::endl;std::cout << "请查看 voylog_aligned_test.log 文件查看完整对齐的日志输出" << std::endl;return 0;
}
结语
VoyLog作为启程者团队的开源贡献,旨在为C++开发者提供一个简单、高效、可靠的日志解决方案。无论你是初学者还是经验丰富的开发者,VoyLog都能为你的项目提供坚实的日志支持。
项目特点总结:
📦 两个文件即可集成
🎯 接口简单易用
🚀 运行高效稳定
🎨 输出美观清晰
欢迎在项目中尝试使用VoyLog!如果你有任何问题或建议,欢迎向我们反馈。让我们在软件开发的道路上,一起扬帆启程!
启程者团队(Voyages) - 探索技术,创造价值
