日志系统

阻塞队列部分借用该老兄的。博主原创文章链接:https://blog.csdn.net/weixin_50437588/article/details/128434180

本文开发了一款跨平台的C++日志系统。

/*
重点实现一个线程安全的缓冲队列:

1、首先使用STL库中的deque作为基本容器;
2、然后定义一个固定大小的容量capacity,防止无限制扩容下去导致内存耗尽;
3、之后需要两个条件变量Productor和Consumer,模拟生产者和消费者;
4、条件变量必须得有互斥量辅助,所以还需一个互斥量mutex。
*/
//BlockQueue.h
#ifndef BLOCKQUEUE_H
#define BLOCKQUEUE_H

#include <mutex>
#include <deque>
#include <condition_variable>
#include <assert.h>
#include <chrono>

template<class T>
class BlockQueue {
public:
	explicit BlockQueue(unsigned int maxCapacity = 1024);
	~BlockQueue();
	void close();							// 关闭队列
	void flush();							// 刷新队列,通知取队列的线程 
	void clear();							// 清空队列 
	T front();								// 模拟队列的front函数  ,返回队尾元素
	T back();								// 模拟队列的back函数 ,返回队首元素
	unsigned int get_size();				// 获取队列 当前有多少元素
	unsigned int get_capacity();			// 获取队列容量  
	void push_back(const T& item);			// 队尾插入数据 
	void push_front(const T& item);			// 队首插入数据  
	bool empty();							// 判断队列为空 
	bool full();							// 判断队列为满 
	bool pop(T& item);						// 删除队首元素并存入 item中
	bool pop(T& item, int timeout);			// 删除队首元素并存入 item中,最多等待timeout毫秒

private:
	std::deque<T> deq;
	unsigned int capacity;
	std::mutex mtx;
	bool isClose;
	std::condition_variable Consumer;
	std::condition_variable Producer;
};


template<class T>
BlockQueue<T>::BlockQueue(unsigned int maxCapacity) :capacity(maxCapacity){
	assert(maxCapacity > 0);
	isClose = false;
}

template<class T>
BlockQueue<T>::~BlockQueue(){
	close();
};

template<class T>
void BlockQueue<T>::close(){
	{
		std::lock_guard<std::mutex> locker(mtx);
		deq.clear();
		isClose = true;
	}
	Producer.notify_all();
	Consumer.notify_all();
};

template<class T>
void BlockQueue<T>::flush(){
	Consumer.notify_one();
}

template<class T>
void BlockQueue<T>::clear(){
	std::lock_guard<std::mutex> lock(mtx);
	deq.clear();
}

template<class T>
T BlockQueue<T>::front(){
	std::lock_guard<std::mutex> lock(mtx);
	return deq.front();
}

template<class T>
T BlockQueue<T>::back(){
	std::lock_guard<std::mutex> lock(mtx);
	return deq.back();
}

template<class T>
unsigned int BlockQueue<T>::get_size(){
	std::lock_guard<std::mutex> lock(mtx);
	return deq.size();
}

template<class T>
unsigned int BlockQueue<T>::get_capacity(){
	std::lock_guard<std::mutex> lock(mtx);
	return capacity;
}

template<class T>
void BlockQueue<T>::push_back(const T& item){
	//使用条件变量前应先对互斥量上锁,此时选择unique_lock而非lock_guard
	//因为unique_lock在上锁后允许临时解锁再加锁,而lock_guard上锁后只能在离开作用域时解锁
	std::unique_lock<std::mutex> lock(mtx);
	while (deq.size() >= capacity)	{
		//若队列已满,则生产者线程进入阻塞状态,自动对互斥量解锁,被唤醒后又自动对互斥量加锁
		Producer.wait(lock);
	}
	deq.push_back(item);		//向队列中放入数据
	Consumer.notify_one();		//唤醒一个阻塞的消费者线程
}

template<class T>
void BlockQueue<T>::push_front(const T& item){
	std::unique_lock<std::mutex> lock(mtx);
	while (deq.size() >= capacity){
		Producer.wait(lock);
	}
	deq.push_front(item);
	Consumer.notify_one();
}

