文章目录
- 一、可变参数的使用
- 二、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;
};
三、结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!