同步/异步日志系统:工具类以及日志的简单模块

前面我们已经了解过同步/异步日志系统的模块划分。本期我们就来动手编写代码

相关代码已经上传至作者个人gitee:同步_异步日志: 本项⽬主要实现⼀个⽇志系统, 其主要⽀持以下功能: 1、⽀持多级别⽇志消息 2、⽀持同步⽇志和异步⽇志 3、⽀持可靠写⼊⽇志到控制台、⽂件以及滚动⽂件中 4、⽀持多线程程序并发写⽇志 5、⽀持扩展不同的⽇志落地⽬标地

目录

工具类实现

日志等级模块

日志消息模块

格式化子项模块

日志输出格式化

格式化日志

日志格式化器

格式化日志信息

创建格式化子项

格式化解析


工具类实现

提前完成一些零碎的功能接口,以便于项目中会用到。

• 获取系统时间

• 判断文件是否存在

• 获取文件的所在目录路径

• 创建目录

util.hpp

cpp 复制代码
/*
    工具类相关的代码:
    • 获取系统时间
    • 判断⽂件是否存在
    • 获取⽂件的所在⽬录路径
    • 创建目录
*/ 
#pragma once
#include <chrono>
#include <string>
#include <ctime>

namespace Logger
{
    namespace util
    {
        class Date
        {
            public:
                // 获取当前时间点(高精度)
                static std::chrono::system_clock::time_point Now();

                // 获取当前时间的 time_t 表示(秒级)
                static std::time_t NowAsTimeT();

                // 获取格式化的当前时间字符串(默认格式:YYYY-MM-DD HH:MM:SS)
                static std::string NowAsString(const std::string& format = "%Y-%m-%d %H:%M:%S");
        };
        class File
        {
            public:
                // 判断文件是否存在
                static bool IsExist(const std::string& path);
                // 获取文件所在目录路径
                static std::string GetPath(const std::string& path);
                // 创建目录(如果目录已存在则返回 false)
                static bool CreateDirectory(const std::string& path);
        };
    } // namespace util
    
} // namespace Loggerclass Date

util.cpp

cpp 复制代码
#include "util.hpp"
#include <ctime>
#include <iomanip>
#include <sstream>
#include <filesystem>

namespace Logger 
{
    namespace util 
    {

        std::chrono::system_clock::time_point Date::Now() 
        {
            return std::chrono::system_clock::now();
        }

        std::time_t Date::NowAsTimeT() 
        {
            auto now = std::chrono::system_clock::now();
            return std::chrono::system_clock::to_time_t(now);
        }

        std::string Date::NowAsString(const std::string& format) 
        {
            auto now = std::chrono::system_clock::now();
            std::time_t tt = std::chrono::system_clock::to_time_t(now);
            std::tm tm = *std::localtime(&tt); // 注意线程安全性,可用 localtime_s 替代

            std::ostringstream oss;
            oss << std::put_time(&tm, format.c_str());
            return oss.str();
        }
        bool File::IsExist(const std::string& path) 
        {
            return std::filesystem::exists(path);
        }
        std::string File::GetPath(const std::string& path) 
        {
            return std::filesystem::path(path).parent_path().string();
        }
        bool File::CreateDirectory(const std::string& path) 
        {
            return std::filesystem::create_directories(path);
        }
    } // namespace util
} // namespace Logger

日志等级模块

⽇志等级总共分为7个等级,分别为:

• OFF 关闭所有⽇志输出

• DEBUG 进⾏debug时候打印⽇志的等级

• INFO 打印⼀些⽤⼾提⽰信息

• WARN 打印警告信息

• ERROR 打印错误信息

• FATAL 打印致命信息- 导致程序崩溃的信息

• UNKOWN 未知信息

Level.hpp

