C++实现异步日志

项目中基本都需要日志系统,通过日志系统来查询问题,目前也有很多成熟的日志库,比如log4cpp,glog等等,这里想自己动手实现一个c++的日志库。

日志级别

日志系统中最基础的是日志级别,不同级别的日志用来表示不同的处理方式,这里设计4个不同的日志级别,通过枚举来表示不同的日志级别。

typedef enum LogLevel
{
	INFO = 0,
	DEBUG,
	ERROR,
	FATAL
}LogLevel;//日志等级

任务结构

我们需要确定日志输出的任务结构内容,根据实际需求来确定输出内容,正常情况下日志输出的一条内容包含时间,任务等级,文件名,行数,函数,内容

时间:表示输出内容的时间节点

任务等级:用于表示任务的输出级别

文件名:输出内容在哪个文件中,方便定位

行数:输出内容在文件的哪一行,进一步定位

函数:输出内容是由哪个函数进行输出的,定位到具体函数

内容:输出具体内容

比如:

2024-3-1 15:34:44:442|INFO|C:\Users\xwh87\Desktop\AsyncLog\AsyncLog\AsyncLogger.hpp:175|[AsyncLogger::write]0

 具体的任务结构如下:

typedef struct LogRecord
{
	LogLevel level; //日志等级
	std::string current_time;//当前时间
	std::string file_name;//文件名
	int line;//行数,文件的第几行
	std::string func_name;//函数名
	std::string content;//输出内容
}LogRecord;//日志输出内容

日志设计

1.专门启动一个线程用于维护日志内容,所有的任务塞入专门的队列,线程从队列中去读取,如果队列不为空的时候,则将内容从队列中取出来,然后写入文件。如果队列为空的时候,则等待。

2.维护一个文件,对文件进行写操作,这里不需要对文件进行读操作,c++中对文件进行写操作可以使用std::ofstream

3.前面定义的日志等级用枚举进行表示,希望输入到文件中的时候以字符串的形式进行输出,方便查看,比如LogLevel::INFO,输出到文件中是INFO,而不是数字1,字符串INFO更加直观,这里通过std::map映射,使其以字符串的形式输出到文件

4.C++获取当前时间,可以使用时间库std::chrono,c++中貌似没有像QT那样QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");直接获取当前时间并转换成字符串形式输出,需要自己做处理

class AsyncLog
{
public:
	AsyncLog():m_stop(false)
	{
		m_map[LogLevel::INFO] = "INFO";
		m_map[LogLevel::DEBUG] = "DEBUG";
		m_map[LogLevel::ERROR] = "ERROR";
		m_map[LogLevel::FATAL] = "FATAL";
		m_thread = std::thread(&AsyncLog::LogThread, this);
	}
	~AsyncLog()
	{
		m_stop.store(true);
		m_cv.notify_one();
		if (m_thread.joinable())
			m_thread.join();
	}
    //open打开文件,提供两种方式
	bool open(std::string &filepath)
	{
		m_stream.open(filepath);
		return m_stream.is_open();
	}

	bool open(const char* filepath)
	{
		m_stream.open(filepath,std::ios_base::out);
		return m_stream.is_open();
	}
    //将日志记录内容塞入队列中,当队列中有数据的时候,唤醒一个线程
	void push(LogRecord &record)
	{
		std::unique_lock<std::mutex> lk(m_mutex);
		m_queue.push(record);
		lk.unlock();
		m_cv.notify_one();
	}
private:
    //线程执行的函数
    //会实时去获取队列中是否有数据,如果有数据的话,就取出来,然后将里面的内容转换成字符串输出
	void LogThread()
	{
		while (!m_stop)
		{
			std::unique_lock<std::mutex> lk(m_mutex);
			m_cv.wait(lk, [&]() 
				{
					return !m_queue.empty() || m_stop.load();
				});
			while (!m_queue.empty())
			{
                //从队列中取出数据
				LogRecord record = m_queue.front();
				m_queue.pop();
                //std::ostringstream是字符集操作模板类,用于执行串行输出
				std::ostringstream os;
				os << record.current_time 
					<< "|" 
					<< LevelToStr(record.level) 
					<< "|"
					<< record.file_name
					<<":"
					<< record.line
					<< "|"<<"["
					<<record.func_name
					<<"]"
					<<record.content << std::endl;
                //先将os的内容转成str的形式在转成c语言格式char*写入文件
				m_stream.write(os.str().c_str(),strlen(os.str().c_str()));
			}

		}
	}
    //将任务等级转换成字符串形式进行输出
	std::string LevelToStr(LogLevel level) const
	{
		return m_map.at(level);
	}
private:
	std::thread m_thread; //日志线程
	std::atomic_bool m_stop; //线程是否启动
	std::mutex m_mutex;//互斥锁,跟条件变量配合使用
	std::condition_variable m_cv;//条件变量,用于唤醒会通知队列
	std::queue<LogRecord> m_queue;//任务队列
	std::ofstream m_stream;//用于文件的写操作,文件读操作std::ifstream
	std::map<LogLevel, std::string> m_map;//用于映射日志等级
};

日志调用 

上述的日志设计完成之后,最后在设计一个专门调用的日志类,在往日志中写数据的时候,每次都是往一个文件中写,因为将该类设计为一个单例模式,确保只能有一个实例对象调用,外部在调用的时候需要写明日志等级,文件名,行数,函数等内容信息。

class AsyncLogger
{
public:
	AsyncLogger(const AsyncLogger&) = delete;
	AsyncLogger& operator = (const AsyncLogger&) = delete;
	static AsyncLogger& GetInstance()
	{
		static AsyncLogger instance;
		return instance;
	}
	bool open(std::string &filepath)
	{
		return m_async_log.open(filepath);
	}
	bool open(const char* filepath)
	{
		return m_async_log.open(filepath);
	}
    //往日志中写内容
	void write(LogLevel level,std::string file_name,int line,std::string func_name ,std::string content)
	{
        //获取系统时间
		auto now = std::chrono::system_clock::now();
		auto now_time_t = std::chrono::system_clock::to_time_t(now);
		auto local_time =  std::localtime(&now_time_t);
        //获取毫秒,如果不需要获取到毫秒级别,可以不用
		auto duration_since_epoch = std::chrono::system_clock::now().time_since_epoch();
		auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration_since_epoch).count() % 1000;
		std::ostringstream time;
		time << 1900 + local_time->tm_year << '-'
			<< 1 + local_time->tm_mon << '-'
			<< local_time->tm_mday << ' '
			<< local_time->tm_hour << ':'
			<< local_time->tm_min << ':'
			<< local_time->tm_sec << ':'
			<< millis;
        //日志记录中的内容
		LogRecord record;
		record.level = level;  //日志等级
		record.content = content;//日志输出的内容
		record.current_time = time.str();//当前时间
		record.func_name = func_name;//函数名
		record.line = line;//行号
		record.file_name = file_name;//文件名
		m_async_log.push(record);//将日志记录的内容塞入队列
	}
private:
	AsyncLogger() = default;
private:
	AsyncLog m_async_log;
	
};

 上述代码我在VS2019Z中编译的时候,报错,提示localtime这个函数不是一个线程安全的函数,这里我从网上查找的解决办法是在文件中添加#pragma warning(disable:4996)就编译通过了

我们在调用日志写内容的时候,可以这样:

//在当前文件夹打开1.log这个文件,如果没有则创建这个文件
AsyncLogger::GetInstance().open("1.log");
//往日志中写入内容为log test,级别是INFO
//__FILE__获取文件名
//__LINE__获取行号
//__FUNCTION__获取函数名
AsyncLogger::GetInstance().write(LogLevel::INFO,__FILE__,__LINE__,__FUNCTION__,"log test");

这样调用的时候非常麻烦,每次都需要写很多参数,因此调用写日志的write函数用宏稍微封装一下,这样就不用每次去写函数名,行号等信息,只需要写输出内容即可。

