同步/异步日志系统
源码地址:点这里
项目演示
基础测试
打印20w条日志分别到标准输出、普通文件、滚动文件(每个文件大小限制为1MB)中
标准输出:
标准文件:
滚动文件:
性能测试
测试环境:
Ubuntu云服务器
同步日志器单线程

同步日志器多线程

异步日志器单线程

异步日志器多线程

工具类(util.hpp)
cpp
#pragma once
/*
工具类:
1.获取系统时间(now)
2.判断文件是否存在(exists)
3.获取文件所在路径(path)
4.创建目录(createdirectory)
函数都是静态成员函数,方便直接使用,不用实例化对象
util:工具
*/
#include <iostream>
#include <fstream>
#include <string>
#include <ctime>
#include <cassert>
#include <sys/stat.h>
#include <filesystem>
using namespace std;
namespace hsl_log
{
namespace util
{
class date
{
public:
static size_t now() { return (size_t)time(nullptr); }
};
class file
{
public:
static bool exists(const std::string &name)
{
struct stat st;
return stat(name.c_str(), &st) == 0;
}
static std::string path(const std::string &name)
{
if (name.empty())
return ".";
size_t pos = name.find_last_of("/\\");
if (pos == std::string::npos)
return ".";
return name.substr(0, pos + 1);
}
static void create_directory_1(const std::string &path)
{
if (path.empty())
return;
if (exists(path))
return;
size_t pos, idx = 0;
while (idx < path.size())
{
pos = path.find_first_of("/\\", idx);
if (pos == std::string::npos)
{
mkdir(path.c_str(), 0755);
return;
}
if (pos == idx)
{
idx = pos + 1;
continue;
}
std::string subdir = path.substr(0, pos);
if (subdir == "." || subdir == "..")
{
idx = pos + 1;
continue;
}
if (exists(subdir))
{
idx = pos + 1;
continue;
}
mkdir(subdir.c_str(), 0755);
idx = pos + 1;
}
}
static void create_directory(const string &pathname)
{
filesystem::create_directories(pathname);
}
};
}
}
日志等级

level.hpp
cpp
#pragma once
/*
枚举类:
1.定义枚举类,枚举出日志等级
2.提供转换接口,将枚举转换为对应字符串
level:级别
UNKNOW:未知的
*/
namespace hsl_log{
class LogLevel{
public:
enum class value {
UNKNOW = 0,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
OFF
};
static const char *toString(LogLevel::value l) {
switch(l) {
case LogLevel::value::DEBUG: return "DEBUG";
case LogLevel::value::INFO: return "INFO";
case LogLevel::value::WARN: return "WARN";
case LogLevel::value::ERROR: return "ERROR";
case LogLevel::value::FATAL: return "FATAL";
case LogLevel::value::OFF: return"OFF";
}
return "UNKNOW";
}
};
}
日志消息

message.hpp
cpp
#pragma once
/*
日志消息类:
1.日志输出时间
2.日志等级
3.源文件名称
4.源代码行号
5.线程ID
6.日志主体消息
7.日志器名称
*/
#include <iostream>
#include <thread>
#include "level.hpp"
#include "util.hpp"
using namespace std;
namespace hsl_log
{
struct LogMsg
{
using ptr = std::shared_ptr<LogMsg>;
size_t _line; // 行号
size_t _ctime; // 时间
std::thread::id _tid; // 线程ID
std::string _name; // 日志器名称
std::string _file; // 文件名
std::string _payload; // 日志消息
LogLevel::value _level; // 日志等级
LogMsg(std::string &name, std::string file, size_t line, std::string &&payload,
LogLevel::value level) : _name(name), _file(file), _payload(std::move(payload)), _level(level),
_line(line), _ctime(util::date::now()), _tid(std::this_thread::get_id())
{
// std::cout << "构造msg\n";
}
//~LogMsg() { /*std::cout << "析构msg\n";*/}
};
}
日志消息格式化

