muduo网络库(三):日志输出
- muduo网络库(三):日志输出
- 总体介绍
- 单例模式的实现
- 为什么不直接构造而用单例
- 利用宏函数输出日志信息
- 时间戳的设计精髓
muduo网络库(三):日志输出
总体介绍
本项目日志输出涉及 4 个文件:
Logger.h/Logger.cc:定义日志记录器类LoggerTimestamp.h/Timestamp.cc:定义时间戳类TimestampLogger是最核心的类,负责记录日志信息。它只有一个成员变量logLevel_,用于存储当前日志级别。构造函数是私有的,只能通过instance()获取唯一实例。对外提供两个接口:
setLogLevel(int level):设置日志级别func_Log(string msg):输出日志信息
输出日志时,func_Log()会自动调用Timestamp::now().toString()获取当前时间,将时间戳和日志消息一起输出。
单例模式的实现
classLogger:noncopyable{public:staticLogger&instance();// 获取日志实例voidsetLogLevel(intlevel);// 设置日志级别voidfunc_Log(string msg);// 输出日志信息private:LogLevel logLevel_;// 当前日志级别Logger(){};// 私有构造,禁止外部创建};``` ```cpp Logger&Logger::instance(){staticLogger logger;// 局部静态变量实现单例returnlogger;}instance()中定义了一个局部静态变量logger。局部静态变量的特点是:第一次执行到该语句时才初始化,之后一直存在直到程序结束。这保证了两个关键特性:
- 线程安全:C++11 起保证局部静态变量初始化是线程安全的,多个线程同时调用
instance()不会创建多个实例 - 懒加载:只有第一次调用时才创建,避免启动时不必要的开销
使用方式很简单:Logger& logger = Logger::instance(),无论在哪里调用,拿到的都是同一个对象。
为什么不直接构造而用单例
如果构造函数是公有的,每个人都能Logger logger1;创建自己的实例,日志级别等状态就不一致了。把构造函数设为私有后,只能通过instance()获取实例,而instance()内部的static Logger logger保证了全局唯一。
同时Logger继承了noncopyable,和私有构造函数形成双重保险,确保实例不会被复制。
利用宏函数输出日志信息
#defineLOG_INFO(Logmessage,...)\do{\Logger&logger=Logger::instance();\Logger::instance().setLogLevel(Logger::kInfo);\charbuf[1024]={0};\snprintf(buf,1024,Logmessage,##__VA_ARGS__);\logger.func_Log(buf);\}while(0)``` 这个宏函数做的事情很直白:1.通过 `instance()` 获取单例2.2.调用 `setLogLevel(kInfo)` 设置日志级别3.3.用 `snprintf` 把格式字符串和可变参数格式化到 `buf` 中4.4.调用 `logger.func_Log(buf)` 输出 几个关键点:-`##__VA_ARGS__` 是 C99 特性,当没有可变参数时会自动去掉前面的逗号--`do{...}while(0)` 保证宏在任何上下文(如if-else)中都能安全展开为单条语句--日志级别有5个:`kDebug`、`kInfo`、`kWarn`、`kError`、`kFatal`,对应 `LOG_DEBUG`、`LOG_INFO` 等宏 使用起来就是一个增强版的 `printf`: ```cppLOG_INFO("server started on port %d",port);LOG_ERROR("connection failed: %s",strerror(errno));简洁、统一、零额外开销。
时间戳的设计精髓
在Logger.cc中,输出日志时会调用Timestamp::now().toString()来获取当前时间的字符串表示。
classTimestamp{public:Timestamp():microSecondsSinceEpoch_(0){}explicitTimestamp(int64_tmicroSecondsSinceEpoch):microSecondsSinceEpoch_(microSecondsSinceEpoch_){}staticTimestampnow();std::stringtoString()const;private:int64_tmicroSecondsSinceEpoch_;// 微秒级时间戳};Timestamp只有一个int64_t成员变量:
一、explicit修饰单参数构造函数,禁止int64_t到Timestamp的隐式转换。否则函数参数为Timestamp类型时,传入一个整数会被悄悄转换,容易产生难以察觉的 bug。
二、now()设计为静态成员函数。获取当前时间是一个与具体实例无关的操作,如果设计为非静态,就得先Timestamp t1; t1.now();,白白创建和析构一个无用的中间对象。设计为static后,直接Timestamp::now()即可,返回的临时对象会自动析构。
三、用int64_t存储微秒级时间,而非struct timeval(秒+微秒两个字段)。好处是时间比较和差值计算都变成简单的整数运算,内存也更紧凑。
注:C++11 中
Timestamp::now()返回的临时对象会优先使用移动构造或 RVO 优化,避免了拷贝开销。这也是Timestamp不需要显式声明析构函数的原因——只有一个int64_t,没有需要释放的资源。