目录
一、概念
二、可变参数
三、日志体系
一、概念
一个正在运行的程序或体系就像一个哑巴,一旦开始运行我们很难知晓其内部的运行状态。
但有时在程序运行过程中,我们想知道其内部不同时刻的运行效果如何,这时一个日志体系可以有用的资助我们监控程序的运行状态。
如果体系或程序发生了错误或存在bug,通过日志的内容我们也可以很快的知道故障的原因并定位错误的位置
一个成熟的日志至少需要包罗以下信息:
根据环境可将日志划分为不同的等级,比方常规信息、告诫信息、严重错误、致命错误、调试信息
二、可变参数
日志的内容需要我们指定格式并传参,而参数的个数是不确定的。因此在学习编写日志体系之前,我们先了解一下可变参数的用法
以下是对可变参数举行操纵时需要用到的函数/宏
- #include <stdarg.h>
- void va_start(va_list ap, last);
- type va_arg(va_list ap, type);
- void va_end(va_list ap);
复制代码 我们以一个可以同时累加多个变量的函数为例:
形参在实例化时会从右向左举行压栈,也就是说多个参数在函数栈帧中是连续的,因此我们可以通过所在的偏移来依次访问到所有的参数
首先:
- int sum(int n, ...)
- {
- va_list s;
- va_start(s, n);
- }
复制代码 此中va_list现实上就是char*, 而va_start可以让s指向参数n的下一个参数,也就是可变参数的第一个参数的位置。此时我们就有了获取第一个参数内容的前提
这也是为什么printf等支持可变参数的函数中必须至少要有一个确定的参数,有了该参数才气找到可变参数的起始所在
- int sum(int n, ...)
- {
- va_list s;
- va_start(s, n);
- int sum = 0;
- while(n--)
- {
- sum += va_arg(s, int);
- }
- va_end(s);
- return sum;
- }
复制代码 此中,va_arg传入s和可变参数的类型,用于提取s指向的参数,并且移动s到下一个参数的位置
va_end将s置为空
测试效果:
拓展问题:如果可变参数中,不同参数有不同的类型怎么办?
这也是为什么printf的第一个参数需要传入一个用于控制格式的字符串,通过遍历字符串就能知道可变参数中有哪些类型了
三、日志体系
本文实现的日志体系具备以下功能:
- 包罗日志等级、日志时间、日志内容
- 将日志功能封装成类,并重载了函数调用运算符
- 可以选择将日志输出到终端、输出到同一文件或按照日志等级分类输出到不同文件
- 用户可自界说日志内容格式
如果要让日志包罗文件名和行号,则可以通过宏界说__FILE__和__LINE__获取文件名和行号
接下来是完备代码(附解释)
- #pragma once
- #include <iostream>
- #include <time.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdarg.h>
- // 日志等级
- #define Info 0
- #define Debug 1
- #define Warning 2
- #define Error 3
- #define Fatal 4
- #define SIZE 1024 // 缓冲区大小
- // 日志的输出方式
- #define Screen 1 // 输出到显示器
- #define Same_file 2 // 输出到同一文件
- #define Diff_file 3 // 按照等级输出到不同文件
- #define Filename "log.txt"
- class Log
- {
- public:
- Log()
- {
- _method = Screen; // 默认输出到显示器
- }
- void output(int method) // 更改输出方式
- {
- _method = method;
- }
- std::string level2string(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, ...)
- {
- va_list s;
- va_start(s, format); // s指向可变参数
- messagehandle(level, format, s);
- }
- void messagehandle(int level, const char *format, va_list s) // 整合日志字符串
- {
- time_t t = time(nullptr); // 获取时间戳
- struct tm *ctime = localtime(&t); // 将时间戳转换为时间
- char levelAndtime[SIZE]; // 日志等级和时间部分
- snprintf(levelAndtime, sizeof(levelAndtime), "[%s][%d-%d-%d %02d:%02d:%02d]", level2string(level).c_str(),
- ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
- char content[SIZE]; // 用户自定义的内容部分
- vsnprintf(content, sizeof(content), format, s);
- va_end(s);
- char message[SIZE * 2]; // 整合所有部分
- snprintf(message, sizeof(message), "%s %s\n", levelAndtime, content);
- OutputLog(level, message); // 将整合后的日志输出
- }
- void OutputLog(int level, const std::string &logmessage)
- {
- switch (_method) // 根据输出方式进行调整
- {
- case Screen: // 输出到显示器
- std::cout << logmessage << std::endl;
- break;
- case Same_file: // 输出到同一文件
- SamefileOutput(Filename, logmessage);
- break;
- case Diff_file: // 输出到不同文件
- DiffileOutput(level, logmessage);
- break;
- default:
- break;
- }
- }
- void SamefileOutput(const std::string &filename, const std::string &logmessage)
- {
- int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); //打开文件
- if(fd < 0) //打开失败
- return;
- write(fd, logmessage.c_str(), logmessage.size()); //写入日志
- close(fd); //关闭文件描述符
- }
- void DiffileOutput(int level, const std::string &logmessage)
- {
- std::string filename = Filename;
- filename += ".";
- filename += level2string(level); //根据日志等级调整文件名
- SamefileOutput(filename, logmessage); //复用SamefileOutput函数
- }
- ~Log()
- {}
- private:
- int _method; // 输出方式
- };
复制代码 测试:
向显示器输出日志(n%5用于模拟不同日志等级)
向同一文件中输出日志
向不同文件中输出日志
完.
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |