目录
noncopyable类防止派生类对象进行拷贝构造,或者是赋值操作,但是能够让派生类对象正常的构造跟析构。muduo网络库的日志系统,日志对于一个软件来说呢,还是非常非常重要的。
日志级别:
infer: 正常的一个日志输出,打印一些重要的流程信息;
error:打印错误信息,但不影响软件继续进行下去;
fatal: 系统是无法正常向下继续运行,输出关键的日志信息,然后exit;
debug: 调试信息呢,一般来说是非常非常多的,可能我们在系统正常运行的情况下,会默认把debug日志关掉,当我们需要去输出debug日志的时候,再打开一个开关。
定义一个日志类,由 noncopyable类继承而来,私有化构造函数,静态成员函数获取唯一日志实列对象,包括设置日志级别、写入日志信息函数。
构造函数:
单例模式的一种,将构造函数放到静态函数中,使用了静态局部变量的概念,静态局部变量的生命周期延长至整个程序运行期间,确保只有在第一次调用instance函数时才会创建Logger实例,在后续调用时直接返回创建的实例,并在程序退出时销毁。该实现方式是线程安全的。
Logger::setLogLevel设置日志级别
定义日志枚举信息:
Logger::log写日志
Logger.h
#pragma once #include <string> #include "noncopyable.h" //LOG_INFO("%s %d", arg1, arg2) //__VA_ARGS__获取可变参的宏 //logmsgFormat:字符串,后面...是可变参 #define LOG_INFO(logmsgFormat, ...) \ do \ { \ Logger &logger = Logger::instance(); \ logger.setLogLevel(INFO); \ char buf[1024] = {0}; \ snprintf(buf, 1024, logmsgFormat, ##__VA_ARGS__); \ logger.log(buf); \ } while(0) #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) #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); \ } while(0) #ifdef MUDEBUG #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 #define LOG_DEBUG(logmsgFormat, ...) #endif //定义日志的级别 INFO(正常的日志输出) ERROR(错误,不影响软件继续向下执行) // FATAL(毁灭性的打击,系统无法正常向下运行) DEBUG(调试信息,一般是关闭的) enum LogLevel { INFO, //普通信息 ERROR,//错误信息 FATAL,//core信息 DEBUG,//调试信息 }; //输出一个日志类 class Logger : noncopyable { public: //获取日志唯一的实例对象 static Logger& instance(); //设置日志级别 void setLogLevel(int level); //写日志 void log(std::string msg); private: int logLevel_; };
这里日志只需要打印出我们所需的信息,不需要用户去获取日志实例然后再设置日历级别再写日志,直接定义成四种宏,使用更方便。
使用格式化字符形式进行日志输出。
使用宏定义优点:
1.代码简洁:使用宏定义可以将一系列操作封装在一个宏中,代码读起来更简洁;
2. 灵活性,宏定义可以接收不同数量的参数,并使用可变参数列表来处理;
3. 高可定制性,宏定义允许开发者根据自己的需求自定义日志格式、日志级别等;
4. 低开销:宏定义在编译时进行替换,不会引入额外函数调用开销。
注意:
使用do{}while(0)来确保宏定义展开有正确的语法结构。
使用##_VA_ARGS_可变参数占位符,它可以接收任意数量和任意类型的参数,需要放在参数化列表的末尾。
对于debug日志,一般不使用时将其关闭,因为debug日志一般信息比较多,一直开着占用资源,所以我们定义#ifdef #endif 条件编译指令。
如何使用:
LOG_INFO("%s %d", arg1, arg2)
Logger.cc
#include "Logger.h" #include "Timestamp.h" #include <iostream> //获取日志唯一的实例对象 Logger& Logger::instance() { static Logger logger; return logger; } //设置日志级别 void Logger::setLogLevel(int level) { logLevel_ = level; } //写日志 [级别信息] time : msg void Logger::log(std::string msg) { switch (logLevel_) { case INFO: std::cout << "[INFO]"; break; case ERROR: std::cout << "[ERROR]"; break; case FATAL: std::cout << "[FATAL]"; break; case DEBUG: std::cout << "[DEBUG]"; break; default: break; } //打印时间和msg std::cout << Timestamp::now().toString() << " : " << msg << std::endl; }