目录
3.1 在src下的include增加头文件:logger.h
3.2 在src下的include增加头文件:lockqueue.h
1.为什么要写日志
图中画圆圈的是我们实现的mprpc框架,这个框架是给别人使用的,把本地的服务发布成远程的RPC服务,框架里最重要的两个成员就是RpcProvider和RpcChannel,他们在运行的过程中会有很多正常的输出信息和错误的信息,我们不可能都cout它们到屏幕上,因为运行时间长了,屏幕上输出的信息特别多,如果万一有什么问题,我们也不好定位,真正用起来的话不方便。所以,一般出问题,我们最直接的方式就是看日志!!!
日志可以记录正常软件运行过程中出现的信息和错误的信息,当我们定位问题,就打开相应的日志去查看,查找。
2.实现思想
假如我们在框架后边输出一个日志模块,我们想把框架运行中的正常的信息和错误信息都记录在日志文件,该怎么做?
左边的两个箭头表示RPC的请求和RPC的响应。
RPC请求过来的时候,我们的框架在执行的时候会产生很多日志文件,我们要写日志,写日志信息的过程是磁盘I/O,磁盘I/O速度很慢,我们不能把磁盘I/O的花销算在RPC请求的业务执行部门里面(否则造成RPC请求处理的效率慢),我们不能把日志花费的时间算在框架业务的执行时间里面,所以,一般在我们的服务器中,增加一个kafka,就可以用在日志系统中做一个消息队列中间件,我们把日志写在一个缓存队列里(这个队列相当于异步的日志写入机制),我们的框架做的就是写日志到内存的队列里面,不做磁盘I/O操作。然后我们在后面有一个专门写日志的线程,就是做磁盘I/O操作,从队头取出日志信息,然后把日志信息写到日志文件中,这样它的磁盘I/O的消耗就不会算在我们的RPC请求的业务当中。
我们的mprpc框架的RpcProvier端是用muduo库实现的,采用的是epoll加多线程,很有可能RPC的处理过程是在多个线程中都会去做这个,多个线程都会去写日志,也就是多个线程都会在这个缓存队列里面添加数据,所以我们的这个缓存队列必须保证线程安全。
我们的C++中有queue这个队列,也就是C++的容器,但是C++容器只考虑使用功能,没有考虑线程安全,所以我们用的是线程互斥机制来维护入队出队的线程安全,写日志的线程也是一个独立的线程,用唯一的互斥锁实现,大家去争这把锁,多线程往队列里写日志,争到锁就写;写线程日志,争到锁,从队列里拿信息,写道文件中,如果队列是空的话,也就是之前的日志都写到日志文件了,写日志的线程这时就不用抢这把互斥锁了,因为没有东西可写。导致写入队列的线程无法及时获取锁,信息无法及时写到队列,破坏了RPC请求的业务的效率。
所以,单单使用一把锁可以解决问题,但是效率低,我们还要处理线程间的通信的问题。用到了线程的互斥+线程的通信
队列如果是空,写日志的线程就一直等待,队列不空的时候,写日志的线程才有必要去抢锁,把日志信息写到日志文件。
我们的日志文件放在当前目录下log。
每一天都生成新的日志文件,有助于在发生问题的时候快速定位。
而且如果当天我们的日志文件如果容量太大,比如说超过20M,就会产生新的!
kafka是开源的,非常著名,分布式消息队列,其中功能:在分布式环境中提供异步的日志写入服务器中间件,日志写入的系统,和我们的queue本质相同,但是它设计高级,高效,高可用性,可容灾,稳定。
3.logger日志系统的具体实现
3.1 在src下的include增加头文件:logger.h
#pragma once
#include "lockqueue.h"
#include <string>
//定义宏 LOG_INFO("xxx %d %s", 20, "xxxx")
//可变参,提供给用户更轻松的使用logger
//snprintf, 缓冲区,缓冲区的长度,写的格式化字符串, ##__VA_ARGS__。
//代表了可变参的参数列表,填到缓冲区当中,然后 logger.Log(c)
#define LOG_INFO(logmsgformat, ...)\
do \
{ \
Logger &logger=Logger::GetInstance();\
logger.SetLogLevel(INFO);\
char c[1024]={0};\
snprintf(c,1024,logmsgformat,##__V