template<class T>
bool BlockQueue<T>::empty(){
	std::lock_guard<std::mutex> lock(mtx);
	return deq.empty();
}

template<class T>
bool BlockQueue<T>::full(){
	std::lock_guard<std::mutex> lock(mtx);
	return deq.size() >= capacity;
}

template<class T>
bool BlockQueue<T>::pop(T& item){
	std::unique_lock<std::mutex> lock(mtx);
	while (deq.empty()){
		//若队列空,则消费者线程进入阻塞,等待被唤醒
		Consumer.wait(lock);
		if (isClose)
			return false;
	}
	item = deq.front();		//当作删除数据的拷贝,把头部数据放到item里面
	deq.pop_front();		//删除队列头部数据
	Producer.notify_one();	//唤醒一个阻塞的生产者线程
	return true;
}

//设计为带计时器的pop函数。若队列在超时时间后仍一直为空,则立刻返回false。  单位为毫秒
template<class T>
bool BlockQueue<T>::pop(T& item, int timeout){
	std::unique_lock<std::mutex> lock(mtx);
	while (deq.empty()){
		//阻塞时间超过指定的timeout,直接返回false
		if (Consumer.wait_for(lock, std::chrono::microseconds(timeout)) == std::cv_status::timeout)
			return false;
		if (isClose)
			return false;
	}
	item = deq.front();
	deq.pop_front();
	Producer.notify_one();
	return true;
}

#endif // BLOCKQUEUE_H

//Log.h
#ifndef LOG_H
#define LOG_H

#include <stdio.h>
#include <iostream>
#include <string>
#include <stdarg.h>
#include <thread>
#include <fstream>
#include <sstream>
#include "BlockQueue.h"

using namespace std;

class Log{
public:
	//C++11以后,使用局部变量懒汉不用加锁
	static Log *getInstance(){
		static Log instance;
		return &instance;
	}

	static void *flush_log_thread(){
		Log::getInstance()->async_write_log();
		return nullptr;
	}

	//可选择的参数有日志文件、日志缓冲区大小、最大行数以及最长日志条队列
	bool init(const string& file_name, int close_log, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0);

	void write_log(int level, const char *format, ...);

	void flush(void);
	int get_close_log(){
		return m_close_log;
	}
	void set_close_log(int close_log){
		m_close_log = close_log;
	}

private:
	Log();				//私有化构造函数
	virtual ~Log();

	void *async_write_log(){
		string single_log;
		//从阻塞队列中取出一个日志string,写入文件
		while (m_log_queue->pop(single_log)){			//这个函数如果队列为空会一直阻塞在这。
			m_mutex.lock();
			//fputs(single_log.c_str(), m_fp); // 虽然 gets()、fgets()、puts()、fputs() 都是字符串处理函数,但它们都包含在 stdio.h
			m_file << single_log << endl;
			m_mutex.unlock();
		}
		return nullptr;
	}

private:
	string m_dir_name;					//路径名
	string m_log_name;					//log文件名

	int m_split_lines;					//日志最大行数
	char *m_buf;
	int m_log_buf_size;					//日志缓冲区大小
	long long m_count;					//日志行数记录
	int m_today;						//因为按天分类,记录当前时间是那一天
	//FILE *m_fp;						//打开log的文件指针  FILE 是C语言的stdio.h里面预定义的一个结构体,是管理文件流的一种结构。
	
	ofstream m_file;					//打开log的文件指针
	BlockQueue<string> *m_log_queue;	//阻塞队列
	bool m_is_async;					//是否同步标志位
	std::mutex m_mutex;
	int m_close_log;					//关闭日志 1表示关闭,0表示打开
};