即LOGINFO("log test");这样调用就简单很多

#ifndef LOGINFO
#define LOGINFO(Format) \
	AsyncLogger::GetInstance().write(LogLevel::INFO,__FILE__,__LINE__,__FUNCTION__,Format);
#endif

#ifndef LOGDEBUG
#define LOGDEBUG(Format) \
	AsyncLogger::GetInstance().write(LogLevel::DEBUG,__FILE__,__LINE__,__FUNCTION__,Format);
#endif

#ifndef LOGERROR
#define LOGERROR(Format) \
	AsyncLogger::GetInstance().write(LogLevel::ERROR,__FILE__,__LINE__,__FUNCTION__,Format);
#endif

#ifndef LOGFATAL
#define LOGFATAL(Format) \
	AsyncLogger::GetInstance().write(LogLevel::FATAL,__FILE__,__LINE__,__FUNCTION__,Format);
#endif

完整代码

#pragma once
#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <fstream>
#include <atomic>
#include <sstream>
#include <map>
#include <chrono>
#include <ctime>

#pragma warning(disable:4996)

#ifndef LOGINFO
#define LOGINFO(Format) \
	AsyncLogger::GetInstance().write(LogLevel::INFO,__FILE__,__LINE__,__FUNCTION__,Format);
#endif

#ifndef LOGDEBUG
#define LOGDEBUG(Format) \
	AsyncLogger::GetInstance().write(LogLevel::DEBUG,__FILE__,__LINE__,__FUNCTION__,Format);
#endif

#ifndef LOGERROR
#define LOGERROR(Format) \
	AsyncLogger::GetInstance().write(LogLevel::ERROR,__FILE__,__LINE__,__FUNCTION__,Format);
#endif

#ifndef LOGFATAL
#define LOGFATAL(Format) \
	AsyncLogger::GetInstance().write(LogLevel::FATAL,__FILE__,__LINE__,__FUNCTION__,Format);
#endif

typedef enum LogLevel
{
	INFO = 0,
	DEBUG,
	ERROR,
	FATAL
}LogLevel; //日志等级

typedef struct LogRecord
{
	LogLevel level; //日志等级
	std::string current_time;//当前时间
	std::string file_name;//文件名
	int line;//行数,文件的第几行
	std::string func_name;//函数名
	std::string content;//输出内容
}LogRecord;//日志输出内容

class AsyncLog
{
public:
	AsyncLog():m_stop(false)
	{
		m_map[LogLevel::INFO] = "INFO";
		m_map[LogLevel::DEBUG] = "DEBUG";
		m_map[LogLevel::ERROR] = "ERROR";
		m_map[LogLevel::FATAL] = "FATAL";
		m_thread = std::thread(&AsyncLog::LogThread, this);
	}
	~AsyncLog()
	{
		m_stop.store(true);
		m_cv.notify_one();
		if (m_thread.joinable())
			m_thread.join();
	}
	bool open(std::string &filepath)
	{
		m_stream.open(filepath);
		return m_stream.is_open();
	}

	bool open(const char* filepath)
	{
		m_stream.open(filepath,std::ios_base::out);
		return m_stream.is_open();
	}

	void close()
	{
		m_stream.close();
	}

	void push(LogRecord &record)
	{
		std::unique_lock<std::mutex> lk(m_mutex);
		m_queue.push(record);
		lk.unlock();
		m_cv.notify_one();
	}
private:
	void LogThread()
	{
		while (!m_stop)
		{
			std::unique_lock<std::mutex> lk(m_mutex);
			m_cv.wait(lk, [&]() 
				{
					return !m_queue.empty() || m_stop.load();
				});
			while (!m_queue.empty())
			{
				LogRecord record = m_queue.front();
				m_queue.pop();
				std::ostringstream os;
				os << record.current_time 
					<< "|" 
					<< LevelToStr(record.level) 
					<< "|"
					<< record.file_name
					<<":"
					<< record.line
					<< "|"<<"["
					<<record.func_name
					<<"]"
					<<record.content << std::endl;
				m_stream.write(os.str().c_str(),strlen(os.str().c_str()));
			}

		}
	}
	std::string LevelToStr(LogLevel level) const
	{
		return m_map.at(level);
	}
private:
	std::thread m_thread;
	std::atomic_bool m_stop;
	std::mutex m_mutex;
	std::condition_variable m_cv;
	std::queue<LogRecord> m_queue;
	std::ofstream m_stream;
	std::map<LogLevel, std::string> m_map;
};