formatter.hpp
cpp
#pragma once
/*
消息格式化类:
formatter:格式化
Item:项(子项)
1.抽象一个格式化基类
2.基于基类,派生出不同的格式化子项子类
子项:
{
日志输出时间
日志等级
源文件名称
源代码行号
线程ID
日志主体消息
日志器名称
换行
制表符
其他
}
这样就可以在父类中定义父类指针的数组,指向不同的格式化子类对象(多态!)
*/
#include "util.hpp"
#include "message.hpp"
#include "level.hpp"
#include <memory>
#include <vector>
#include <tuple>
#include <sstream>
namespace hsl_log
{
// 抽象一个"格式化项"基类
class FormatItem
{
public:
virtual ~FormatItem() {}
// 等价于typedef std::shared_ptr<FormatItem> ptr;
using ptr = shared_ptr<FormatItem>; // 别名
virtual void format(ostream &out, LogMsg &msg) = 0;
};
/*
基于基类,派生出不同的格式化子项子类
子项:
{
日志输出时间
日志等级
源文件名称
源代码行号
线程ID
日志主体消息
日志器名称
换行
制表符
其他
}
*/
// 消息主体
class MsgFormatItem : public FormatItem
{
public:
void format(ostream &out, LogMsg &msg) override
{
out << msg._payload;
}
};
// 等级
class LevelFormatItem : public FormatItem
{
public:
void format(ostream &out, LogMsg &msg) override
{
out << LogLevel::toString(msg._level);
}
};
// 时间
class TimeFormatItem : public FormatItem
{
public:
TimeFormatItem(const string &fmt = "%H:%M:%S")
: _time_fmt(fmt)
{
// if (fmt.empty()) _time_fmt = "%H:%M:%S";
}
/*
void format(ostream &out, LogMsg &msg) override
{
time_t t = msg._ctime;
struct tm lt;
localtime_r(&t, <);
char tmp[128];
strftime(tmp, 127, _time_fmt.c_str(), <);
out << tmp;
}
*/
void format(ostream &out, LogMsg &msg) override
{
string tmp = TimeStampExLocalTime();
out << tmp.c_str();
}
static std::string TimeStampExLocalTime()
{
time_t currtime = time(nullptr);
struct tm *curr = localtime(&currtime);
char time_buffer[128];
snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
curr->tm_year + 1900, curr->tm_mon + 1, curr->tm_mday,
curr->tm_hour, curr->tm_min, curr->tm_sec);
return time_buffer;
}
private:
string _time_fmt; //%H:%M:%S
};
// 行号
class LineFormatItem : public FormatItem
{
public:
void format(ostream &out, LogMsg &msg) override
{
out << msg._line;
}
};
// 源文件名
class FileFormatItem : public FormatItem
{
public:
void format(ostream &out, LogMsg &msg) override
{
out << msg._file;
}
};
// 线程ID
class ThreadFormatItem : public FormatItem
{
public:
void format(ostream &out, LogMsg &msg) override
{
out << msg._tid;
}
};
// 日志器名称
class LoggerFormatItem : public FormatItem
{
public:
void format(ostream &out, LogMsg &msg) override
{
out << msg._name;
}
};
// 制表符
class TabFormatItem : public FormatItem
{
public:
void format(ostream &out, LogMsg &msg) override
{
out << '\t';
}
};
// 换行符
class NLineFormatItem : public FormatItem
{
public:
void format(ostream &out, LogMsg &msg) override
{
out << '\n';
}
};
// 其他
class OtherFormatItem : public FormatItem
{
public:
OtherFormatItem(const string &str) : _str(str) {}
void format(ostream &out, LogMsg &msg) override
{
out << _str;
}
private:
string _str;
};
// 格式化类(主模块)
/*
%d 日期
%T 缩进
%t 线程id
%p 日志级别
%c 日志器名称
%f 文件名
%l 行号
%m 日志消息
%n 换行
*/
class Formatter
{
public:
using ptr = shared_ptr<Formatter>;
Formatter(const std::string &pattern = "[%d{%H:%M:%S}][%t][%p][%c][%f:%l]%T%m%n") : _pattern(pattern)
{
assert(parsePattern());
}
// 格式化到标准输出
void format(ostream &out, LogMsg &msg)
{
for (auto &items : _items)
{
items->format(out, msg);
}
}
// 能获取到格式化后的字符串
string format(LogMsg &msg)
{
stringstream ss;
format(ss, msg);
return ss.str();
}
private:
// 解析字符串
bool parsePattern()
{
// 1.对格式化规则进行解析
vector<pair<string, string>> fmt_order;//存放key,val,方便最后存进成员变量_items里
size_t pos = 0; //
string key, val; // 分别存放 %x中的x and 其他字符(原始字符)
while (pos < _pattern.size())
{
// 1.1处理字符串--判断是否为 %,否则就是其他字符
if (_pattern[pos] != '%')
{
val.push_back(_pattern[pos++]);
continue;
}
// 1.2到这说明pos位置就是%,还要判断一下%%的情况
if (pos + 1 < _pattern.size() && _pattern[pos + 1] == '%')
{
val.push_back('%');
pos += 2;
continue;
}
if (val.empty() == false)
{
fmt_order.push_back(make_pair("", val));
val.clear();
}
// 1.3到这说明一定是格式化字符(%X)
// pos位置就是%,pos+1位置就是我们要的字符
pos += 1;
if (pos == _pattern.size())
{
cout << "%之后没有对应的格式化字符" << endl;
return false;
}
key = _pattern[pos];
pos += 1;
// 1.4这时pos指向格式化字符的下一个位置
if (pos < _pattern.size() && _pattern[pos] == '{') // 如果是{
{
pos += 1; // 指向子规则的起始位置:%
while (pos < _pattern.size() && _pattern[pos] != '}')
{
val.push_back(_pattern[pos++]);
}
if (pos == _pattern.size()) // 没有找到'}'
{
cout << "子规则{}匹配出错!\n";
return false;
}
pos += 1; // 这时候pos在'}'位置,+1到
}
fmt_order.push_back(make_pair(key, val));
key.clear();
val.clear();
}
// 2.根据解析得到的数据格式化子项数组成员
for (auto &it : fmt_order)
{
_items.push_back(createItem(it.first, it.second));
}
return true;
}
// 根据不同的格式化字符创建不同的格式化子项 %x //其他字符(原始字符)
FormatItem ::ptr createItem(const string &key, const string &val)
{
if (key == "d")
return make_shared<TimeFormatItem>(val);
if (key == "T")
return make_shared<TabFormatItem>();
if (key == "t")
return make_shared<ThreadFormatItem>();
if (key == "p")
return make_shared<LevelFormatItem>();
if (key == "c")
return make_shared<LoggerFormatItem>();
if (key == "f")
return make_shared<FileFormatItem>();
if (key == "l")
return make_shared<LineFormatItem>();
if (key == "m")
return make_shared<MsgFormatItem>();
if (key == "n")
return make_shared<NLineFormatItem>();
if (key == "")
return make_shared<OtherFormatItem>(val);
cout << "没有对应的格式化字符:%" << key << endl;
abort();
}
private:
string _pattern; // 符合格式化规则的字符串
vector<FileFormatItem::ptr> _items; // 定义存放格式化对象的数组
};
}
日志消息落地
sink.hpp
cpp
#pragma once
/*
日志落地模块实现
1. 抽象落地基类
2. 派生落地子类(根据不同的方向进行派生)
3. 使用工厂模式进行创建与表示的分离
sink:下沉/落地
*/
#include "util.hpp"
#include "message.hpp"
#include "formatter.hpp"
#include <memory>
#include <mutex>
namespace hsl_log
{
// 日志落地基类,定义统一的日志输出接口
class LogSink
{
public:
using ptr = std::shared_ptr<LogSink>;//智能指针的别名(==typedef...)
LogSink() {}
virtual ~LogSink() {}
virtual void log(const char *data, size_t len) = 0;// 纯虚函数,定义日志输出接口
};
// 标准输出
class StdoutSink : public LogSink
{
public:
using ptr = std::shared_ptr<StdoutSink>;//
StdoutSink() = default;// 默认构造函数
void log(const char *data, size_t len)
{
std::cout.write(data, len);
}
};
// 指定文件
class FileSink : public LogSink
{
public:
using ptr = std::shared_ptr<FileSink>;
FileSink(const std::string &filename) : _filename(filename)
{
// 1.创建日志所在的目录
util::file::create_directory(util::file::path(filename));
// 2.创建并打开日志文件(app追加形式)
_ofs.open(_filename, std::ios::binary | std::ios::app);
assert(_ofs.is_open());
}
const std::string &file() { return _filename; }
void log(const char *data, size_t len)
{
_ofs.write((const char *)data, len);
if (_ofs.good() == false)
{
std::cout << "日志输出文件失败!\n";
}
}
private:
std::string _filename;
std::ofstream _ofs;
};
//滚动文件
class RollSink : public LogSink
{
public:
using ptr = std::shared_ptr<RollSink>;
RollSink(const std::string &basename, size_t max_fsize) : _basename(basename), _max_fsize(max_fsize), _cur_fsize(0),_name_count(0)
{
util::file::create_directory(util::file::path(basename));
}
void log(const char *data, size_t len)
{
initLogFile();
_ofs.write(data, len);
if (_ofs.good() == false)
{
std::cout << "日志输出文件失败!\n";
}
_cur_fsize += len;
}
private:
// 初始化日志文件,检查是否需要创建新文件
void initLogFile()
{
// 如果文件未打开或当前文件大小超过限制,则创建新文件
if (_ofs.is_open() == false || _cur_fsize >= _max_fsize)
{
_ofs.close();
std::string name = createFilename();
_ofs.open(name, std::ios::binary | std::ios::app);
assert(_ofs.is_open());
_cur_fsize = 0;
return;
}
return;
}
//用时间戳当文件名,保证文件名的唯一性
std::string createFilename()
{
time_t t = time(NULL);
struct tm lt;
localtime_r(&t, <);
std::stringstream ss;
ss << _basename;
ss << lt.tm_year + 1900;
ss << lt.tm_mon + 1;
ss << lt.tm_mday;
ss << lt.tm_hour;
ss << lt.tm_min;
ss << lt.tm_sec;
ss << "-";
ss << _name_count++;
ss << ".log";
return ss.str();
}
private:
size_t _name_count;
std::string _basename;
std::ofstream _ofs;
size_t _max_fsize;
size_t _cur_fsize;
};
// 日志落地工厂类,用于创建各种类型的日志落地器
class SinkFactory
{
public:
// 工厂方法,创建指定类型的日志落地器
// 模板参数:
// SinkType: 要创建的落地器类型
// Args: 构造参数类型
// 参数:
// args: 传递给构造函数的参数
// 返回值: 落地器的共享指针
template <typename SinkType, typename... Args>
static LogSink::ptr create(Args &&...args)
{
//使用完美转发创建指定类型的落地器
return std::make_shared<SinkType>(std::forward<Args>(args)...);
}
};
}
日志器

