目录
1.日志的简介
1.1日志的概念
1.2日志的常见格式
2.实现日志类
包含所需头文件,定义所需宏
类成员
levelToString
operator()
printLog
printOneFile&printClassFile
完整呈现
1.日志的简介
1.1日志的概念
日志是软件运行过程中产生的带时间戳的、结构化的诊断记录,是调试的重要助手
1.2日志的常见格式
一条日志信息常包括默认部分与自定义部分
默认部分常包括:日志等级与日志时间
自定义部分则包括用户自定义的日志消息
常见的日志等级为:
(1)Info:常规消息(2)Warning:报警信息(3)Error:比较严重了,可能需要立即出来(4)Fatal:致命的(5)Debug:调试
2.实现日志类
使用下述类时,注意确保
./log/目录存在
包含所需头文件,定义所需宏
#pragma once #include <fcntl.h> #include <iostream> #include <stdarg.h> #include <string> #include <sys/stat.h> #include <sys/types.h> #include <time.h> #include <unistd.h> #define SIZE 1024 #define Info 0 #define Debug 1 #define Warning 2 #define Error 3 #define Fatal 4 #define Screen 1 #define Onefile 2 #define Classfile 3 #define LOGFILE "log.txt"类成员
class Log { public: //构造函数:初始化打印方法与路径 Log() : printMethod(Screen) , path("./log/"){ } //用户自定义打印方法 void Enable(int method) { printMethod = method; } //将日志等级转化为字符串 std::string levelToString(int level) { } //运算符重载以记录日志消息 void operator()(int level, const char *format, ...) { } //打印函数 void printLog(int level, const std::string &logtxt) { } //向一个文件打印日志消息 void printOneFile(const std::string &logname, const std::string &logtxt) { } //分类打印日志消息 void printClassFile(int level, const std::string &logtxt) { } //无实际意义,让类看着更完整 ~Log() { } private: int printMethod;//打印方法:显示器;一个文件;多个文件 std::string path;//日志文件存放的路径 };levelToString
std::string levelToString(int level) { switch (level) { case Info: return "Info"; case Debug: return "Debug"; case Warning: return "Warning"; case Error: return "Error"; case Fatal: return "Fatal"; default: return "NONE"; } }根据宏定义直接转化
operator()
void operator()(int level, const char *format, ...) { // 默认部分:【日志等级】【年-月-日 时:分:秒】 time_t t = time(nullptr); struct tm *ctime = localtime(&t); char leftbuffer[SIZE]; snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec); // 自定义部分 va_list s; va_start(s, format); char rightbuffer[SIZE]; vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); va_end(s); // 整合 char logtxt[SIZE * 2 + 1]; snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer); // 存储或者打印 printLog(level, logtxt); }(1)重载 operator(),使得写日志像函数调用一样自然
Log log; log(Info, "Server started on port %d", 8080); // 输出:[Info][2024-...] Server started on port 8080(2)默认部分包含日志等级和日志时间:将时间戳改造为年月日时分秒的格式。指的注意的是年是从1900开始的,月的范围是0-11,打印时需注意
(3)对于自定义部分日志信息,需要处理可变参数:
void Log(int level, const char *format, ...) // 1. 函数声明接受可变参数 { // 2. 定义遍历指针 va_list args_ptr; // char* 指针,用于逐个“指向”内存中的可变参数 // 3. 初始化指针,定位到第一个可变参数 va_start(args_ptr, format); // 关键:编译器知道‘format’是最后一个固定参数, // 通过它的地址计算出第一个可变参数在内存中的位置, // 让 args_ptr 指向那里。 // 4. 使用指针进行格式化 char buffer[256]; vsnprintf(buffer, sizeof(buffer), format, args_ptr); // vsnprintf 内部会: // a) 解析 format 字符串中的占位符(如 %d, %s) // b) 根据占位符类型,从 args_ptr 指向的内存位置“取出”相应大小的数据 // c) 每取一个参数,自动将 args_ptr 向内存高位移动相应距离,指向下一个参数 // d) 循环直到所有占位符处理完毕 // 5. 清理指针(防止野指针) va_end(args_ptr); // 通常将 args_ptr 置为 NULL }(4)将默认部分与自定义部分整合后调用日志打印函数
printLog
void printLog(int level, const std::string &logtxt) { switch (printMethod) { case Screen: std::cout << logtxt << std::endl; break; case Onefile: printOneFile(LOGFILE, logtxt); break; case Classfile: printClassFile(level, logtxt); break; default: break; } }日志打印函数通过打印方法,调用对应打印方法的函数
printOneFile&printClassFile
void printOneFile(const std::string &logname, const std::string &logtxt) { std::string filename = path + logname; int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666); // log.txt if (fd < 0) return; write(fd, logtxt.c_str(), logtxt.size()); close(fd); } void printClassFile(int level, const std::string &logtxt) { std::string filename = LOGFILE; filename += "."; filename += levelToString(level); // log.txt./Info/Debug....... printOneFile(filename, logtxt); }(1)向一个文件打印所有日志信息时,首先为默认文件名添加路径(让日志信息处于新建目录中),然后调用系统调用open打开文件并写入
(2)分类打印日志消息,只需要为每种日志定义好文件名,然后复用向一个文件打印日志信息的代码就可
完整呈现
使用下述类时,注意确保
./log/目录存在
#pragma once #include <fcntl.h> #include <iostream> #include <stdarg.h> #include <string> #include <sys/stat.h> #include <sys/types.h> #include <time.h> #include <unistd.h> #define SIZE 1024 #define Info 0 #define Debug 1 #define Warning 2 #define Error 3 #define Fatal 4 #define Screen 1 #define Onefile 2 #define Classfile 3 #define LOGFILE "log.txt" class Log { public: Log() : printMethod(Screen) , path("./log/"){ } void Enable(int method) { printMethod = method; } std::string levelToString(int level) { switch (level) { case Info: return "Info"; case Debug: return "Debug"; case Warning: return "Warning"; case Error: return "Error"; case Fatal: return "Fatal"; default: return "NONE"; } } void operator()(int level, const char *format, ...) { // 默认部分:【日志等级】【年-月-日 时:分:秒】 time_t t = time(nullptr); struct tm *ctime = localtime(&t); char leftbuffer[SIZE]; snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec); // 自定义部分 va_list s; va_start(s, format); char rightbuffer[SIZE]; vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); va_end(s); // 整合 char logtxt[SIZE * 2 + 1]; snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer); // 存储或者打印 printLog(level, logtxt); } void printLog(int level, const std::string &logtxt) { switch (printMethod) { case Screen: std::cout << logtxt << std::endl; break; case Onefile: printOneFile(LOGFILE, logtxt); break; case Classfile: printClassFile(level, logtxt); break; default: break; } } void printOneFile(const std::string &logname, const std::string &logtxt) { std::string filename = path + logname; int fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666); // log.txt if (fd < 0) return; write(fd, logtxt.c_str(), logtxt.size()); close(fd); } void printClassFile(int level, const std::string &logtxt) { std::string filename = LOGFILE; filename += "."; filename += levelToString(level); // log.txt./Info/Debug....... printOneFile(filename, logtxt); } ~Log() { } private: int printMethod; std::string path; };