怎么替换网站模板,apache建设本地网站,软件代理销售公司,郑州网站建设 易云互联一、日志项目的介绍
1.1 为什么要有日志系统
1、⽣产环境的产品为了保证其稳定性及安全性是不允许开发⼈员附加调试器去排查问题#xff0c;可以借助日志系统来打印⼀些⽇志帮助开发⼈员解决问题 为什么不直接printf打印在屏幕上呢#xff1f;#xff1f;因为现实中没有…
一、日志项目的介绍
1.1 为什么要有日志系统
1、⽣产环境的产品为了保证其稳定性及安全性是不允许开发⼈员附加调试器去排查问题可以借助日志系统来打印⼀些⽇志帮助开发⼈员解决问题 为什么不直接printf打印在屏幕上呢因为现实中没有谁能够一整天都盯着机房看系统运行信息而且刷新可能很快根本看不过来
2、上线客户端的产品出现bug⽆法复现并解决可以借助⽇志系统打印日志并上传到服务端帮助开发人员进行分析 为什么不允许使用调试器呢因为使用gdb需要对程序的数据进行分析有些时候是不被允许的一些工程内的数据都是有隐私有安全要求的
3、对于⼀些高频操作如定时器、心跳包在少量调试次数下可能无法触发我们想要的行为通过断点的暂停⽅式我们不得不重复操作几十次、上百次甚至更多导致排查问题效率是非常低下可以借助打印日志的方式查问题 4、在分布式、多线程/多进程代码中出现bug比较难以定位可以借助日志系统打印log帮助定位bug 日志可以快读定位bug出现在哪一个模块从而帮助程序员进行更好的分析
5、帮助首次接触项目代码的新开发人员理解代码的运行流程
1.2 日志系统的作用简述
日志程序运行过程中所记录的程序运行状态时间、错误原因、行号……
作用记录了程序运行状态信息以便于程序员能够随时根据状态信息对系统的运行状态进行分析
一般来说日志都是作为一个小组件给其他业务打辅助用的所以我们为了确保他能够更高效地开发除了他能够正常使用之外必要时候也要去阐述他的具体性能 1.3 需要实现的功能
1、⽀持多级别日志消息 区分各种信息的程度比如调试、警告、致命……。同时要让程序在发布的时候不要输出调试的信息而是只输出那些让我们程序出错的信息设置输出限制比如未发布的时候设置为调试级别发布时设为错误级别即低于错误的都不输出
2、⽀持同步日志和异步日志 同步就是将业务数据写入到数据库的操作由我的业务线程自己完成而异步是我将数据放到内存里面而写入的操作由一些专门负责工作的线程负责因为如果都由我负责那么万一写入有问题就会导致业务也做不了了
3、支持可靠写入日志到控制台、文件以及滚动文件中 日志信息可以写到控制台、写到文件、滚动切换文件 也可以三个同时进行 想怎么输出由自己决定 滚动文件就是我们如果一直往一个文件里写入那么可能就会很大所以当一个文件写到一定程度可以按照文件大小也可以按照日期来切换然后设置一个定时任务每天清理3天以前的日志只保留3天以内的日志文件的时候切换下一个文件来进行写入
4、支持多线程程序并发写日志 保证线程安全
5、⽀持扩展不同的⽇志落地⽬标地 同时也支持扩展比方说支持把日志信息写到数据库里或者写到一个日志分析服务器里
1.4 开发环境核心技术环境搭建
开发环境用到哪些软件
• CentOS7
• vscode/vim
• g/gdb
• Makefile
核心技术会用到哪些前置知识
• 类层次设计(继承和多态的应⽤)
• C11(多线程、auto、智能指针、右值引⽤等)
• 双缓冲区
• ⽣产消费模型
• 多线程
• 设计模式(单例、工厂、代理、建造者等) 环境搭建会用到哪个第三方库 本项⽬不依赖其他任何第三⽅库只需要安装好CentOS/Ubuntuvscode/vim环境即可开发。
1.5 日志系统的技术实现
⽇志系统的技术实现主要包括三种类型:
1、利⽤printf、std::cout等输出函数将⽇志信息打印到控制台
2、对于⼤型商业化项⽬为了⽅便排查问题我们⼀般会将⽇志输出到⽂件或者是数据库系统⽅便查询和分析⽇志主要分为同步⽇志和异步⽇志⽅式
1.5.1 同步写日志 同步⽇志是指当输出⽇志时必须等待⽇志输出语句执⾏完毕后才能执⾏后⾯的业务逻辑语句日志输出语句与程序的业务逻辑语句将在同⼀个线程运行。每次调⽤⼀次打印⽇志API就对应⼀次系统调⽤write写⽇志⽂件。 在高并发场景下随着日志数量不断增加同步日志系统容易产生系统瓶颈
• ⼀⽅⾯⼤量的⽇志打印陷⼊等量的write系统调⽤有⼀定系统开销.
• 另⼀⽅⾯使得打印⽇志的进程附带了⼤量同步的磁盘IO影响程序性能
1.5.2 异步写日志 异步⽇志是指在进⾏⽇志输出时日志输出语句与业务逻辑语句并不是在同⼀个线程中运行而是有专门的线程用于进行日志输出操作。业务线程只需要将⽇志放到⼀个内存缓冲区中不⽤等待即可继续执⾏后续业务逻辑作为⽇志的⽣产者⽽⽇志的落地操作交给单独的⽇志线程去完成作为⽇志的消费者,这是⼀个典型的⽣产-消费模型。 这样做的好处是即使日志没有真的地完成输出也不会影响程序的主业务可以提⾼程序的性能
• 主线程调⽤日志打印接⼝成为非阻塞操作
• 同步的磁盘IO从主线程中剥离出来交给单独的线程完成
二、不定参函数 在初学C语⾔的时候我们都⽤过printf函数进⾏打印。其中printf函数就是⼀个不定参函数在函数内部可以根据格式化字符串中格式化字符分别获取不同的参数进⾏数据的格式化。⽽这种不定参函数在实际的使⽤中也⾮常多⻅
2.1 不定参宏函数 我们起初的风格可以这样写但是这样写的话我们每次写一些固定的变量的时候都需重复书写比如 __FILE__和__LINE__
#includeiostream
using namespace std;
int main()
{printf([%s:%d] %s-%d\n, __FILE__, __LINE__, hello,666);return 0;
} 所以我们希望 __FILE__和__LINE__这俩无论任何一条日志都要打印的信息给他设置到宏函数里面
1、通过把宏参数列表中最后的参数写成省略号...使其可以接受数量可变的宏参数。
2、后边用不定参数宏__VA_ARGS__可以自动扩展
按理来说一定要带参数否则__VA_ARGS__无法扩展最后替换时会留一个‘’导致错误但是我们可以用##来避免这个问题
3、## __VA_ARGS__是为了确保当没有传入任何参数的时候把最后面的‘’给去掉
##告诉编译器。如果我没有传任何参数给__VA_ARGS__那么就把前面的‘’去掉
#includeiostream
#define LOG(fmt, ...) printf([%s:%d] fmt \n, __FILE__, __LINE__, ##__VA_ARGS__)int main()
{//printf([%s:%d] %s-%d\n, __FILE__, __LINE__, hello,666);LOG(%s-%d, hello, 666);return 0;
}
运行结果 使用了宏定义来实现日志功能虽然这种方式可以达到目的但宏定义在C中不够类型安全并且调试时不如函数调用方便。
2.2 C风格不定参函数 头文件strarg.h中定义了一组对象、方法使得我们可以使用不定参数。
va_list ap用于储存省略部分数据的对象类型va_start(format, ap)使得ap指向format后的不定参数列表即不定参数列表中的第一个参数int tmp va_arg(ap, int)将当前ap指向的值返回并使ap指针按照type类型向后移动va_arg中第二个参数类型名要与返回值类型相同(决定了向后移动几个字节)va_end(ap)完成清理工作释放动态分配申请的用于存储参数的内存
#include iostream
#include cstdarg
void printNum(int n, ...) {va_list al;va_start(al, n);//让al指向n参数之后的第⼀个可变参数 for (int i 0; i n; i) {int num va_arg(al, int);//从可变参数中取出⼀个整形参数,然后向后移动int个字节std::cout num std::endl;}va_end(al);//清空可变参数列表--其实是将al置空
}
int main()
{printNum(3, 11,22,33);printNum(5, 44,55,66,77,88);return 0;
} 注意虽然是不定参但是也要遵守规定比如第一个数字就是专门用来确定后面有多少个参数的如果乱写的话可能导致未定义的行为因为va_arg会尝试读取超出传入参数数量的内存。 如果我们的va_argc传的类型不匹配呢那这必然导致我们读到的数据是错的 所以这也是为什么printf有格式化字符串就是为了告诉编译器接下来要从后面读几个字节的数据应该当做什么类型去做处理 vasprintf 是一个C库函数它允许通过可变参数列表创建格式化字符串并将其存储在动态分配的内存中。这个函数的行为类似于printf但它不会将结果输出到标准输出而是将格式化后的字符串存储在一个字符指针变量中。 char**strp一级指针的地址会在动态分配的内存中给我们的格式化字符串分配足够的空间
const char*fmt带格式化的字符串
va_list ap:从ap里面一个个取参数进行解析然后将组织好的字符串放到我们预先申请的空间里 注意因为这个空间是由OS帮我们申请的所以最后我们一定要记得free 成功的话会返回对应的字节数失败的话会返回-1
#define _GNU_SOURCE
#includeiostream
#includecstdarg
#includestdlib.h
void myprintf(const char* fmt, ...)
{va_list ap;va_start(ap, fmt);char*res; //不需要我们自己申请int ret vasprintf(res,fmt,ap);if(ret!-1){printf(res);free(res);}va_end(ap);
}
int main()
{myprintf(hello %s, world);return 0;
} 2.3 C风格不定参函数 cpp使用的是模版参数包 sizeof …args是固定写法
因为递归到最后并没有空函数的xprintf因此我们还得定义一个没有函数的xprintf
#include iostream
#include cstdarg
#include memory
#include functional
void xprintf() {std::cout std::endl;
}
templatetypename T, typename ...Args
void xprintf(const T value, Args ...args) {std::cout value ;if ((sizeof ...(args)) 0) {xprintf(std::forwardArgs(args)...);}else {xprintf();}
}
int main()
{xprintf(hello);xprintf(hello, 666);xprintf(hello, world, 666);return 0;
}