cpp
#pragma once
/*
日志器模块:
1. 抽象日志基类
2. 派生出不同的子类(同布日志器类 & 异步日志器类)
*/
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "formatter.hpp"
#include "sink.hpp"
#include "looper.hpp"
#include <vector>
#include <list>
#include <atomic>
#include <unordered_map>
#include <cstdarg>
#include <type_traits>
namespace hsl_log
{
class SyncLogger;
class AsyncLogger;
class Logger
{
public:
enum class Type
{
LOGGER_SYNC = 0,
LOGGER_ASYNC
};
using ptr = std::shared_ptr<Logger>;
Logger(const std::string &name,
Formatter::ptr formatter,
std::vector<LogSink::ptr> &sinks,
LogLevel::value level = LogLevel::value::DEBUG) : _name(name), _level(level), _formatter(formatter),
_sinks(sinks.begin(), sinks.end())
{
}
std::string loggerName() { return _name; }
LogLevel::value loggerLevel() { return _level; }
void debug(const char *file, size_t line, const char *fmt, ...)
{
// 1.判断是否达到了日志等级
if (shouldLog(LogLevel::value::DEBUG) == false)
{
return;
}
// 处理不定参...
va_list al;
va_start(al, fmt);
log(LogLevel::value::DEBUG, file, line, fmt, al);
va_end(al);
}
void info(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::INFO) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::INFO, file, line, fmt, al);
va_end(al);
}
void warn(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::WARN) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::WARN, file, line, fmt, al);
va_end(al);
}
void error(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::ERROR) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::ERROR, file, line, fmt, al);
va_end(al);
}
void fatal(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::FATAL) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::FATAL, file, line, fmt, al);
va_end(al);
}
public:
// 建造者模式:就是为了解决一个对象构造过于复杂,参数过多的问题,为用户使用提供便利 降低用户使用的复杂度
// 1.抽象一个日志建造者类
// 1.1 设置日志器类型
// 1.2 将不同类型日志器的创建放到同一个日志建造者类中完成
class Builder
{
public:
using ptr = std::shared_ptr<Builder>;
Builder() : _level(LogLevel::value::DEBUG),
_logger_type(Logger::Type::LOGGER_SYNC),
_looper_type(AsyncType::ASYNC_SAFE) {}
void buildLoggerName(const std::string &name) { _logger_name = name; }
void buildLoggerLevel(LogLevel::value level) { _level = level; }
void buildEnableUnSafeAsync() {_looper_type = AsyncType::ASYNC_UNSAFE; }
void buildLoggerType(Logger::Type type) { _logger_type = type; }
void buildFormatter(const std::string pattern) { _formatter = std::make_shared<Formatter>(pattern); }
void buildFormatter(const Formatter::ptr &formatter) { _formatter = formatter; }
//添加日志落地目标
template <typename SinkType, typename... Args>
void buildSink(Args &&...args)
{
auto sink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
_sinks.push_back(sink);
}
// 纯虚函数,由具体建造者实现
virtual Logger::ptr build() = 0;
protected:
AsyncType _looper_type;
Logger::Type _logger_type;
std::string _logger_name;
LogLevel::value _level;
Formatter::ptr _formatter;
std::vector<LogSink::ptr> _sinks;
};
protected:
bool shouldLog(LogLevel::value level) { return level >= _level; }
// log序列化
void log(LogLevel::value level, const char *file, size_t line, const char *fmt, va_list al)
{
// 2.对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串
char *buf;
std::string msg;
int len = vasprintf(&buf, fmt, al);
if (len < 0)
{
msg = "格式化日志消息失败!!";
}
else
{
msg.assign(buf, len);
free(buf);
}
// 3. 构造LogMsg对象
// LogMsg(name, file, line, payload, level)
LogMsg lm(_name, file, line, std::move(msg), level);
// 4.通过格式化工具对LogMsg进行格式化,得到格式化后的日志字符串
std::stringstream ss;
_formatter->format(ss, lm);
logIt(std::move(ss.str()));
}
virtual void logIt(const std::string &msg) = 0;
protected:
std::mutex _mutex;
std::string _name;
Formatter::ptr _formatter; // 格式化
std::atomic<LogLevel::value> _level; // 限制等级
std::vector<LogSink::ptr> _sinks; // 落地方向
};
// 同步日志
class SyncLogger : public Logger
{
public:
using ptr = std::shared_ptr<SyncLogger>;
SyncLogger(const std::string &name,
Formatter::ptr formatter,
std::vector<LogSink::ptr> &sinks,
LogLevel::value level = LogLevel::value::DEBUG) : Logger(name, formatter, sinks, level)
{
std::cout << LogLevel::toString(level) << " 同步日志器: " << name << "创建成功...\n";
}
private:
void logIt(const std::string &msg) override
{
std::unique_lock<std::mutex> lock(_mutex);
if (_sinks.empty())
{
return;
}
for (auto &it : _sinks)
{
it->log(msg.c_str(), msg.size());
}
}
};
// 异步日志器
class AsyncLogger : public Logger
{
public:
using ptr = std::shared_ptr<AsyncLogger>;
AsyncLogger(const std::string &name,
Formatter::ptr formatter,
std::vector<LogSink::ptr> &sinks,
LogLevel::value level = LogLevel::value::DEBUG,
AsyncType looper_type= AsyncType::ASYNC_SAFE)
:Logger(name, formatter, sinks, level),
_looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::backendLogIt, this, std::placeholders::_1),looper_type))
{
std::cout << LogLevel::toString(level) << "异步日志器: " << name << "创建成功...\n";
}
protected:
void logIt(const std::string &msg) override
{
_looper->push(msg);
}
//实际落地函数,将缓冲区中的数据落地
void backendLogIt(Buffer &msg)
{
if (_sinks.empty())
{
return;
}
for (auto &it : _sinks)
{
it->log(msg.begin(), msg.readAbleSize());
}
}
protected:
AsyncLooper::ptr _looper;
};
//本地日志记录器构建器(局部)
class LocalLoggerBuilder : public Logger::Builder
{
public:
Logger::ptr build() override
{
if (_logger_name.empty())
{
std::cout << "日志器名称不能为空!!";
abort();
}
if (_formatter.get() == nullptr)
{
std::cout << "当前日志器:" << _logger_name << " 未检测到日志格式,默认设置为[ %d{%H:%M:%S}%T%t%T[%p]%T[%c]%T%f:%l%T%m%n ]!\n";
_formatter = std::make_shared<Formatter>();
}
if (_sinks.empty())
{
std::cout << "当前日志器:" << _logger_name << " 未检测到落地方向,默认设置为标准输出!\n";
_sinks.push_back(std::make_shared<StdoutSink>());
}
Logger::ptr lp;
if (_logger_type == Logger::Type::LOGGER_ASYNC)
{
lp = std::make_shared<AsyncLogger>(_logger_name, _formatter, _sinks, _level,_looper_type);
}
else
{
lp = std::make_shared<SyncLogger>(_logger_name, _formatter, _sinks, _level);
}
return lp;
}
};
//日志管理器:单例模式(懒汉)
class loggerManager
{
private:
std::mutex _mutex;
Logger::ptr _root_logger;
std::unordered_map<std::string, Logger::ptr> _loggers;
private:
loggerManager()
{
std::unique_ptr<LocalLoggerBuilder> slb(new LocalLoggerBuilder());
slb->buildLoggerName("root");
slb->buildLoggerType(Logger::Type::LOGGER_SYNC);
_root_logger = slb->build();
_loggers.insert(std::make_pair("root", _root_logger));
}
loggerManager(const loggerManager &) = delete;
loggerManager &operator=(const loggerManager &) = delete;
public:
static loggerManager &getInstance()
{
static loggerManager lm;
return lm;
}
bool hasLogger(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _loggers.find(name);
if (it == _loggers.end())
{
return false;
}
return true;
}
void addLogger(const std::string &name, const Logger::ptr logger)
{
std::unique_lock<std::mutex> lock(_mutex);
_loggers.insert(std::make_pair(name, logger));
}
Logger::ptr getLogger(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _loggers.find(name);
if (it == _loggers.end())
{
return Logger::ptr();
}
return it->second;
}
Logger::ptr rootLogger()
{
std::unique_lock<std::mutex> lock(_mutex);
return _root_logger;
}
};
//全局日志建造者
class GlobalLoggerBuilder : public Logger::Builder
{
public:
Logger::ptr build() override
{
if (_logger_name.empty())
{
std::cout << "日志器名称不能为空!!";
abort();
}
assert(loggerManager::getInstance().hasLogger(_logger_name) == false);
if (_formatter.get() == nullptr)
{
std::cout << "当前日志器:" << _logger_name << " 未检测到日志格式,默认设置为[ %d{%H:%M:%S}%T%t%T[%p]%T[%c]%T%f:%l%T%m%n ]!\n";
_formatter = std::make_shared<Formatter>();
}
if (_sinks.empty())
{
std::cout << "当前日志器:" << _logger_name << " 未检测到落地方向,默认设置为标准输出!\n";
_sinks.push_back(std::make_shared<StdoutSink>());
}
Logger::ptr lp;
if (_logger_type == Logger::Type::LOGGER_ASYNC)
{
lp = std::make_shared<AsyncLogger>(_logger_name, _formatter, _sinks, _level);
}
else
{
lp = std::make_shared<SyncLogger>(_logger_name, _formatter, _sinks, _level);
}
loggerManager::getInstance().addLogger(_logger_name, lp);
return lp;
}
};
}
异步线程


