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() 重载
  • 自动时间戳 + 日志级别
  • 控制台 + 文件双输出
  • 全局单例,随处可用
  • 轻量、无依赖、易集成

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

相关推荐
小辉同志2 小时前
208. 实现 Trie (前缀树)
开发语言·c++·leetcode·图论
Ops菜鸟(Xu JieHao)2 小时前
Linux快速生成测试日志flog
linux·运维·服务器·日志·log
掘金者阿豪2 小时前
一次 AI 调用 15 万 Token 只花了 \$0.058?彻底搞懂 Token、缓存读、补全计费机制!(附完整架构图)
后端
John.Lewis2 小时前
C++加餐课-stack_queue:反向迭代器
数据结构·c++
云栖梦泽2 小时前
Linux内核与驱动:12.设备树实例分析
linux·c++·单片机
程序员柒叔2 小时前
OpenClaw 踩坑记:Cron 任务 Feishu 推送失败
后端·github
AskHarries2 小时前
在 AI 快速发展的今天,“人还重要吗?
后端
SimonKing2 小时前
OpenCode 20 个斜杠命令,90% 的人只用过 3 个
java·后端·程序员
Gopher_HBo2 小时前
BlockingQueue详解
java·后端