文章目录
- 一、可变参数的使用
- 二、Log
- 2.1 日志打印
- 2.1.1 时间获取
- 2.1.2 日志分块打印
- 2.2 打印模式选择
- 2.3 Log 使用样例
- 2.4 Log 完整源码
- 三、结语
一、可变参数的使用
int sum(int n, ...)
{
va_list s; // va_list 本质上就是一个指针
va_start(s, n);
int sum = 0;
while(n)
{
sum += va_arg(s, int);
n--;
}
va_end(s);
return sum;
}
va_list
本质上就是一个 char *
类型,va_start
的作用就是让指针 s
指向函数栈帧中第一个非可变形参。因为函数的实参是按照从右向左的顺序进行压栈的,因此只要知道了第一个非可变形参的位置,就可以找到第一个可变参数的位置,进而去解析所有的可变参数。这就要求:可变参数的左边必须要有一个具体的参数。va_arg
是根据第二个参数所确定的类型来提取可变参数,va_end
是将 s
置空。
二、Log
2.1 日志打印
日志一般包括:日志的时间、日志的等级、日志的内容、文件名称和行号。
2.1.1 时间获取
时间获取介绍:time
返回值是时间戳、gettimeofday
系统调用接口、localtime
将一个时间戳转化成我们看得懂的格式。
gettimeofday:
localtime:
void logmessage(int level, const char *format, ...)
{
time_t t = time(nullptr);
struct tm *ctime = localtime(&t);
printf("%d-%d-%d %d:%d:%d", ctime->tm_year+1900, ctime->tm_mon+1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
}
2.1.2 日志分块打印
根据日志的内容,可以将一条日志信息分成两部分:默认部分和自定义部分。
-
默认部分:日志的时间、等级。
-
自定义部分:日志的内容,需要进行格式化控制的内容。
日志等级转字符串模块:
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";
}
}
默认部分代码:通过 snprintf
函数将默认部分的信息写入到一个字符数组中。
void DefaultMessage(int level, char *defaultbuffer, int size)
{
time_t t = time(nullptr);
struct tm *ctime = localtime(&t);
snprintf(defaultbuffer, size, "[%s][%d-%d-%d %d:%d:%d]", LevelToString(level), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
}
自定义部分代码:通过 vsnprintf
函数来帮助我们解析用户的格式化输入,将用户自定义的信息写入到一个字符数组中。
void logmessage(int level, const char *format, ...)
{
char defaultbuffer[SIZE]; // 存储默认内容
DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));
char userbuffer[SIZE]; // 存储自定义内容
va_list s;
va_start(s, format);
vsnprintf(userbuffer, sizeof(userbuffer), format, s);
va_end(s);
}
将默认内容和自定义内容合并:
void logmessage(int level, const char *format, ...)
{
char defaultbuffer[SIZE]; // 存储默认内容
DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));
char userbuffer[SIZE]; // 存储自定义内容
va_list s;
va_start(s, format);
vsnprintf(userbuffer, sizeof(userbuffer), format, s);
va_end(s);
char logtxt[SIZE*2];
snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);
std::cout << logtxt << std::endl;
}
2.2 打印模式选择
可以通过设置选项,将日志信息打印到:**显示器、一个文件、多个文件(同等级的在一个文件)。**抽象一个 Log
类,在里面设置一个成员变量来选择日志的输出。
模式设置:
void SetStyle(int style)
{
_outputstyle = style;
}
模式选择:
void OutPutLog(int level, const std::string &message)
{
switch (_outputstyle)
{
case Screen: // 向显示器打印
OutPutToScreen(message);
return;
case Onefile: // 向一个文件中打印
OutPutToOnefile(_logpath, message);
return;
case Classfile: // 向多个文件中打印
OutPutToClassfile(level, message);
return;
}
}
向显示器打印:
// 将日志信息打印到屏幕
void OutPutToScreen(const std::string &message)
{
std::cout << message << std::endl;
}
向一个文件中写入:
// 将日志信息保存到一个文件中
void OutPutToOnefile(const std::string &path, const std::string &message)
{
// 打开文件
std::fstream fp;
fp.open(path, std::ios::app);
if (!fp.is_open())
{
std::cout << path << " open faile" << std::endl;
}
// 向文件写入
fp << message << std::endl;
// 关闭文件
fp.close();
}
向多个文件中写入:
// 将日志信息按照等级保存到不同文件中
void OutPutToClassfile(int level, const std::string &message)
{
switch (level)
{
case Info:
OutPutToOnefile(INFO_LOG_PATH, message);
break;
case Debug:
OutPutToOnefile(DEBUG_LOG_PATH, message);
break;
case Warning:
OutPutToOnefile(WARING_LOG_PATH, message);
break;
case Error:
OutPutToOnefile(ERROR_LOG_PATH, message);
break;
case Fatal:
OutPutToOnefile(FATAL_LOG_PATH, message);
break;
default:
break;
}
return;
}
operator() 让调用显得更加优雅:
void operator()(int level, const char *format, ...)
{
char defaultbuffer[SIZE]; // 存储默认内容
DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));
char userbuffer[SIZE]; // 存储自定义内容
va_list s;
va_start(s, format);
vsnprintf(userbuffer, sizeof(userbuffer), format, s);
va_end(s);
char logtxt[SIZE * 2];
snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);
// std::cout << logtxt << std::endl;
OutPutLog(level, logtxt);
}
2.3 Log 使用样例
#include "log.hpp"
#include <stdlib.h>
#include <unistd.h>
int main()
{
Log log;
int cnt = 10;
while (cnt--)
{
if(cnt == 5) log.SetStyle(Classfile);
log(Info, "I am %d %s %f", 2, "wuchengyang", 3.14);
log(Debug, "I am %d %s %f", 3, "wuchengyang", 4.78);
log(Fatal, "I am %d %s %f", 4, "wuchengyang", 5.32);
sleep(1);
}
return 0;
}
2.4 Log 完整源码
#pragma once
#include <stdarg.h>
#include <iostream>
#include <time.h>
#include <fstream>
#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 LOG_PATH "./Log/log.txt"
#define INFO_LOG_PATH "./Log/InfoLog.txt"
#define DEBUG_LOG_PATH "./Log/DebugLog.txt"
#define WARING_LOG_PATH "./Log/WaringLog.txt"
#define ERROR_LOG_PATH "./Log/ErrorLog.txt"
#define FATAL_LOG_PATH "./Log/FatalLog.txt"
class Log
{
public:
Log(const std::string &logpath = LOG_PATH, int style = Onefile)
: _logpath(logpath),
_outputstyle(style)
{
}
private:
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 DefaultMessage(int level, char *defaultbuffer, int size)
{
time_t t = time(nullptr);
struct tm *ctime = localtime(&t);
snprintf(defaultbuffer, size, "[%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);
}
// 将日志信息打印到屏幕
void OutPutToScreen(const std::string &message)
{
std::cout << message << std::endl;
}
// 将日志信息保存到一个文件中
void OutPutToOnefile(const std::string &path, const std::string &message)
{
// 打开文件
std::fstream fp;
fp.open(path, std::ios::app);
if (!fp.is_open())
{
std::cout << path << " open faile" << std::endl;
}
// 向文件写入
fp << message << std::endl;
// 关闭文件
fp.close();
}
// 将日志信息按照等级保存到不同文件中
void OutPutToClassfile(int level, const std::string &message)
{
switch (level)
{
case Info:
OutPutToOnefile(INFO_LOG_PATH, message);
break;
case Debug:
OutPutToOnefile(DEBUG_LOG_PATH, message);
break;
case Warning:
OutPutToOnefile(WARING_LOG_PATH, message);
break;
case Error:
OutPutToOnefile(ERROR_LOG_PATH, message);
break;
case Fatal:
OutPutToOnefile(FATAL_LOG_PATH, message);
break;
default:
break;
}
return;
}
void OutPutLog(int level, const std::string &message)
{
switch (_outputstyle)
{
case Screen: // 向显示器打印
OutPutToScreen(message);
return;
case Onefile: // 向一个文件中打印
OutPutToOnefile(_logpath, message);
return;
case Classfile: // 向多个文件中打印
OutPutToClassfile(level, message);
return;
}
}
public:
void SetStyle(int style)
{
_outputstyle = style;
}
// void logmessage(int level, const char *format, ...)
// {
// char defaultbuffer[SIZE]; // 存储默认内容
// DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));
// char userbuffer[SIZE]; // 存储自定义内容
// va_list s;
// va_start(s, format);
// vsnprintf(userbuffer, sizeof(userbuffer), format, s);
// va_end(s);
// char logtxt[SIZE * 2];
// snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);
// // std::cout << logtxt << std::endl;
// OutPutLog(level, logtxt);
// }
void operator()(int level, const char *format, ...)
{
char defaultbuffer[SIZE]; // 存储默认内容
DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));
char userbuffer[SIZE]; // 存储自定义内容
va_list s;
va_start(s, format);
vsnprintf(userbuffer, sizeof(userbuffer), format, s);
va_end(s);
char logtxt[SIZE * 2];
snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);
// std::cout << logtxt << std::endl;
OutPutLog(level, logtxt);
}
~Log()
{
}
private:
int _outputstyle;
std::string _logpath;
};
三、结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!