解决方案:双缓冲区


buffer.hpp(缓冲区设计)
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <cassert>
namespace hsl_log
{
//buff大小
#define MAX_BUFFER_SIZE 10*1024*1024//10MB
//阈值(软上限)
#define THRESHOLD_BUFFER_SIZE 1024*1024*1024//1024MB
//增量
#define INCREMENT_BUFFER_SIZE 10*1024*1024//10MB
class Buffer
{
private:
size_t _reader_idx;//当前可读数据的指针--本质是下标
size_t _writer_idx;//当前可写数据的指针--本质是下标
size_t _capacity;//缓冲区的总容量
std::vector<char> _buffer;//buffer缓冲区
public:
// 构造函数:初始化缓冲区容量(默认10MB)
Buffer(size_t capacity = MAX_BUFFER_SIZE)
: _reader_idx(0),
_writer_idx(0),
_capacity(capacity),
_buffer(_capacity) {}
// 判断缓冲区是否已满(写指针是否到达末尾)
bool full() { return _writer_idx == _capacity; }
// 判断缓冲区是否为空(读指针是否等于写指针)
bool empty() { return _reader_idx == _writer_idx; }
// 获取当前可读数据的大小
size_t readAbleSize() { return _writer_idx - _reader_idx; }
// 获取当前可写数据的大小
size_t writeAbleSize() { return _capacity - _writer_idx; }
// 重置(初始化 )缓冲区(读写指针归零)
void reset() { _reader_idx = _writer_idx = 0; }
// 交换两个缓冲区的所有内容(高效操作)
void swap(Buffer &buf)
{
_buffer.swap(buf._buffer);
std::swap(_reader_idx, buf._reader_idx);
std::swap(_writer_idx, buf._writer_idx);
std::swap(_capacity, buf._capacity);
}
// 写入数据到缓冲区
void push(const char *data, size_t len)
{
//阻塞,返回false
//assert(len <= writeAbleSize());
//扩容+软限制
ensure_enough_size(len);
//把数据拷贝进缓冲区
std::copy(data, data + len, &_buffer[_writer_idx]);
//将当前位置向后偏移
moveWriter(len);
}
// 获取当前可读数据的起始地址(用于直接读取)
const char *begin() { return &_buffer[_reader_idx]; }
// 从缓冲区移除已读数据(推进读指针)
void pop(size_t len)
{
moveReader(len);
assert(_reader_idx <= _capacity);
if (empty())
reset();
}
private:
//对写指针进行向后偏移操作
void moveWriter(size_t len){_writer_idx += len;}
//对读指针进行向后偏移操作
void moveReader(size_t len){_reader_idx += len;}
//确保足够大小
void ensure_enough_size(size_t len)
{
if(len<writeAbleSize())return;
size_t new_size =0;
if(_buffer.size()<THRESHOLD_BUFFER_SIZE)
{
new_size = _buffer.size()*2+len;//成倍增长
}
else
{
new_size = _buffer.size()+INCREMENT_BUFFER_SIZE+len;//线性增长
//return false;//到软限制就返回false阻塞
}
_buffer.reserve(new_size);
}
};
}
looper.hpp(异步线程模块)
cpp
#pragma once
#include "util.hpp"
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <functional>
#include "buffer.hpp"
namespace hsl_log
{
enum class AsyncType
{
ASYNC_SAFE,//安全状态,缓冲区满了就阻塞,避免资源耗尽的风险
ASYNC_UNSAFE,//非安全状态,用于性能测试
};
class AsyncLooper
{
public:
using Functor = std::function<void(Buffer &buffer)>; // 定义回调函数类型
using ptr = std::shared_ptr<AsyncLooper>; // 智能指针别名
AsyncLooper(const Functor &cb,AsyncType looper_type = AsyncType::ASYNC_SAFE )
:_looper_type(looper_type),
_running(true),
_looper_callback(cb),
_thread(std::thread(&AsyncLooper::worker_loop, this)) // 创建工作线程
{
}
~AsyncLooper() { stop(); } // 析构函数,自动停止处理器
void stop()
{
_running = false; // 设置运行标志为false
_pop_cond.notify_all(); // 通知所有等待的线程(唤醒所所有的消费线程)
_thread.join(); // 等待工作线程结束
}
void push(const std::string &msg)
{
if (_running == false)
return;
{
std::unique_lock<std::mutex> lock(_mutex);
// 等待直到缓冲区有足够空间写入新消息
if(_looper_type ==AsyncType::ASYNC_SAFE)
_push_cond.wait(lock, [&]{ return _tasks_push.writeAbleSize() >= msg.size(); });
// 将消息写入推送缓冲区
_tasks_push.push(msg.c_str(), msg.size());
}
// 唤醒消费者对缓冲区中的数据进行处理
_pop_cond.notify_all();
}
private:
// 线程入口函数--对消费缓冲区中的数据进行处理,处理完毕之后,初始化缓冲区,交换缓冲区
void worker_loop()
{
while (1)
{
// 1.判断生产缓冲区有无数据,有就交换,无就等待
// "{}"给锁设置生命周期
{
// 加锁
std::unique_lock<std::mutex> lock(_mutex);
// 检查停止条件:运行标志为false且生产缓冲区为空
if (_running == false && _tasks_push.empty())
{
return;
}
// 等待直到生产缓冲区有数据或处理器停止
_pop_cond.wait(lock, [&]{ return !_tasks_push.empty() || !_running; });
// 交换生产缓冲区和消费缓冲区
_tasks_push.swap(_tasks_pop);
}
// 2.通知生产者线程缓冲区有空闲空间,唤醒生产者
if(_looper_type ==AsyncType::ASYNC_SAFE)
_push_cond.notify_all(); // 通知生产者线程缓冲区有空闲空间
// 3.被唤醒后,对消费者缓冲区进行处理
_looper_callback(_tasks_pop); // 调用回调函数处理消费缓冲区中的数据
// 4.消息处理进行一个重置
_tasks_pop.reset(); // 重置消费缓冲区
}
return;
}
private:
Functor _looper_callback;
private:
AsyncType _looper_type;
std::mutex _mutex; // 保护共享数据的互斥锁
std::atomic<bool> _running; // 原子布尔值,控制循环是否继续运行
std::condition_variable _push_cond; // 生产者条件变量,用于等待缓冲区空间
std::condition_variable _pop_cond; // 消费者条件变量,用于等待缓冲区数据
Buffer _tasks_push; // 推送/生产缓冲区(生产者写入)
Buffer _tasks_pop; // 弹出/消费缓冲区(消费者读取)
std::thread _thread; // 工作线程
};
}