cpp 复制代码
/*
    日志等级
    • OFF:关闭日志记录,不输出任何日志信息。
    • DEBUG:调试信息,开发阶段使用,记录详细的程序运行信息。
    • INFO:普通信息,记录程序运行中的普通信息。
    • WARNING:警告信息,记录程序运行中的警告信息。
    • ERROR:错误信息,记录程序运行中的错误信息。
    • FATAL:致命错误信息,记录程序运行中的致命错误信息。
    • UNKNOWN:未知日志等级,表示未定义或无法识别的日志等级。
    每一个项目中都会设置一个默认的日志输出等级,只有输出的日志等级大于等于默认限制等级的时候才可以进行输出
    提供接口,将枚举转化为字符串表示。
*/
#pragma once
#include <string>
namespace Logger
{
    class LogLevel
    {
        public:
            enum class Value
            {
                UNKNOWN = 0,
                OFF,
                DEBUG,
                INFO,
                WARNING,
                ERROR,
                FATAL
            };
            static std::string ToString(Value value);

    };
} // namespace Logger 

Level.cpp

cpp 复制代码
#include "Level.hpp"
namespace Logger
{
    std::string LogLevel::ToString(LogLevel::Value value) 
    {
        switch (value) 
        {
            case LogLevel::Value::OFF: return "OFF";
            case LogLevel::Value::DEBUG: return "DEBUG";
            case LogLevel::Value::INFO: return "INFO";
            case LogLevel::Value::WARNING: return "WARNING";
            case LogLevel::Value::ERROR: return "ERROR";
            case LogLevel::Value::FATAL: return "FATAL";
            default: return "UNKNOWN";
        }
    }
} // namespace Logger

日志消息模块

日志消息类主要是封装⼀条完整的日志消息所需的内容,其主要包括以下内容:

  1. 日志的输出时间 用于过滤日志输出时间

  2. 日志等级 用于进行日志过滤分析

  3. 源文件名称

  4. 源代码行号 用于定位出现错误的代码位置

  5. 线程ID 用于过滤出错的线程

  6. 日志主体消息

  7. 日志器名称 (当前支持多日志器的同时使用)

例如:

