跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

手写一个线程安全的 C++ 日志库

手写一个线程安全的 C++ 日志库,采用策略模式实现屏幕与文件双输出,通过互斥锁保障并发安全,并支持日志等级分级管理。代码涵盖日志等级转换、时间戳生成及文件路径处理,利用重载流操作符实现便捷调用,适合用于生产环境的调试与监控。

魔尊发布于 2026/3/27更新于 2026/10/2284 浏览
手写一个线程安全的 C++ 日志库

手写一个线程安全的 C++ 日志库

在开发过程中,printf 或 std::cout 往往显得力不从心。它们缺乏结构化信息,难以区分日志级别,且在多线程环境下容易混行。为了解决这些问题,我们不妨自己造个轮子,实现一个支持多输出模式、线程安全且易于扩展的 C++ 日志库。

核心设计思路

一个完善的日志系统通常包含以下要素:

  • 日志等级:DEBUG、INFO、WARNING、ERROR、FATAL,用于控制输出粒度。
  • 输出目标:既能打印到屏幕方便调试,也能写入文件便于归档。
  • 线程安全:多线程并发写入时不能出现数据竞争。
  • 便捷调用:类似流式操作符,减少样板代码。

为了实现灵活的输出方式,我们采用策略模式。定义一个基类抽象出日志刷新接口,然后分别实现'屏幕输出'和'文件输出'的具体策略。这样后续如果需要增加数据库输出,只需新增一个策略类即可,无需修改主逻辑。

策略模式实现输出端

首先定义策略基类,声明纯虚函数用于刷新日志内容。

namespace log_lib {

class Strategy_Pattern {
public:
    virtual ~Strategy_Pattern() = default;
    // 纯虚函数,子类需实现具体刷新逻辑
    virtual void Log_refresh_mode(const std::string &message) = 0;
};
}

屏幕输出策略

屏幕是公共资源,直接写入可能导致输出混乱,因此必须加锁保护。这里使用 pthread_mutex_t 来保证互斥。

#include <iostream>
#include <pthread.h>

namespace log_lib {

class Refresh_to_screen : public Strategy_Pattern {
public:
    Refresh_to_screen() {
        pthread_mutex_init(&_mutex, nullptr);
    }
    
    ~Refresh_to_screen() {
        (&_mutex);
    }

    {
        (&_mutex);
        std::cout << message << std::endl;
        (&_mutex);
    }

:
     _mutex;
};
}
pthread_mutex_destroy
void Log_refresh_mode(const std::string &message) override
pthread_mutex_lock
pthread_mutex_unlock
private
pthread_mutex_t

文件输出策略

文件写入同样需要互斥,同时要注意目录创建和文件追加模式。这里用到了 C++17 的 <filesystem> 特性来处理路径。

#include <fstream>
#include <filesystem>

namespace log_lib {

const std::string Default_Path = "./log/";
const std::string Default_Name = "Log.txt";

class Refresh_to_file : public Strategy_Pattern {
public:
    Refresh_to_file(const std::string &Path = Default_Path,
                    const std::string &Name = Default_Name)
        : _Default_Path(Path), _Default_Name(Name) {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_mutex_lock(&_mutex);
        if (std::filesystem::exists(_Default_Path)) {
            pthread_mutex_unlock(&_mutex);
            return;
        }
        try {
            std::filesystem::create_directories(_Default_Path);
        } catch (std::filesystem::filesystem_error &e) {
            std::cerr << e.what() << "\n";
        }
        pthread_mutex_unlock(&_mutex);
    }

    void Log_refresh_mode(const std::string &message) override {
        pthread_mutex_lock(&_mutex);
        std::string log = _Default_Path + _Default_Name;
        std::ofstream out(log, std::ios::app);
        if (!out.is_open()) {
            pthread_mutex_unlock(&_mutex);
            return;
        }
        out << message << std::endl;
        out.close();
        pthread_mutex_unlock(&_mutex);
    }

    ~Refresh_to_file() {
        pthread_mutex_destroy(&_mutex);
    }

private:
    pthread_mutex_t _mutex;
    std::string _Default_Path;
    std::string _Default_Name;
};
}

日志类封装与宏定义

有了具体的输出策略,接下来就是核心的 Log 类。它负责管理当前使用的策略指针,并提供便捷的接口。

#include <memory>
#include <sstream>
#include <chrono>
#include <unistd.h>

namespace log_lib {

enum class LogLevel { DEBUG, INFO, WARNING, ERROR, FATAL };

// 辅助函数:将等级转为字符串(实际项目中可完善)
inline const char* LogLevelToStr(LogLevel level) {
    switch(level) {
        case LogLevel::DEBUG: return "DEBUG";
        case LogLevel::INFO: return "INFO";
        case LogLevel::WARNING: return "WARNING";
        case LogLevel::ERROR: return "ERROR";
        case LogLevel::FATAL: return "FATAL";
        default: return "UNKNOWN";
    }
}

// 获取当前时间字符串
inline std::string GetCurrtime() {
    auto now = std::chrono::system_clock::now();
    auto time = std::chrono::system_clock::to_time_t(now);
    char buf[32];
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&time));
    return std::string(buf);
}

