目录
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;
};