---------------------------------------------------------------------------------------------------------------------------------
每日鸡汤:找一个对的人,然后好好去爱。一个你跟他在一起,然后又可以舒舒服服做自己的人。
---------------------------------------------------------------------------------------------------------------------------------
目录
一:可变参数的理解
int sum(int n, ...)
{
va_list s; // char*
va_start(s, n);
int sum = 0;
while(n)
{
sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
n--;
}
va_end(s); //s = NULL
return sum;
}
可变参数。va_list 一个 char* 类型的指针,va_start 函数作用是将 s 指向函数栈帧中的第一个非可变形参,因为函数的实参是从右向左的顺序进行在栈帧中压栈的,所以只需要知道第一个非可变形参的位置,那么就能找到第一个可变参数的位置,之后再解析出所有的可变参数,故此必须要求可变参数的最左边必须要有一个具体的参数(n),va_arg 函数作用是根据第二个参数的类型来提取可变参数,va_end 函数作用是将该指针置nullptr。
二:日志理解
2.1、日志打印
日志的元素一般包含有,时间、等级、内容、名称等等。
时间函数介绍:time返回当前时间的时间戳;localtime加工时间戳,将其转变成为我们一眼看得懂的形式。
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);
一般的日志打印包含为:默认部分+自定义部分
- 默认部分:日志的等级、时间
- 自定义部分:日志的内容
默认代码:
// 默认部分
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];
snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
printLog(level, logtxt); // 将日志打印到指定地方
将日志等级转化为字符串:
std::string LevelToString(int level)
{
switch (level)
{
case Info:
return "Info";
break;
case Debug:
return "Debug";
break;
case Warning:
return "Warning";
break;
case Error:
return "Error";
break;
case Fatal:
return "Fatal";
break;
default:
return "None";
break;
}
}
将日志打印到指定地方:
void printLog(int level, const std::string &logtxt)
{
// std::cout << "1" << std::endl;
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 logmessage(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]",
ctime->tm_year + 1990, 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];
snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
printLog(level, logtxt); // 将日志打印到指定地方
}
不过为了方便我们用户使用,一般我们将它重载到 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];
snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
printLog(level, logtxt); // 将日志打印到指定地方
// printf("%s", logtxt);
}
2.2、打印模式
我们可以通过一些选项来决定我们将日志打印到哪个地方【显示器、一个文件内、多个文件内】
模式设置:
// 将日志打印到Method
void LogWhere(int Method)
{
printMethod = Method;
}
模式选项:
void printLog(int level, const std::string &logtxt)
{
// std::cout << "1" << std::endl;
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 logname_ = path + logname;
int fd = open(logname_.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
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);
PrintOneFile(filename_, logtxt);
}
2.3、Log使用
#include "log.hpp"
using namespace std;
int main()
{
Log lg;
int cnt = 5;
while(cnt--)
{
// 默认向显示器上打印
lg(Info, "this a info");
lg(Debug, "this a debug");
lg(Fatal, "this a fatal");
}
cnt = 5;
while(cnt--)
{
// 向一个文件内打印
lg.LogWhere(OneFile);
lg(Info, "this a info");
lg(Debug, "this a debug");
lg(Fatal, "this a fatal");
}
cnt = 5;
while(cnt--)
{
// 向多个文件中打印
lg.LogWhere(ClassFile);
lg(Info, "this a info");
lg(Debug, "this a debug");
lg(Fatal, "this a fatal");
}
// cout << "hell" << endl;
return 0;
}
2.4、Log整体代码
#pragma once
#include <iostream>
#include <time.h>
// open函数头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// write函数头文件
#include <unistd.h>
// 可变参数头文件
#include <stdarg.h>
#define LogFile "log.txt"
#define SIZE 1024
enum Level
{
Info,
Debug,
Warning,
Error,
Fatal
};
enum PrintM
{
Screen = 1,
OneFile,
ClassFile
};
class Log
{
public:
Log()
{
printMethod = Screen; // 默认打印到屏幕上
path = "./log/"; // 默认打印文件至该路径下
}
~Log()
{
}
public:
// 决定要将日志打印至哪里
void Enable(int method)
{
printMethod = method;
}
// 根据日志等级获取对应的字符串
std::string LevelToString(int level)
{
switch (level)
{
case Info:
return "Info";
case Debug:
return "Info";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "None";
}
}
// 将信息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 logname_ = path + logname; // ./log/log.txt
int fd = open(logname_.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd < 0)
return;
write(fd, logtxt.c_str(), logtxt.size());
close(fd);
}
// 将日志打印到多个文件中,按level进行分类
void PrintClassFile(int level, const std::string &logtxt)
{
std::string filename = LogFile; // "log.txt"
filename += ".";
filename += LevelToString(level); // "log.txt.Info/Debug/Warning..."
PrintOneFile(filename, logtxt);
}
// 打印日志的形式
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];
snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
PrintLog(level, logtxt);
}
private:
int printMethod; // 打印位置:屏幕/一个文件/多个文件
std::string path; // 路径
};
三:结语
今天的分享到这里就结束了,如果觉得文章还可以的话,就一键三连支持一下欧。各位的支持就是捣蛋鬼前进的动力。