C++/C++11实现的中小项目级别日志系统源码

该文章描述了一个自定义的日志类`Log`,支持不同级别(如Error,Warn,Info等)的日志记录,并能异步保存到文件。通过`BlockingQueue`管理日志信息,确保线程安全。在程序退出时能停止异步日志记录。示例代码展示了如何使用这个日志系统在多个线程中记录日志。
摘要由CSDN通过智能技术生成
特点:
1. 支持同步、异步记录方式————方便按需使用
2. 线程安全————可多个线程同时记录日志
3. 日志格式清晰————排版为"日志等级;记录时间(毫秒级);线程id;源文件以及行号;日志内容",有需要也可以把函数名加进去
4. 支持多文件记录————可分多个文件进行记录,默认日志为./global.log
5. 使用方便————只需包含头文件,调用Start函数即可使用,且日志记录方式同C++流使用方式一致
6. 支持设置日志等级————低于默认日志等级的日志不会记录
7. 支持记录任意数据类型————需要自定义实现operator<<操作符,写入到ostreamstring流中
8. 代码量少,易于集成和拓展————代码量300行左右,有任何疑问欢迎留言讨论

main.cpp

#include "Log.h"
#include <thread>
const std::string log11("./log1.log");
const std::string log22("./log2.log");
// 演示程序,运行后会在项目当前目录下,生成log1.log,log2.log和global.log,注意日志等级,有的日志可能不会记录。
int main()
{
	Log::StartSyncLog();
	//Log::StartAsyncLog();

	std::thread t([] {
		LogInfoF(log11) << "ci thread";
	});

	LogInfoF(log11) << "flower";

	int i = 2;

	LogDebugF(log22) << "123" << i;
	LogErrorF(log22) << "123123" << "qwe";
	LogInfo << "wuhu" << i;
	t.join();
	return 0;
}

Log.h

#pragma once

#include <string>
#include <memory>
#include <thread>
#include <sstream>
#include <mutex>

#include "UtilTemplate.h"
#include "BlockingQueue.h"

class Log {
public:
	enum class Level {
		DEBUG,
		INFO,
		WARN,
		ERROR,
		FATAL,
	};
public:
	Log();
	Log(std::string fileName);
	~Log();
public:
	static void SetDefaultLogFileName(std::string fileName) { defaultLogFileName_ = fileName; }
	static void SetLogLevel(Log::Level level) { defaultLogLevel_ = level; }
	static Log::Level GetLogLevel() { return defaultLogLevel_; }
	static bool GetStop() { return stop_; }
	std::ostringstream& Record(Log::Level level, const char* fileName, int lineNum, const char* funcName);
private:
	std::string fileName_;
	std::ostringstream stream_;
private:
	static bool async_;
	static bool stop_;
	static std::mutex mtx_;
private:
	static Log::Level defaultLogLevel_;
	static std::string defaultLogFileName_;
public:
	static void StartSyncLog();
	static void StartAsyncLog();
private:
	static void StopAsyncLog();
	static void RecordAsyncLog();
	static void WriteLogToFile();
private:
	static std::thread recordThread_;
	static std::unique_ptr<BlockingQueue<std::pair<std::string, std::string>>> blockingQueues_;
};

#define LogDebug if(Log::GetLogLevel() <= Log::Level::DEBUG && !Log::GetStop()) Log().Record(Log::Level::DEBUG, SRC_FILE_NAME(__FILE__), __LINE__, __func__)
#define LogInfo if(Log::GetLogLevel() <= Log::Level::INFO && !Log::GetStop()) Log().Record(Log::Level::INFO, SRC_FILE_NAME(__FILE__), __LINE__, __func__)
#define LogWarn if(Log::GetLogLevel() <= Log::Level::WARN && !Log::GetStop()) Log().Record(Log::Level::WARN, SRC_FILE_NAME(__FILE__), __LINE__, __func__)
#define LogError if(Log::GetLogLevel() <= Log::Level::ERROR && !Log::GetStop()) Log().Record(Log::Level::ERROR, SRC_FILE_NAME(__FILE__), __LINE__, __func__)
#define LogFatal if(Log::GetLogLevel() <= Log::Level::FATAL && !Log::GetStop()) Log().Record(Log::Level::FATAL, SRC_FILE_NAME(__FILE__), __LINE__, __func__)