class AsyncLogger
{
public:
	AsyncLogger(const AsyncLogger&) = delete;
	AsyncLogger& operator = (const AsyncLogger&) = delete;
	static AsyncLogger& GetInstance()
	{
		static AsyncLogger instance;
		return instance;
	}
	bool open(std::string &filepath)
	{
		return m_async_log.open(filepath);
	}
	bool open(const char* filepath)
	{
		return m_async_log.open(filepath);
	}

	void write(LogLevel level,std::string file_name,int line,std::string func_name ,std::string content)
	{
		auto now = std::chrono::system_clock::now();
		auto now_time_t = std::chrono::system_clock::to_time_t(now);
		auto local_time =  std::localtime(&now_time_t);
		auto duration_since_epoch = std::chrono::system_clock::now().time_since_epoch();
		auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration_since_epoch).count() % 1000;
		std::ostringstream time;
		time << 1900 + local_time->tm_year << '-'
			<< 1 + local_time->tm_mon << '-'
			<< local_time->tm_mday << ' '
			<< local_time->tm_hour << ':'
			<< local_time->tm_min << ':'
			<< local_time->tm_sec << ':'
			<< millis;
		LogRecord record;
		record.level = level;
		record.content = content;
		record.current_time = time.str();
		record.func_name = func_name;
		record.line = line;
		record.file_name = file_name;
		m_async_log.push(record);
	}
private:
	AsyncLogger():m_close(true)
	{
	}
	~AsyncLogger()
	{
		
	}
private:
	AsyncLog m_async_log;
	
};

 调用:

int main()
{

	AsyncLogger::GetInstance().open("1.log");
	
	for (int i = 0; i < 100; i++)
	{
		std::ostringstream os;
		os << i;
		LOGINFO(os.str());
		LOGDEBUG(os.str());
		LOGERROR(os.str());
		LOGFATAL(os.str());
	}
	
	return 0;
}

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Window C实现web服务器可以使用多种方式,下面是一种常见的实现方式。 首先,需要使用Window操作系统提供的Socket API来创建并管理服务器的Socket连接。通过调用Socket API创建一个Socket套接字,并绑定一个IP地址和端口号。 接下来,需要编写C代码来处理客户端的请求。可以使用多线程或者异步的方式处理多个客户端的请求。首先调用Socket API的接收函数accept()来接收客户端的连接请求。然后,使用Socket API的发送和接收函数来与客户端进行数据交互,实现HTTP协议的请求和响应。 在C代码中,需要解析客户端发送的HTTP请求报文,包括请求方法、URI、HTTP版本、请求头等信息。根据请求的URI,可以使用文件IO函数来读取服务器上的网页文件,将读取到的文件内容作为HTTP响应的主体返回给客户端。 另外,还需要处理一些特殊的请求,比如GET请求和POST请求。对于GET请求,可以从查询字符串中获取参数,根据参数的不同返回不同的响应;对于POST请求,需要解析请求主体中的表单数据,并根据表单数据的不同做出相应的处理。 最后,在C代码中还可以实现一些功能,比如日志记录、错误处理和异常处理等,以提高服务器的稳定性和安全性。 总结来说,Window C实现web服务器需要使用Window操作系统提供的Socket API来创建和管理服务器的Socket连接。通过编写C代码来处理客户端的请求,包括解析HTTP请求报文、读取网页文件、处理特殊请求等。除此之外,还需要实现一些额外的功能,比如日志记录和错误处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值