本文由启程者团队(Voyages)发布,介绍我们自研的C++日志库VoyLog
为什么需要日志库?
在软件开发中,日志就像程序的"黑匣子" - 它记录着程序的运行状态、错误信息和调试数据。没有良好的日志系统,排查问题就像在黑暗中摸索。今天,我们启程者团队开源了自研的轻量级C++日志库:VoyLog。
VoyLog是什么?
VoyLog(名称来源于Voyager的前三个字母+Log)是我们团队在多年项目开发中沉淀出来的高性能C++日志库。它具有以下核心特性:
-
🚀 轻量高效:单例模式设计,资源占用极小
-
🎨 彩色输出:控制台支持彩色日志,一目了然
-
📁 双输出:同时支持控制台和文件输出
-
🔒 线程安全:多线程环境下稳定运行
-
🎯 多级别:DEBUG/INFO/WARN/ERROR四级日志
-
📝 格式丰富:支持类似printf的格式化输出
快速上手
基础使用(最简单的方式)
cpp
#include "VoyLog.h"
int main() {
// 直接使用,无需初始化!
VOY_LOG_INFO("程序启动成功!");
VOY_LOG_DEBUG("用户 %s 登录", "张三");
VOY_LOG_WARN("内存使用率: %d%", 85);
VOY_LOG_ERROR("连接服务器失败!");
return 0;
}
输出效果:
bash
[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] 连接服务器失败!
进阶配置
cpp
#include "VoyLog.h"
void initLogSystem() {
// 初始化日志系统
VoyLog::initialize(LogLevel::DEBUG, true); // DEBUG级别 + 控制台输出
// 设置日志文件
if (!VoyLog::getInstance()->setLogFile("myapp.log")) {
VOY_LOG_ERROR("创建日志文件失败!");
}
VOY_LOG_INFO("日志系统初始化完成");
}
核心功能详解
1. 四种日志级别
cpp
// 调试信息 - 开发阶段使用
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. 灵活的配置选项
cpp
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级别 - 调试信息
实际应用场景
网络编程中的使用
cpp
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;
}
}
};
多线程环境
cpp
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. 初始化时机
cpp
// 在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. 环境区分配置
cpp
void setupLogging() {
#ifdef DEBUG
// 开发环境:详细日志
VoyLog::initialize(LogLevel::DEBUG, true);
#else
// 生产环境:重要日志 + 文件输出
VoyLog::initialize(LogLevel::INFO, false);
VoyLog::getInstance()->setLogFile("/var/log/myapp.log");
#endif
}
3. 性能敏感场景
cpp
void processHighFrequencyData() {
// 高频数据处理时,避免过多的DEBUG日志
// VOY_LOG_DEBUG("处理数据..."); // 注释掉以减少开销
// 只在必要时记录
if (errorOccurred) {
VOY_LOG_ERROR("数据处理错误: %s", errorMsg);
}
}
为什么选择VoyLog?
🆚 与其他日志库对比
| 特性 | VoyLog | spdlog | glog |
|---|---|---|---|
| 头文件大小 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| 学习成本 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| 性能 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 功能完备性 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
✨ VoyLog的优势
-
即插即用:包含两个文件,直接加入项目即可使用
-
零依赖:只使用C++标准库,无需额外依赖
-
易于理解:代码简洁,接口直观,新手也能快速掌握
-
团队验证:已在启程者团队多个项目中稳定运行
类代码
VoyLog.h
cpp
#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_H
VoyLog.cpp
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);
}
完整示例
cpp
#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和ERROR
VOY_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) - 探索技术,创造价值