一、头文件(Logger.h)完整注释+语法解析
cpp
// 1. 预处理指令:防止头文件被重复包含(现代编译器特性,替代传统#ifndef/#define/#endif)
// 语法:#pragma是编译器专属预处理指令,once表示该文件仅被编译一次
// 作用:避免多次包含Logger.h导致类/宏重复定义的编译错误
#pragma once
// 2. 引入C++标准库字符串头文件
// 语法:#include <string> 引入系统标准头文件,尖括号<>用于系统库
// 作用:提供std::string类型的定义,支撑Logger类的log()函数参数、字符串拼接等操作
#include <string>
// 3. 引入自定义的noncopyable.h头文件
// 语法:#include "头文件名.h" 引入项目内自定义头文件,双引号""用于本地文件
// 作用:获取noncopyable类的声明,Logger类继承该类以禁止拷贝
#include "noncopyable.h"
// 4. 宏定义:LOG_INFO 日志宏(普通信息级别)
// 语法:#define 宏名(参数列表) 宏体,...表示可变参数,##__VA_ARGS__处理可变参数(兼容无参数场景)
// 设计思路:用do-while(0)包裹宏体,保证宏在任何场景下语法正确(如if后不加{}也不会出错)
#define LOG_INFO(logmsgFormat, ...) \ // \是行连接符,将多行宏体合并为一行
do \ // do-while(0)包裹:保证宏体作为一个整体执行
{ \
Logger &logger = Logger::instance(); \ // 获取Logger单例对象的引用(避免拷贝)
logger.setLogLevel(INFO); \ // 设置日志级别为INFO
char buf[1024] = {0}; \ // 定义1024字节字符数组缓冲区,{0}初始化所有元素为0
snprintf(buf, 1024, logmsgFormat, ##__VA_ARGS__); \ // 格式化日志内容到buf:snprintf是安全格式化函数,避免缓冲区溢出;##__VA_ARGS__处理可变参数(无参数时自动省略逗号)
logger.log(buf); \ // 调用log()函数输出日志
} while (0) // while(0):让do-while仅执行一次,保证宏体语法完整性
// 5. 宏定义:LOG_ERROR 日志宏(错误信息级别)
// 语法/逻辑与LOG_INFO一致,仅日志级别改为ERROR
#define LOG_ERROR(logmsgFormat, ...) \
do \
{ \
Logger &logger = Logger::instance(); \
logger.setLogLevel(ERROR); \
char buf[1024] = {0}; \
snprintf(buf, 1024, logmsgFormat, ##__VA_ARGS__); \
logger.log(buf); \
} while (0)
// 6. 宏定义:LOG_FATAL 日志宏(致命错误级别)
// 语法/逻辑与LOG_INFO一致,新增exit(-1):输出日志后终止程序
#define LOG_FATAL(logmsgFormat, ...) \
do \
{ \
Logger &logger = Logger::instance(); \
logger.setLogLevel(FATAL); \
char buf[1024] = {0}; \
snprintf(buf, 1024, logmsgFormat, ##__VA_ARGS__); \
logger.log(buf); \
exit(-1); \ // exit(-1):终止程序,返回非0表示异常退出
} while (0)
// 7. 条件编译:仅定义MUDEBUG宏时,才启用LOG_DEBUG宏
// 语法:#ifdef 宏名 编译分支1 #else 编译分支2 #endif
// 作用:调试模式下输出DEBUG日志,发布模式下禁用(减少性能开销)
#ifdef MUDEBUG
// 8. 宏定义:LOG_DEBUG 日志宏(调试信息级别)
#define LOG_DEBUG(logmsgFormat, ...) \
do \
{ \
Logger &logger = Logger::instance(); \
logger.setLogLevel(DEBUG); \
char buf[1024] = {0}; \
snprintf(buf, 1024, logmsgFormat, ##__VA_ARGS__); \
logger.log(buf); \
} while (0)
#else
// 9. 非调试模式:LOG_DEBUG宏为空(调用时无任何操作)
#define LOG_DEBUG(logmsgFormat, ...)
#endif
// 10. 枚举类型定义:日志级别枚举
// 语法:enum 枚举名 { 枚举值1, 枚举值2, ... }; 枚举默认从0开始赋值(INFO=0, ERROR=1, FATAL=2, DEBUG=3)
// 作用:用枚举替代魔法数字,提高代码可读性和可维护性
enum LogLevel
{
INFO, // 普通信息级别
ERROR, // 错误信息级别
FATAL, // 致命错误级别(会终止程序)
DEBUG // 调试信息级别(仅调试模式启用)
};
// 11. 日志类定义:封装日志的级别设置、日志输出逻辑
// 语法:class 类名 : 继承基类 { 成员声明 }; Logger继承noncopyable以禁止拷贝
// 设计模式:单例模式(instance()返回唯一实例)
class Logger : noncopyable // 继承noncopyable:禁止Logger对象被拷贝(拷贝构造/赋值运算符被删除)
{
// 12. 公有访问控制符:暴露对外接口
public:
// 13. 静态成员函数声明:获取日志类的唯一实例(单例模式)
// 语法:static 返回值类型 函数名(); 静态函数属于类而非对象,无this指针
// 作用:返回Logger的唯一实例引用,保证全局只有一个Logger对象
static Logger &instance();
// 14. 成员函数声明:设置日志级别
// 语法:void 函数名(参数类型 参数名); 无返回值,接收int类型的级别参数
// 作用:将日志级别存储到私有成员logLevel_中
void setLogLevel(int level);
// 15. 成员函数声明:写日志
// 语法:void 函数名(参数类型 参数名); 接收std::string类型的日志内容
// 作用:拼接日志级别、时间戳和日志内容,输出到控制台
void log(std::string msg);
// 16. 私有访问控制符:隐藏内部数据
private:
// 17. 私有成员变量:存储当前日志级别
// 语法:int 变量名_; 下划线结尾是C++工程命名规范,区分参数和成员变量
// 作用:记录当前设置的日志级别(INFO/ERROR/FATAL/DEBUG)
int logLevel_;
};
二、源文件(Logger.cpp)完整注释+语法解析
cpp
// 1. 引入C++标准库输入输出流头文件
// 语法:#include <iostream> 提供std::cout(控制台输出)、std::endl(换行)等功能
// 作用:支撑log()函数的日志输出操作
#include <iostream>
// 2. 引入自定义Logger头文件
// 语法:#include "Logger.h" 获取Logger类的声明,否则类外定义成员函数会编译报错
#include "Logger.h"
// 3. 引入自定义Timestamp头文件
// 语法:#include "Timestamp.h" 获取Timestamp类的声明,用于生成日志的时间戳
#include "Timestamp.h"
// 4. 静态成员函数instance()类外定义:实现单例模式
// 语法:返回值类型 类名::函数名() { 函数体 } 静态函数类内声明加static,类外定义不加
// 单例实现:局部静态变量(static Logger logger),程序运行期仅初始化一次,保证全局唯一
Logger &Logger::instance()
{
static Logger logger; // 局部静态变量:第一次调用时初始化,后续调用返回同一对象;存储在静态存储区,生命周期贯穿程序全程
return logger; // 返回Logger对象的引用:避免拷贝(若返回值会调用拷贝构造,但Logger继承noncopyable,拷贝构造已删除)
}
// 5. 成员函数setLogLevel()类外定义:设置日志级别
// 语法:void 类名::函数名(参数类型 参数名) { 函数体 }
// 作用:将传入的级别参数赋值给私有成员logLevel_
void Logger::setLogLevel(int level)
{
logLevel_ = level; // 直接修改私有成员变量:成员函数可访问类的私有成员
}
// 6. 成员函数log()类外定义:写日志(核心逻辑)
// 语法:void 类名::函数名(参数类型 参数名) { 函数体 }
// 作用:拼接日志级别前缀、时间戳和日志内容,输出到控制台
void Logger::log(std::string msg)
{
std::string pre = ""; // 定义字符串变量pre,存储日志级别前缀(如[INFO]),初始化为空字符串
// 7. 分支判断:根据当前日志级别设置前缀
// 语法:switch(变量) { case 枚举值: 逻辑; break; default: 逻辑; }
// 作用:匹配不同日志级别,设置对应的前缀字符串
switch (logLevel_)
{
case INFO: // 匹配INFO级别(枚举值0)
pre = "[INFO]"; // 设置前缀为[INFO]
break; // break跳出switch,避免穿透到下一个case
case ERROR: // 匹配ERROR级别(枚举值1)
pre = "[ERROR]";
break;
case FATAL: // 匹配FATAL级别(枚举值2)
pre = "[FATAL]";
break;
case DEBUG: // 匹配DEBUG级别(枚举值3)
pre = "[DEBUG]";
break;
default: // 匹配未定义的级别(兜底逻辑)
break;
}
// 8. 输出日志到控制台
// 语法解析:
// - std::cout:标准输出流,向控制台打印内容
// - +:字符串拼接运算符,将pre(级别前缀)、Timestamp::now().toString()(当前时间戳字符串)拼接
// - <<:流插入运算符,依次输出拼接后的字符串、分隔符、日志内容、换行符
// - Timestamp::now():调用Timestamp的静态函数获取当前时间戳对象
// - toString():调用Timestamp的常成员函数,将时间戳转为字符串
// - std::endl:输出换行符并刷新缓冲区
std::cout << pre + Timestamp::now().toString() << " : " << msg << std::endl;
}
三、核心语法&符号总结
| 符号/语法 | 作用 | 出现位置 |
|---|---|---|
#pragma once |
预处理指令,防止头文件重复包含 | Logger.h开头 |
#define |
宏定义指令,定义日志宏(LOG_INFO/ERROR/FATAL/DEBUG) | Logger.h日志宏定义 |
.../##__VA_ARGS__ |
可变参数宏:...接收可变参数,##__VA_ARGS__处理可变参数(无参数时省略逗号) |
日志宏的参数列表/格式化函数 |
\ |
行连接符,将多行宏体合并为一行(宏定义必须单行,用\拆分) | 日志宏的每行结尾 |
do-while(0) |
包裹宏体,保证宏在任何语法场景下正确(如if后不加{}) | 日志宏体 |
enum |
枚举类型定义,封装日志级别(替代魔法数字) | LogLevel枚举 |
class : noncopyable |
类继承,Logger继承noncopyable以禁止拷贝 | Logger类定义 |
static |
静态成员函数/变量: 1. instance():类级函数,无对象也可调用 2. logger:局部静态变量,实现单例 | instance()函数/局部变量 |
:: |
作用域解析符: 1. Logger::instance():定位类的静态函数 2. Timestamp::now():定位Timestamp的静态函数 | 单例调用/时间戳调用 |
& |
引用: 1. Logger &logger:获取单例对象的引用(避免拷贝) 2. 返回值Logger&:返回对象引用 | instance()返回值/logger变量 |
switch/case/break |
分支语句,根据日志级别匹配前缀 | log()函数的级别判断 |
+ |
字符串拼接运算符,拼接级别前缀和时间戳 | log()函数的输出逻辑 |
<< |
流插入运算符,向std::cout输出内容 | log()函数的控制台输出 |
四、关键设计亮点&注意事项
- 单例模式实现 :
instance()函数中的局部静态变量static Logger logger是C++单例模式的经典实现,优点是:线程安全(C++11及以后)、无需手动释放、代码简洁。 - 宏定义的do-while(0)包裹 :
避免宏在if/for等语句后不加{}导致的语法错误(如if(flag) LOG_INFO("test");),保证宏体作为一个整体执行。 - noncopyable继承 :
禁止Logger对象被拷贝,确保单例的唯一性(若允许拷贝,可能创建多个Logger对象,违背单例设计)。 - 条件编译的DEBUG宏 :
调试模式下输出DEBUG日志,发布模式下禁用,减少生产环境的性能开销。 - 安全格式化函数snprintf :
替代sprintf,指定缓冲区长度(1024),避免缓冲区溢出导致的程序崩溃。
五、测试示例(补充)
可编写main函数测试日志功能:
cpp
#include "Logger.h"
int main() {
LOG_INFO("程序启动成功,进程ID:%d", getpid()); // 输出INFO级别日志
LOG_ERROR("文件读取失败,路径:%s", "test.txt"); // 输出ERROR级别日志
LOG_DEBUG("调试信息:变量x的值为%d", 10); // 仅MUDEBUG定义时输出
// LOG_FATAL("致命错误:内存分配失败"); // 输出后终止程序
return 0;
}
编译时若要启用DEBUG日志,需添加编译选项-DMUDEBUG:
bash
g++ -std=c++11 main.cpp Logger.cpp Timestamp.cpp -o test_log -DMUDEBUG