异步日志器模块(在logger.hpp中的一个类)
cpp
// 异步日志器
class AsyncLogger : public Logger
{
public:
using ptr = std::shared_ptr<AsyncLogger>;
AsyncLogger(const std::string &name,
Formatter::ptr formatter,
std::vector<LogSink::ptr> &sinks,
LogLevel::value level = LogLevel::value::DEBUG,
AsyncType looper_type= AsyncType::ASYNC_SAFE)
:Logger(name, formatter, sinks, level),
_looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::backendLogIt, this, std::placeholders::_1),looper_type))
{
std::cout << LogLevel::toString(level) << "异步日志器: " << name << "创建成功...\n";
}
protected:
void logIt(const std::string &msg) override
{
_looper->push(msg);
}
//实际落地函数,将缓冲区中的数据落地
void backendLogIt(Buffer &msg)
{
if (_sinks.empty())
{
return;
}
for (auto &it : _sinks)
{
it->log(msg.begin(), msg.readAbleSize());
}
}
protected:
AsyncLooper::ptr _looper;
};
难点(未实现):实现双向循环队列缓冲区时数据到达上限扩不扩容?
实际使用缓冲区时,无限制的扩容缓冲区是很危险的,当程序出现错误死循环时,会导致缓冲区一直刷数据,资源耗尽,程序/系统崩溃。
所以一开始想的是直接对buffer进行扩容,考虑到这一点就比较犹豫。
但如果不扩容,进行阻塞等待(返回值返回,由上层线程调用wait进行等待)的话,日志写入通常是后台操作,阻塞业务线程会降低系统整体性能。
所以到后面就想着既然不能一直扩容,也不能直接阻塞,那就两种方法结合起来用,最后采用了动态扩容 + 软上限 + 环形队列
- 做法:缓冲区满时自动扩容(如2倍增长),但设置软上限(如1024MB)。
- 软上限:达到软上限后再进行阻塞
- 环形缓冲区:覆盖最旧的日志(牺牲部分历史数据)。
项目中的做法:
缓冲区没满不管,缓冲区满时性能优先,自动扩容(如2倍增长),但设置软上限(如1024MB)。当缓冲区大小超过阈值(上限)后,采用线性增长策略
日志管理(单例)
日志管理器模块(在logger.hpp中的一个类)
cpp
//日志管理器:单例模式(懒汉)
class loggerManager
{
private:
std::mutex _mutex;
Logger::ptr _root_logger;
std::unordered_map<std::string, Logger::ptr> _loggers;
private:
loggerManager()
{
std::unique_ptr<LocalLoggerBuilder> slb(new LocalLoggerBuilder());
slb->buildLoggerName("root");
slb->buildLoggerType(Logger::Type::LOGGER_SYNC);
_root_logger = slb->build();
_loggers.insert(std::make_pair("root", _root_logger));
}
loggerManager(const loggerManager &) = delete;
loggerManager &operator=(const loggerManager &) = delete;
public:
static loggerManager &getInstance()
{
static loggerManager lm;
return lm;
}
bool hasLogger(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _loggers.find(name);
if (it == _loggers.end())
{
return false;
}
return true;
}
void addLogger(const std::string &name, const Logger::ptr logger)
{
std::unique_lock<std::mutex> lock(_mutex);
_loggers.insert(std::make_pair(name, logger));
}
Logger::ptr getLogger(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _loggers.find(name);
if (it == _loggers.end())
{
return Logger::ptr();
}
return it->second;
}
Logger::ptr rootLogger()
{
std::unique_lock<std::mutex> lock(_mutex);
return _root_logger;
}
};
提供全局接口&宏函数,方便用户使用

