本文由启程者团队(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) - 探索技术,创造价值