#define LOG_DEBUG(format, ...) if(0 == m_close_log) {Log::getInstance()->write_log(0, format, ##__VA_ARGS__); Log::getInstance()->flush();}
#define LOG_INFO(format, ...) if(0 == m_close_log) {Log::getInstance()->write_log(1, format, ##__VA_ARGS__); Log::getInstance()->flush();}
#define LOG_WARN(format, ...) if(0 == m_close_log) {Log::getInstance()->write_log(2, format, ##__VA_ARGS__); Log::getInstance()->flush();}
#define LOG_ERROR(format, ...) if(0 == m_close_log) {Log::getInstance()->write_log(3, format, ##__VA_ARGS__); Log::getInstance()->flush();}
#endif

// Log.cpp
#include <string>
#include <cstring>
#include <time.h>
#include <ctime>
#include <stdio.h>
#include <stdarg.h>
#include <chrono>
#include "Log.h"

using namespace std;
using namespace std::chrono;

start 在VS中定义一下这个,否者会报错
//#if _MSC_VER
//#define snprintf _snprintf
//#endif 
end

Log::Log(){
	m_count = 0;
	m_is_async = false;
}

Log::~Log(){
	if (m_file.is_open()){
		m_file.flush();
		m_file.clear();
		m_file.close();
	}
}

//异步需要设置阻塞队列的长度,同步不需要设置
bool Log::init(const string& file_name, int close_log, int log_buf_size, int split_lines, int max_queue_size){
	//如果设置了max_queue_size,则设置为异步
	if (max_queue_size >= 1){
		m_is_async = true;
		m_log_queue = new BlockQueue<string>(max_queue_size);
		//pthread_t tid;
		//flush_log_thread为回调函数, 这里表示创建线程异步写日志
		//pthread_create(&tid, NULL, flush_log_thread, NULL);

		std::thread t(flush_log_thread);
		t.detach();
	}

	m_close_log = close_log;
	m_log_buf_size = log_buf_size;
	m_buf = new char[m_log_buf_size];
	memset(m_buf, '\0', m_log_buf_size);
	m_split_lines = split_lines;
	stringstream stream1;

	time_t t = time(NULL);					//获取1970年1月1日0点0分0秒到现在经过的秒数
	struct tm *sys_tm = localtime(&t);		//将秒数转换为本地时间,年从1900算起,需要+1900,月为0-11,所以要+1
	struct tm my_tm = *sys_tm;				//复制一个对象
	char temp[32];
	sprintf(temp, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday);
	m_today = my_tm.tm_mday;

	int index = file_name.rfind('/', file_name.size());
	//cout << " index = " << index << endl;
	if (index == -1){		//未找到
		stream1 << temp << file_name;
	}
	else{					//找到了
		string m_dir_name = file_name.substr(0, index + 1);
		string m_log_name = file_name.substr(index + 1, file_name.size() - index - 1);
		//cout << " dir_name = " << dir_name << endl;
		//cout << " log_name = " << log_name << endl;
		stream1 << m_dir_name << temp << m_log_name;
	}
	string log_full_name;
	stream1 >> log_full_name;
	//cout << log_full_name.c_str() << endl;
	m_file.open(log_full_name, ios::app);		//以追加模式打开文件
	if (!m_file.is_open()){
		return false;
	}
	return true;
}