class Log {
public:
    class LogMessage {
    public:
        LogMessage(std::string filename, LogLevel level, int line, Log &logger)
            : _filename(filename), _line(line), _loglevel(level),
              _currtime(GetCurrtime()), _pid(getpid()), _logger(logger) {
            std::stringstream ssbuffer;
            ssbuffer << "[" << _currtime << "] [" << LogLevelToStr(_loglevel) << "]"
                     << "[" << _pid << "][" << _filename << "][" << _line << "] - ";
            _loginfo = ssbuffer.str();
        }

        template<class T>
        LogMessage& operator<<(const T& data) {
            std::stringstream ss;
            ss << data;
            _loginfo += ss.str();
            return *this;
        }

        ~LogMessage() {
            if (_logger._Strategy) {
                _logger._Strategy->Log_refresh_mode(_loginfo);
            }
        }

    private:
        std::string _filename;
        LogLevel _loglevel;
        std::string _currtime;
        pid_t _pid;
        int _line;
        std::string _loginfo;
        Log &_logger;
    };

    // 默认构造函数,初始化屏幕输出策略
    Log() {
        _Strategy = std::make_shared<Refresh_to_screen>();
    }

    // 重载 () 运算符,构建临时 LogMessage 对象
    LogMessage operator()(LogLevel level, const std::string filename, int line) {
        return LogMessage(filename, level, line, *this);
    }

    // 切换输出到文件
    void RefreshToFile() {
        _Strategy = std::make_shared<Refresh_to_file>();
    }

    // 切换输出到屏幕
    void RefreshToScreen() {
        _Strategy = std::make_shared<Refresh_to_screen>();
    }

private:
    std::shared_ptr<Strategy_Pattern> _Strategy;
};

} // namespace log_lib

// 全局日志实例
log_lib::Log logger;

// 定义宏,简化调用
#define LOG(Level) logger(Level, __FILE__, __LINE__)

使用示例

通过宏定义,我们可以像写普通语句一样记录日志,既保留了流式操作的灵活性,又自动注入了时间、文件、行号等关键信息。

int main() {
    // 默认输出到屏幕
    LOG(log_lib::LogLevel::INFO) << "程序启动成功";
    LOG(log_lib::LogLevel::DEBUG) << "变量值:" << 42;

    // 切换到文件输出
    logger.RefreshToFile();
    LOG(log_lib::LogLevel::ERROR) << "发生严重错误,请检查配置";

    return 0;
}

运行后,你会看到类似这样的输出格式:

[2024-08-04 12:27:03][DEBUG][202938][main.cc][16]- hello world

这种结构化的日志不仅方便人类阅读,也更容易被自动化监控工具解析。通过这种方式,我们不仅摆脱了 printf 的局限,还获得了一个可扩展、线程安全的基础日志框架。

目录

  1. 手写一个线程安全的 C++ 日志库
  2. 核心设计思路
  3. 策略模式实现输出端
  4. 屏幕输出策略
  5. 文件输出策略
  6. 日志类封装与宏定义
  7. 使用示例

更多推荐文章

查看全部
  • C++ 基于正倒排索引的 Boost 搜索引擎实现
  • Python 异步编程与协程实战指南
  • 大模型技术基础学习路线与核心能力要求
  • Spring Web MVC 入门:从概念到实战(上)
  • Python 网络编程实战:基于 Socket 实现简易聊天室
  • 大语言模型智能体 (LLM Agents) 入门指南
  • JavaScript 事件循环进阶:rAF、Web Workers 与任务调度
  • 2019 年信奥赛 C++ 提高组 CSP-S 初赛真题解析(选择题 11-15)
  • 向大模型学习:优化人类思维模式与认知结构
  • Python + Linux 构建高可用数据采集服务
  • C++ 继承进阶:友元、静态成员与菱形继承解析
  • Spring AI Alibaba 快速搭建带对话记忆的智能客服聊天机器人
  • OpenClaw 本地 AI 代理技术架构与实战部署
  • C/C++ 算法入门:一维动态规划实战解析
  • LLM 本地知识库构建:文档分割与向量化方案探讨
  • 高企年报自动化系统实战:用低代码替代 Excel 手工填报
  • Python 爬虫入门指南:从基础到 Scrapy 框架
  • C++ 多态底层实现原理与内存布局解析
  • Node.js+Vue 在线票务系统设计与功能实现
  • 基于 Docker 部署 AstrBot+NapCat 打造 QQ 智能客服

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online