#define LogDebugF(fileName) if(Log::GetLogLevel() <= Log::Level::DEBUG && !Log::GetStop()) Log(fileName).Record(Log::Level::DEBUG, SRC_FILE_NAME(__FILE__), __LINE__, __func__)
#define LogInfoF(fileName) if(Log::GetLogLevel() <= Log::Level::INFO && !Log::GetStop()) Log(fileName).Record(Log::Level::INFO, SRC_FILE_NAME(__FILE__), __LINE__, __func__)
#define LogWarnF(fileName) if(Log::GetLogLevel() <= Log::Level::WARN && !Log::GetStop()) Log(fileName).Record(Log::Level::WARN, SRC_FILE_NAME(__FILE__), __LINE__, __func__)
#define LogErrorF(fileName) if(Log::GetLogLevel() <= Log::Level::ERROR && !Log::GetStop()) Log(fileName).Record(Log::Level::ERROR, SRC_FILE_NAME(__FILE__), __LINE__, __func__)
#define LogFatalF(fileName) if(Log::GetLogLevel() <= Log::Level::FATAL && !Log::GetStop()) Log(fileName).Record(Log::Level::FATAL, SRC_FILE_NAME(__FILE__), __LINE__, __func__)

Log.cpp

#include "Log.h"

#include <map>
#include <string>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <thread>
#include <chrono>

std::mutex Log::mtx_{};
bool Log::stop_ = true;
bool Log::async_ = false;
std::thread Log::recordThread_{};
std::unique_ptr<BlockingQueue<std::pair<std::string, std::string>>>
Log::blockingQueues_{ new BlockingQueue<std::pair<std::string, std::string>>() };

Log::Level Log::defaultLogLevel_ = Log::Level::INFO;
std::string Log::defaultLogFileName_ = "./global.log";

namespace {
std::map<Log::Level, const char*> levelMap = {
	{ Log::Level::DEBUG, "DEBUG" },
	{ Log::Level::INFO, "INFO" },
	{ Log::Level::WARN, "WARN" },
	{ Log::Level::ERROR, "ERROR" },
	{ Log::Level::FATAL, "FATAL" },
};

std::string GetFormatTime()
{
	std::stringstream ss;
	auto curTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
	struct tm t;
	localtime_s(&t, &curTime);
	ss << std::put_time(&t, "%Y-%m-%d %H:%M:%S");
	auto tNow = std::chrono::system_clock::now();
	auto tMilli = std::chrono::duration_cast<std::chrono::milliseconds>(tNow.time_since_epoch());
	auto tSeconds = std::chrono::duration_cast<std::chrono::seconds>(tNow.time_since_epoch());
	auto ms = tMilli - tSeconds;
	ss << "." << std::setfill('0') << std::setw(3) << ms.count();
	return ss.str();
}
}

void Log::StartSyncLog()
{
	stop_ = false;
	async_ = false;
}

void Log::StartAsyncLog()
{
	stop_ = false;
	async_ = true;

	blockingQueues_->SetMaxSize(INT_MAX);
	blockingQueues_->Start();
	std::thread tmp(RecordAsyncLog);
	recordThread_.swap(tmp);

	atexit(StopAsyncLog);
}

void Log::StopAsyncLog()
{
	stop_ = true;
	blockingQueues_->Notify();
	recordThread_.join();

	if (!blockingQueues_->Empty()) {
		blockingQueues_->Start();
		while (!blockingQueues_->Empty()) {
			WriteLogToFile();
		}
	}
	blockingQueues_->Stop();
}

void Log::RecordAsyncLog()
{
	while (!stop_) {
		WriteLogToFile();
	}
}