void Log::write_log(int level, const char *format, ...){
	system_clock::time_point time_point_now = system_clock::now();									// 获取当前时间点
	system_clock::duration duration_since_epoch = time_point_now.time_since_epoch();				// 从1970-01-01 00:00:00到当前时间点的时长
	time_t microseconds_since_epoch = duration_cast<microseconds>(duration_since_epoch).count();	// 将时长转换为微秒数
	time_t seconds_since_epoch = microseconds_since_epoch / 1000000;								// 将时长转换为秒数

	std::tm current_time = *std::localtime(&seconds_since_epoch);									// 获取当前时间(精确到秒)
	time_t tm_microsec = microseconds_since_epoch % 1000;											// 当前时间的微妙数
	time_t tm_millisec = microseconds_since_epoch / 1000 % 1000;									// 当前时间的毫秒数
	char temp[36]{ 0 };
	sprintf(temp, "%04d-%02d-%02d %02d:%02d:%02d.%03d%03d", current_time.tm_year + 1900, current_time.tm_mon + 1, current_time.tm_mday,
		current_time.tm_hour, current_time.tm_min, current_time.tm_sec, static_cast<int>(tm_millisec), static_cast<int>(tm_microsec));
	std::string ExactTime(temp);
	//cout << ExactTime << endl;

	string s;
	switch (level)
	{
		case 0:
			s = "[debug]:";
			break;
		case 1:
			s = "[info]: ";
			break;
		case 2:
			s = "[warn]: ";
			break;
		case 3:
			s = "[erro]: ";
			break;
		default:
			s = "[info]: ";
			break;
	}
	m_mutex.lock();
	m_count++;
	
	if (m_today != current_time.tm_mday || m_count % m_split_lines == 0) //everyday log
	{
		string new_log;
		m_file.clear();
		m_file.close();

		char tail[16] = { 0 };
		sprintf(tail, "%d_%02d_%02d_", current_time.tm_year + 1900, current_time.tm_mon + 1, current_time.tm_mday);
	
		if (m_today != current_time.tm_mday){
			new_log = m_dir_name + std::string(tail) + m_log_name;
			m_today = current_time.tm_mday;
			m_count = 0;
		}
		else{
			char temp[16] = { 0 };
			sprintf(temp, ".%lld", m_count / m_split_lines);
			new_log = m_dir_name + std::string(tail) + m_log_name + std::string(temp);
		}
		m_file.open(new_log, ios::app);		//以追加模式打开文件
	}
	// ----------------------------------------------------------------------------------------------------
	m_mutex.unlock();
	
	va_list valst;
	va_start(valst, format);
	
	string log_str;
	m_mutex.lock();
	
	//写入的具体时间内容格式
	log_str += ExactTime + " " + s;

	//int n = sprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ",
	//	my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday,
	//	my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s);
	
	int m = vsnprintf(m_buf, m_log_buf_size - 1, format, valst);
	m_buf[m] = '\0';
	//m_buf[m + 1] = '\0';
	log_str += m_buf;
	
	m_mutex.unlock();
	
	if (m_is_async && !m_log_queue->full()){
		m_log_queue->push_back(log_str);
	}
	else{
		m_mutex.lock();
		m_file << log_str << endl;
		//fputs(log_str.c_str(), m_fp);
		m_mutex.unlock();
	}
	
	va_end(valst);
}

void Log::flush(void)
{
	m_mutex.lock();
	//强制刷新写入流缓冲区
	m_file.flush();
	//m_file.clear();
	m_mutex.unlock();
}


test.cpp
#include <iostream>
#include <stdio.h>
#include <string>
#include "Log.h"
#include "BlockQueue.h"
#include <fstream>
#include <sstream>
#include <time.h>
#include <stdio.h>
#include <stdarg.h>
#include <chrono>
#include <ctime>

using namespace std;
using namespace std::chrono;


void threadFun(int m_close_log){
	for (int j = 20000; j <= 35000; j++){									//60000条log
		LOG_DEBUG("%s 111111111 %d ============= ", "main", j);
		LOG_INFO("%s 111111111 %d ============= ", "main", j);
		LOG_WARN("%s 111111111 %d ============= ", "main", j);
		LOG_ERROR("%s 111111111 %d ============= ", "main", j);
	}
}


int main(){
	cout << "hello world" << endl;
	int m_close_log = 0;
	int cnt = 0;

	Log::getInstance()->init("./ServerLog.log", m_close_log, 2000, 800000, 1000);
	thread yy(threadFun, 0);
	// yy.detach();

	for (int j = 0; j <= 15000; j++){									//60000条log
		LOG_DEBUG("%s 111111111 %d ============= ", "main", j);
		LOG_INFO("%s 111111111 %d ============= ", "main", j);
		LOG_WARN("%s 111111111 %d ============= ", "main", j);
		LOG_ERROR("%s 111111111 %d ============= ", "main", j);
	}
	yy.join();

	//while (1){};

	system("pause");
	return 0;
}



g++ test.cpp Log.h Log.cpp BlockQueue.h -o app -std=c17 -lpthread

在VS中也可以直接编译

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值