目录
[1. 单例模式](#1. 单例模式)
[2. operator () 重载](#2. operator () 重载)
[3. 可变参数与格式化](#3. 可变参数与格式化)
[4. 时间戳](#4. 时间戳)
[5. 文件输出](#5. 文件输出)
在日常写项目、做实验、写服务端程序时,我们总需要一个轻量好用的日志工具。既能打印控制台,又能输出到文件,自带时间戳和级别,调用方式还要干净优雅。
今天就基于重载 operator () 的思路,实现一个极简、可直接嵌入项目的 C++ 日志类,代码不多但功能完整,非常适合学习和工程使用。
设计思路
- 调用方式优雅 :使用
()运算符重载,像函数一样直接调用log(INFO, "hello %d", 123); - 功能完整:自动时间戳、日志级别、控制台输出、文件输出
- 全局单例:一处定义,随处使用,不用反复创建对象
- 轻量无依赖:仅用标准库,不引入第三方组件
- 格式统一 :
[时间] [级别] 信息,美观易读
完整代码实现
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()重载 - 自动时间戳 + 日志级别
- 控制台 + 文件双输出
- 全局单例,随处可用
- 轻量、无依赖、易集成
无论是课程实验、管道通信、多进程服务,还是小型项目,都可以直接拿来用,非常适合作为自己的基础工具类。