特点:
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))])