2003-08-16 12:38:26\]\[root\]\[12345678\]\[main.c:99\]\[FATAL\]: 创建套接字失败... ```cpp /* 日志消息类: 1. 日志的输出时间 用于过滤日志输出时间 2. 日志等级 用于进行日志过滤分析 3. 源文件名称 4. 源代码行号 用于定位出现错误的代码位置 5. 线程ID 用于过滤出错的线程 6. 日志主体消息 7. 日志器名称 (当前支持多日志器的同时使用) */ #pragma once #include "util.hpp" #include "Level.hpp" #include #include #include namespace Logger { class Message { private: size_t time_; // 日志时间戳 LogLevel::Value level_; // 日志等级 std::string file_; // 源文件名称 size_t line_; // 源代码行号 std::thread::id thread_id_; // 线程ID std::string message_; // 日志主体消息 std::string logger_name_; // 日志器名称 public: Message(LogLevel::Value level, size_t line, const std::string& file, const std::string& message, const std::string& logger_name) : level_(level), time_(util::Date::NowAsTimeT()), line_(line), thread_id_(std::this_thread::get_id()), file_(file), message_(message), logger_name_(logger_name) {} }; } ``` ## 格式化子项模块 格式化子项的作用:从日志消息中取出指定的元素,追加到一块内存空间中 设计思想: 1. 抽象一个格式化子项基类 2. 基于基类,派生出不同的格式化子项子类 主体消息、日志等级、时间子项、文件名、行号、日志器名称、线程ID、制表符、换行、其他这样就可以在父类中定义父类指针的数组,指向不同的格式化子项子类对象 ```cpp /* 日志消息类: 1. 日志的输出时间 用于过滤日志输出时间 2. 日志等级 用于进行日志过滤分析 3. 源文件名称 4. 源代码行号 用于定位出现错误的代码位置 5. 线程ID 用于过滤出错的线程 6. 日志主体消息 7. 日志器名称 (当前支持多日志器的同时使用) */ #pragma once #include "util.hpp" #include "Level.hpp" #include #include #include namespace Logger { struct Message { std::time_t time_; // 日志时间戳 LogLevel::Value level_; // 日志等级 std::string file_; // 源文件名称 size_t line_; // 源代码行号 std::thread::id thread_id_; // 线程ID std::string message_; // 日志主体消息 std::string logger_name_; // 日志器名称 Message(LogLevel::Value level, size_t line, const std::string& file, const std::string& message, const std::string& logger_name) : level_(level), time_(util::Date::NowAsTimeT()), line_(line), thread_id_(std::this_thread::get_id()), file_(file), message_(message), logger_name_(logger_name) {} }; } ``` ## 日志输出格式化 ### 格式化日志 format.hpp ```cpp namespace Logger { //抽象接口基类 class FormatItem { public: using ptr = std::shared_ptr; virtual ~FormatItem() = default; virtual void Format(std::ostream& os, const Message& msg) = 0; }; // 派生格式化子项子类--消息、等级、时间、文件名、行号、线程ID、日志器名、制表符、换行、其他 class MessageFormatItem : public FormatItem { public: void Format(std::ostream& os, const Message& msg) override; }; class LevelFormatItem : public FormatItem { public: void Format(std::ostream& os, const Message& msg) override; }; class DateTimeFormatItem : public FormatItem { public: DateTimeFormatItem(const std::string& format = "%Y-%m-%d %H:%M:%S"); void Format(std::ostream& os, const Message& msg) override; private: std::string time_fmt_;//%H:%M:%S }; class FileNameFormatItem : public FormatItem { public: void Format(std::ostream& os, const Message& msg) override; }; class LineFormatItem : public FormatItem { public: void Format(std::ostream& os, const Message& msg) override; }; class ThreadIdFormatItem : public FormatItem { public: void Format(std::ostream& os, const Message& msg) override; }; class LoggerNameFormatItem : public FormatItem { public: void Format(std::ostream& os, const Message& msg) override; }; class TabFormatItem : public FormatItem { public: void Format(std::ostream& os, const Message& msg) override; }; class NewLineFormatItem : public FormatItem { public: void Format(std::ostream& os, const Message& msg) override; }; class OtherFormatItem : public FormatItem { public: OtherFormatItem(const std::string& str); void Format(std::ostream& os, const Message& msg) override; private: std::string str_; }; } ``` format.cpp ```cpp #include"format.hpp" namespace Logger { void MessageFormatItem::Format(std::ostream& os, const Message& msg) { os << msg.message_; } void LevelFormatItem::Format(std::ostream& os, const Message& msg) { os << LogLevel::ToString(msg.level_); } DateTimeFormatItem::DateTimeFormatItem(const std::string& format = "%H:%M:%S") : time_fmt_(format) {} void DateTimeFormatItem::Format(std::ostream& os, const Message& msg) { struct tm tm_time; localtime_r((time_t*)&msg.time_, &tm_time); char time_str[64]; strftime(time_str, sizeof(time_str), time_fmt_.c_str(), &tm_time); os << time_str; } void FileNameFormatItem::Format(std::ostream& os, const Message& msg) { os << msg.file_; } void LineFormatItem::Format(std::ostream& os, const Message& msg) { os << msg.line_; } void ThreadIdFormatItem::Format(std::ostream& os, const Message& msg) { os << msg.thread_id_; } void LoggerNameFormatItem::Format(std::ostream& os, const Message& msg) { os<< msg.logger_name_; } void TabFormatItem::Format(std::ostream& os, const Message& msg) { os << "\t"; } void NewLineFormatItem::Format(std::ostream& os, const Message& msg) { os << std::endl; } OtherFormatItem::OtherFormatItem(const std::string& str) : str_(str) {} void OtherFormatItem::Format(std::ostream& os, const Message& msg) { os << str_; } } ``` ### 日志格式化器 格式转化器需要对不同形式格式化的进行解析处理。 > %d 表示日期 ,包含子格式 {%H: %M: %S} > > %t 表示线程ID > > %c 表示日志器名称 > > %f 表示源码文件名 > > %l 表示源码行号 > > %p 表示日志级别 > > %T 表示制表符缩进 > > %m 表示主体消息 > > %n 表示换行 声明如下: ```cpp // 日志格式化器类 class Formatter { /* %d 表示日期 ,包含子格式 {%H: %M: %S} %t 表示线程ID %c 表示日志器名称 %f 表示源码文件名 %l 表示源码行号 %p 表示日志级别 %T 表示制表符缩进 %m 表示主体消息 %n 表示换行 */ public: Formatter(const std::string& pattern="[]"); // 格式化日志消息 void Format(std::ostream& os, const Message& msg); std::string Format(const Message& msg); // 解析格式化模式字符串 bool ParsePattern(const std::string& pattern); private: // 根据不同的格式化项类型创建不同的格式化子项 FormatItem::ptr CreateFormatItem(const std::string& key,const std::string& val); std::string pattern_; // 格式化模式字符串 std::vector items_; // 格式化子项列表 }; ``` #### 格式化日志信息 ```cpp #include"format.hpp" namespace Logger { void Formatter::Format(std::ostream& os, const Message& msg) { for (auto& item : items_) { item->Format(os, msg); } } std::string Formatter::Format(const Message& msg) { std::ostringstream oss; Format(oss, msg); return oss.str(); } } ``` #### 创建格式化子项 ```cpp FormatItem::ptr Formatter::CreateFormatItem(const std::string& key,const std::string& val) { if(key=="d")//日期格式化字符,包含子格式 {%H: %M: %S} { return std::make_shared(val); } if(key=="t")//线程ID格式化字符 { return std::make_shared(); } if(key=="c")//日志器名称格式化字符 { return std::make_shared(); } if(key=="f")//源码文件名格式化字符 { return std::make_shared(); } if(key=="l")//源码行号格式化字符 { return std::make_shared(); } if(key=="p")//日志级别格式化字符 { return std::make_shared(); } if(key=="T")//制表符缩进格式化字符 { return std::make_shared(); } if(key=="m")//主体消息格式化字符 { return std::make_shared(); } if(key=="n")//换行格式化字符 { return std::make_shared(); } // 如果key不是支持的格式字符,创建OtherFormatItem if(key.empty()) std::make_shared(key); std::cerr<<"没有对应的格式化字符:%"< namespace Logger { bool Formatter::ParsePattern(const std::string& pattern) { //1、解析格式化模式字符串 std::vector> format_items; // 存储解析后的格式化项,包含key和val size_t pos=0; std::string key, val; bool error_flag = false; while(pos < pattern.size()) { // 1. 处理原始字符串 - 判断是否是%,不是就是原始字符 if (pattern[pos] != '%') { val.push_back(pattern[pos++]); continue; } // 能走下来就代表pos位置就是%字符,%%处理称为一个原始%字符 if (pos + 1 < pattern.size() && pattern[pos + 1] == '%') { val.push_back('%'); pos += 2; continue; } // 能走下来,代表%后边是个格式化字符,代表原始字符串处理完毕 if(!val.empty()) { format_items.push_back(std::make_pair("", val)); val.clear(); } pos += 1; if(pos==pattern.size())//pos指向了字符串的末尾,代表格式化模式字符串处理完毕 { std::cout<<"%后没有对应的格式化字符"<

相关推荐
王老师青少年编程2 小时前
动态规划之【树形DP】第4课:树形DP应用案例实践3
c++·动态规划·dp·树形dp·csp·信奥赛·提高组
corpse20102 小时前
VirtualBox 安装ubuntu-25 ,配置SSH工具登录
linux·ubuntu·ssh
杜子不疼.2 小时前
浏览器秒连服务器!WebSSH 实战体验,远程运维再也不折腾
运维·服务器·人工智能
她说彩礼65万2 小时前
C语言 整形提升及算数转换
linux·服务器·c语言
loockluo2 小时前
NFS网络存储部署与性能优化实战:家用服务器的学习与实践
服务器·网络·性能优化
RenPenry2 小时前
2026 在Linux上搭建CS2插件服务器
linux·运维·服务器·cs2·debian13
流年笙歌_2 小时前
(超详细)手把手教你安装银河麒麟高级服务器操作系统 V11
运维·服务器
爱学习的小囧2 小时前
VCF 私有 AI 服务(PAIS)自签名 TLS 证书配置避坑指南
服务器·esxi·虚拟化·vcf
微学AI2 小时前
code-server 体验:一行命令把 VS Code 跑在服务器上,随时用浏览器写代码
运维·服务器