cpp
#pragma once
#include "logger.hpp"
namespace hsl_log
{
//1. 提供获取指定日志器的全局接口(避免用户自己操作单例对象)
Logger::ptr getLogger(const std::string &name) {
return loggerManager::getInstance().getLogger(name);
}
Logger::ptr rootLogger() {
return loggerManager::getInstance().rootLogger();
}
//2. 使用宏函数,对日志器的接口进行代理(代理模式)
#define debug(fmt, ...) debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define info(fmt, ...) info(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define warn(fmt, ...) warn(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define error(fmt, ...) error(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define fatal(fmt, ...) fatal(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
//提供宏函数,直接通过默认日志器进行日志的标准打印
#define LOG_DEBUG(logger, fmt, ...) (logger)->debug(fmt, ##__VA_ARGS__)
#define LOG_INFO(logger, fmt, ...) (logger)->info(fmt, ##__VA_ARGS__)
#define LOG_WARN(logger, fmt, ...) (logger)->warn(fmt, ##__VA_ARGS__)
#define LOG_ERROR(logger, fmt, ...) (logger)->error(fmt, ##__VA_ARGS__)
#define LOG_FATAL(logger, fmt, ...) (logger)->fatal(fmt, ##__VA_ARGS__)
#define LOGD(fmt, ...) LOG_DEBUG(hsl_log::rootLogger(), fmt, ##__VA_ARGS__)
#define LOGI(fmt, ...) LOG_INFO(hsl_log::rootLogger(), fmt, ##__VA_ARGS__)
#define LOGW(fmt, ...) LOG_WARN(hsl_log::rootLogger(), fmt, ##__VA_ARGS__)
#define LOGE(fmt, ...) LOG_ERROR(hsl_log::rootLogger(), fmt, ##__VA_ARGS__)
#define LOGF(fmt, ...) LOG_FATAL(hsl_log::rootLogger(), fmt, ##__VA_ARGS__)
}