C++ 日志类设计

目录

一、设计思路

二、完整代码实现

三、核心知识点讲解

[1. 单例模式](#1. 单例模式)

[2. operator () 重载](#2. operator () 重载)

[3. 可变参数与格式化](#3. 可变参数与格式化)

[4. 时间戳](#4. 时间戳)

[5. 文件输出](#5. 文件输出)

四、使用示例

五、总结


在日常写项目、做实验、写服务端程序时,我们总需要一个轻量好用的日志工具。既能打印控制台,又能输出到文件,自带时间戳和级别,调用方式还要干净优雅。

今天就基于重载 operator () 的思路,实现一个极简、可直接嵌入项目的 C++ 日志类,代码不多但功能完整,非常适合学习和工程使用。


设计思路

  1. 调用方式优雅 :使用 () 运算符重载,像函数一样直接调用 log(INFO, "hello %d", 123);
  2. 功能完整:自动时间戳、日志级别、控制台输出、文件输出
  3. 全局单例:一处定义,随处使用,不用反复创建对象
  4. 轻量无依赖:仅用标准库,不引入第三方组件
  5. 格式统一[时间] [级别] 信息,美观易读

完整代码实现

cpp 复制代码
#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <cstdarg>
#include <ctime>

// 日志级别
enum LogLevel {
    INFO, DEBUG, WARNING, ERROR, FATAL
};

class Logger {
public:
    // 单例模式,全局唯一实例
    static Logger& instance() {
        static Logger log;
        return log;
    }

    // 核心:重载 operator()
    void operator()(LogLevel level, const char* fmt, ...) {
        // 格式化时间
        char time_buf[32];
        std::time_t now = std::time(nullptr);
        std::strftime(time_buf, sizeof(time_buf), "%F %T", std::localtime(&now));

        // 获取级别字符串
        const char* level_str = get_level_str(level);

        // 格式化用户输入日志内容
        char content[1024];
        va_list args;
        va_start(args, fmt);
        vsnprintf(content, sizeof(content), fmt, args);
        va_end(args);

        // 拼接并输出到控制台
        std::cout << "[" << time_buf << "] [" << level_str << "] " << content << "\n";

        // 如果设置了日志文件,则同时写入文件
        if (!log_file.empty()) {
            std::ofstream ofs(log_file, std::ios::app);
            if (ofs) {
                ofs << "[" << time_buf << "] [" << level_str << "] " << content << "\n";
            }
        }
    }

    // 设置日志输出文件
    void set_file(const std::string& path) {
        log_file = path;
    }

private:
    Logger() = default;  // 私有构造,禁止外部创建
    std::string log_file;

    // 日志级别转字符串
    const char* get_level_str(LogLevel level) const {
        switch (level) {
            case INFO:    return "INFO";
            case DEBUG:   return "DEBUG";
            case WARNING: return "WARNING";
            case ERROR:   return "ERROR";
            case FATAL:   return "FATAL";
            default:      return "UNKNOWN";
        }
    }
};

// 全局日志对象,直接使用
static Logger& log = Logger::instance();

核心知识点讲解

1. 单例模式

cpp 复制代码
static Logger& instance() {
    static Logger log;
    return log;
}
  • 保证全局只有一个日志实例
  • 避免多次打开文件、重复输出
  • 懒加载,用到时才初始化

2. operator () 重载

这是整个设计最优雅的地方:

cpp 复制代码
void operator()(LogLevel level, const char* fmt, ...)

通过重载括号运算符,我们可以直接像调用函数一样使用:

cpp 复制代码
log(INFO, "server started, pid = %d", getpid());

3. 可变参数与格式化

使用 C 标准库的可变参数机制:

  • va_list / va_start / va_end 处理参数列表
  • vsnprintf 格式化字符串
  • 支持 %d %s %f 等常用占位符,和 printf 用法一致

4. 时间戳

cpp 复制代码
std::strftime(time_buf, sizeof(time_buf), "%F %T", std::localtime(&now));
  • %F → 年 - 月 - 日
  • %T → 时:分: 秒
  • 日志格式整齐,便于查看和排查问题

5. 文件输出

cpp 复制代码
std::ofstream ofs(log_file, std::ios::app);
  • std::ios::app 表示追加模式,不会覆盖原有内容
  • 不设置文件时只打印控制台,设置后同时落盘

使用示例

cpp 复制代码
#include <unistd.h>
#include "Logger.h"

int main() {
    // 可选:设置日志文件
    log.set_file("service.log");

    // 直接使用
    log(INFO,  "service start successfully, pid = %d", getpid());
    log(DEBUG, "connect to client success, fd = %d", 5);
    log(WARNING, "low memory warning");
    log(ERROR, "open fifo failed");
    log(FATAL, "system error, process exit");

    return 0;
}

运行效果:

复制代码
[2026-04-14 15:30:20] [INFO] service start successfully, pid = 12345
[2026-04-14 15:30:20] [DEBUG] connect to client success, fd = 5
[2026-04-14 15:30:20] [WARNING] low memory warning
[2026-04-14 15:30:20] [ERROR] open fifo failed
[2026-04-14 15:30:20] [FATAL] system error, process exit

同时,这些内容也会被写入 service.log


总结

这个极简日志类虽然代码不长,但具备了日常开发所需的核心能力:

  • 优雅调用:operator() 重载
  • 自动时间戳 + 日志级别
  • 控制台 + 文件双输出
  • 全局单例,随处可用
  • 轻量、无依赖、易集成

无论是课程实验、管道通信、多进程服务,还是小型项目,都可以直接拿来用,非常适合作为自己的基础工具类。

相关推荐
ServBay9 小时前
打通 AI 编程本地运维边界,利用 MCP 协议简化环境与服务管理
后端·ai编程·mcp
程序员cxuan9 小时前
DeepSeek 杀入多模态,识图功能正式上线!
人工智能·后端·程序员
IT_陈寒12 小时前
SpringBoot这个自动配置坑我跳了三次
前端·人工智能·后端
用户3952409988012 小时前
排坑日记:ASP.NET Core 中 "Required field is not provided" 验证错误全记录
后端
用户83562907805113 小时前
使用 Python 自动化 PowerPoint 形状布局与格式设置
后端·python
AlfredZhao13 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
Oneslide14 小时前
sudo免密权限配置不生效
后端
站大爷IP14 小时前
为什么Python不用var或let声明变量?
后端
赴星半途14 小时前
NestJS实战-创建AuthService
后端