【C++日志库】启程者团队开源:轻量级高性能VoyLog日志库完全指南

本文由启程者团队(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的优势

  1. 即插即用:包含两个文件,直接加入项目即可使用

  2. 零依赖:只使用C++标准库,无需额外依赖

  3. 易于理解:代码简洁,接口直观,新手也能快速掌握

  4. 团队验证:已在启程者团队多个项目中稳定运行

类代码

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) - 探索技术,创造价值

相关推荐
聪明努力的积极向上6 小时前
【C#】HTTP中URL编码方式解析
开发语言·http·c#
嵌入式-老费6 小时前
自己动手写深度学习框架(快速学习python和关联库)
开发语言·python·学习
ctgu906 小时前
PyQt5(八):ui设置为可以手动随意拉伸功能
开发语言·qt·ui
许长安6 小时前
C++中指针和引用的区别
c++·经验分享·笔记
CVer儿6 小时前
libtorch ITK 部署 nnUNetV2 模型
开发语言
asyxchenchong8886 小时前
OpenLCA、GREET、R语言的生命周期评价方法、模型构建
开发语言·r语言
没有梦想的咸鱼185-1037-16637 小时前
【生命周期评价(LCA)】基于OpenLCA、GREET、R语言的生命周期评价方法、模型构建
开发语言·数据分析·r语言
hetao17338377 小时前
2025-10-30 ZYZOJ Star(斯达)模拟赛 hetao1733837的record
c++·算法
程序猿20237 小时前
Python每日一练---第三天:删除有序数组中的重复项
开发语言·python