void Log::WriteLogToFile()
{
	std::pair<std::string, std::string> pair;
	bool res = blockingQueues_->Pop(pair);
	if (res) {
		std::ofstream out(pair.first, std::ios::app);
		if (out.rdstate() == std::ofstream::goodbit) {
			out << pair.second << std::endl;
			out.close();
		}
	}
}

Log::Log()
	:fileName_(defaultLogFileName_)
{
}

Log::Log(std::string fileName)
	:fileName_(fileName)
{
}

Log::~Log()
{
	auto log = stream_.str();
	if (async_) {
		blockingQueues_->Push({ fileName_, log });
	} else {
		std::unique_lock<std::mutex> lock(mtx_);
		std::ofstream out(fileName_, std::ios::app);
		if (out.rdstate() == std::ofstream::goodbit) {
			out << log << std::endl;
			out.close();
		}
	}
}

std::ostringstream& Log::Record(Log::Level level, const char* fileName, int lineNum, const char* funcName)
{
	stream_ << levelMap[level] << ";"
		<< GetFormatTime() << ";"
		<< "tid:" << std::setfill('0') << std::setw(5) << std::this_thread::get_id() << ";"
		<< fileName << ":" << lineNum << ";";
	return stream_;
}

BlockingQueue.h

#pragma once

#include <memory>
#include <list>
#include <mutex>
#include <condition_variable>

template<typename T>
class BlockingQueue {
public:
	BlockingQueue() = default;
	BlockingQueue(size_t maxSize) : maxSize_(maxSize) {}
	BlockingQueue(const BlockingQueue&) = default;

	bool Pop(T& t) {
		std::unique_lock<std::mutex> lock(mutex_);
		conditionVar_.wait(lock, [this]() { return !queue_.empty() || isStop_; });
		if (isStop_) {
			return false;
		}
		t = queue_.front();
		queue_.pop_front();
		conditionVar_.notify_one();
		return true;
	}

	bool Push(T elem) {
		std::unique_lock<std::mutex> lock(mutex_);
		conditionVar_.wait(lock, [this]() { return (queue_.size() < maxSize_) || isStop_; });
		if (isStop_) {
			return false;
		}
		queue_.push_back(elem);
		conditionVar_.notify_one();
		return true;
	}

	void SetMaxSize(int size) {
		maxSize_ = size;
	}

	void Notify() {
		{
			std::unique_lock<std::mutex> lock(mutex_);
			isStop_ = true;
		}
		conditionVar_.notify_all();
	}

	void Stop() {
		{
			std::unique_lock<std::mutex> lock(mutex_);
			isStop_ = true;
		}
		conditionVar_.notify_all();
		queue_.clear();
	}

	void Start() {
		std::unique_lock<std::mutex> lock(mutex_);
		isStop_ = false;
	}

	bool Empty() {
		std::unique_lock<std::mutex> lock(mutex_);
		return queue_.empty();
	}

	size_t Size() {
		std::unique_lock<std::mutex> lock(mutex_);
		return queue_.size();
	}

private:
	bool isStop_ = false;
	std::mutex mutex_;
	std::condition_variable conditionVar_;
	std::list<T> queue_;
	size_t maxSize_ = 10;
};

UtilTemplate.h

#pragma once
//用于编译期获取源文件名称,来源[https://stackoverflow.com/questions/8487986/file-macro-shows-full-path#]
template <typename T, size_t N>
inline constexpr size_t get_file_name_offset(const T(&str)[N], size_t i = N - 1)
{
	return (str[i] == '/' || str[i] == '\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
}

template <typename T>
inline constexpr size_t get_file_name_offset(T(&str)[1])
{
	return 0;
}

namespace utility {
	template <typename T, T v>
	struct const_expr_value
	{
		static constexpr const T value = v;
	};
}

#define UTILITY_CONST_EXPR_VALUE(exp) ::utility::const_expr_value<decltype(exp), exp>::value

#define SRC_FILE_NAME(FN) (&FN[UTILITY_CONST_EXPR_VALUE(get_file_name_offset(FN))])

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

flower980323

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值