同步/异步日志系统

同步/异步日志系统

源码地址:点这里

项目演示

基础测试

打印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, &lt);
            char tmp[128];
            strftime(tmp, 127, _time_fmt.c_str(), &lt);
            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, &lt);
            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__)
    }
相关推荐
zhczzm2 小时前
深入浅出之STL源码分析2_stl与标准库,编译器的关系
c++
Darkwanderor4 小时前
c++STL-string的模拟实现
c++·string
南风与鱼4 小时前
【数据结构】红黑树(C++)
c++·红黑树
李匠20244 小时前
C++GO语言微服务和服务发现②
开发语言·c++·golang·服务发现
虾球xz5 小时前
游戏引擎学习第271天:生成可行走的点
c++·学习·游戏引擎
qq_433554545 小时前
C++ STL编程 vector空间预留、vector高效删除、vector数据排序、vector代码练习
开发语言·c++
XiaoCCCcCCccCcccC5 小时前
Linux网络基础 -- 局域网,广域网,网络协议,网络传输的基本流程,端口号,网络字节序
linux·c语言·网络·c++·网络协议
菜狗想要变强6 小时前
C++ STL入门:vecto容器
开发语言·c++
五花肉村长6 小时前
Linux-Ext系列文件系统
linux·运维·服务器·c++·笔记·visual studio
weixin_428498497 小时前
在Lua中使用轻量级userdata在C/C++之间传递数据和调用函数
c语言·c++·lua