当前位置: 首页> 文旅> 旅游 > 网站推广东莞_管理咨询的作用_网站制作需要多少钱_app联盟推广平台

网站推广东莞_管理咨询的作用_网站制作需要多少钱_app联盟推广平台

时间:2025/7/10 0:51:07来源:https://blog.csdn.net/qq_67693066/article/details/147162921 浏览次数:0次
网站推广东莞_管理咨询的作用_网站制作需要多少钱_app联盟推广平台

C++项目 —— 基于多设计模式下的同步&异步日志系统(1)

  • 工具类(utils)实现
  • struct stat 文件或目录状态信息结构体
      • 1. `struct stat` 的定义
      • 2. 代码中 `struct stat` 的作用
      • 3. `struct` 的用法总结
      • 4. 注意事项
  • 日志消息类的具体实现
    • 日志等级类
  • 日志消息格式化类
        • 格式化项(FormatItem子类)
        • 日志消息结构(LogMsg)
        • 工作流程示例
    • 格式化子项类实现
    • 日志消息格式化类实现
  • 一些小点
    • localtime_r
    • strftime
      • 函数原型
      • 2. **格式化符号对照表**

我们今天来用C++来做一个项目:基于多设计模式下的同步&异步日志系统,我们首先将重点放在日志系统上,我们这次实现的日志系统主要有以下的功能:

1.支持多级别日志消息
2.支持同步日志和异步日志
3.支持可靠写入日志到控制台、文件以及滚动文件中
4.支持多线程程序并发写日志
5.支持扩展不同的日志落地目标地

我们首先看一下这些功能,这次涉及到的技术不算很广:C++,Linux多线程,文件操作

我们可以根据这些要求,把各个模块之间的关系做出一些大概的梳理:

在这里插入图片描述
我们首先关注上面的功能的实现:

在这里插入图片描述

工具类(utils)实现

日志系统要经常创建文件,我们可以把这个功能封装起来,为之后的程序编写提供便利:

#ifndef __M_UTIL_H__
#define __M_UTIL_H__// 工具的实现
namespace logs
{namespace utils{class Date{public:static size_t get_time(){// 获取时间return (size_t)time(nullptr);}};// 关于文件的一些操作class File{public:// 判断文件是否存在static bool exist(const std::string &pathname){struct stat st;if (stat(pathname.c_str(), &st) < 0){return false;}return true;}// 获取路径名字static std::string path(const std::string &pathname){size_t pos = pathname.find_last_of("/\\"); // 找到最后一个反斜杠if (pos == std::string::npos){return ".";}return pathname.substr(0, pos + 1);}// 创建文件(递归创建文件)static void createDiretory(const std::string &pathname){size_t pos, idx = 0;while (idx < pathname.size()){// 找到第一个反斜杠pos = pathname.find_first_of("/\\", idx);if (pos == std::string::npos){mkdir(pathname.c_str(), 0777);return;}// 截取父级目录std::string parent_dir = pathname.substr(0, pos + 1);//判断父级目录是否存在if(exist(parent_dir) == true){idx = pos + 1;continue;}//如果父级目录不存在,就创建父级目录mkdir(parent_dir.c_str(),0777);idx = pos + 1;}}};}
}
#endif

这里介绍一下这里面的一些新用法:

struct stat 文件或目录状态信息结构体

Linux 2号手册可以查到对应的信息:
在这里插入图片描述
在这段代码中,struct stat 是 C/C++ 中用于获取文件或目录状态信息的一个结构体。它的用法可以分解如下:

1. struct stat 的定义

struct stat 是 POSIX 标准中的一个结构体,定义在头文件 <sys/stat.h> 中。它用来存储文件或目录的元信息(如大小、权限、修改时间等)。以下是 struct stat 的常见成员(具体字段可能因操作系统而异):

struct stat {dev_t     st_dev;      // 文件所在的设备 IDino_t     st_ino;      // inode 号mode_t    st_mode;     // 文件类型和权限nlink_t   st_nlink;    // 硬链接数uid_t     st_uid;      // 文件所有者的用户 IDgid_t     st_gid;      // 文件所有者的组 IDdev_t     st_rdev;     // 特殊文件的设备 IDoff_t     st_size;     // 文件大小(字节数)time_t    st_atime;    // 最后访问时间time_t    st_mtime;    // 最后修改时间time_t    st_ctime;    // 最后状态改变时间blksize_t st_blksize;  // 文件系统 I/O 块大小blkcnt_t  st_blocks;   // 分配的块数
};

2. 代码中 struct stat 的作用

在这段代码中,struct stat st; 定义了一个名为 st 的变量,用来存储通过 stat() 函数获取的文件或目录的状态信息。

  • stat() 函数

    • 原型:int stat(const char *pathname, struct stat *buf);
    • 功能:根据路径 pathname 获取文件或目录的状态信息,并将其填充到 buf 指向的 struct stat 结构体中。
    • 返回值:
      • 成功时返回 0。
      • 失败时返回 -1,并设置 errno 表示错误原因。
  • 代码逻辑

    struct stat st;
    if (stat(pathname.c_str(), &st) < 0)
    {return false;  // 如果 stat 调用失败,返回 false
    }
    return true;       // 如果 stat 调用成功,返回 true
    
    • stat(pathname.c_str(), &st)
      • pathname 转换为 C 风格字符串(const char*),并调用 stat() 获取其状态信息。
      • 如果 stat() 返回值小于 0,说明发生了错误(例如文件不存在或没有权限),函数返回 false
      • 如果 stat() 成功,则返回 true

3. struct 的用法总结

  • struct 是 C/C++ 中用于定义结构体的关键字。
  • 在这段代码中,struct stat 表示一个结构体类型,st 是该类型的变量。
  • stat() 函数通过填充 struct stat 类型的变量来提供文件或目录的详细信息。

4. 注意事项

  • 头文件:使用 struct statstat() 函数时,需要包含以下头文件:

    #include <sys/stat.h>  // 定义 struct stat 和 stat()
    #include <cstring>     // 如果需要处理字符串(如 pathname.c_str())
    #include <cerrno>      // 如果需要检查 errno
    
  • 错误处理:如果 stat() 返回 -1,可以通过检查 errno 来确定具体的错误原因。例如:

    #include <cerrno>
    #include <iostream>
    if (stat(pathname.c_str(), &st) < 0)
    {std::cerr << "Error: " << strerror(errno) << std::endl;return false;
    }
    

日志消息类的具体实现

对于日志系统来说,主体就是日志消息:
在这里插入图片描述

日志等级类

日志消息会有一个等级,这里等级我们封装为一个类,方便我们在其他地方使用:

namespace logs
{class Loglevel{public:enum class value{UNKOWN = 0,DEBUG,INFO,WARN,ERROR,FATAL,OFF};//将类型转换为字符串static const char* toString(Loglevel::value level){switch(level){case Loglevel::value::DEBUG: return "DEBUG";case Loglevel::value::ERROR: return "DEBUG";case Loglevel::value::FATAL: return "DEBUG";case Loglevel::value::INFO: return "DEBUG";case Loglevel::value::OFF:  return "OFF";case Loglevel::value::WARN: return "WARN";}return "UNKOWN";}};
}

在此基础上我们完成日志消息类的构造:

#ifdef __M_MEASSAGE_H__
#define __M_MEASSAGE_H__
#include "utils.hpp"
#include "level.hpp"
#include <ctime>
#include <memory>
#include <thread>namespace logs
{struct logMsg{size_t _ctime; //时间戳Loglevel::value _level; //日志等级std::string _file_name; //源文件名称size_t _line; //行号std::thread::id _tid; //线程名称std::string _playload; //日志器主体消息std::string _logger_name; //日志器名称logMsg(Loglevel::value level,const std::string file_name,size_t line,const std::string playload,const std::string logger_name):_ctime(logs::utils::Date::get_time()),_level(level),_file_name(file_name),_line(line),_tid(std::this_thread::get_id()),playload(_playload),_logger_name(logger_name){}};
}
#endif

日志消息格式化类

格式化类主要就是对消息进行格式化处理,处理成我们想要的格式类型:
在这里插入图片描述
因为我们这里一条消息包含的格式化子项很多,而且类型也不同,所以我们会考虑用一个基类的数组来存储这些不同的子项

pattern成员

  • 存储日志格式字符串(如 "[%d{%H:%M:%S}] %m%n"
  • 支持以下格式标记:
标记说明对应数据
%d日期时间LogMsg::_ctime
%T制表符缩进固定\t
%t线程IDLogMsg::_tid
%p日志级别LogMsg::_level
%c日志器名称LogMsg::_name
%f源码文件名LogMsg::_file
%l源码行号LogMsg::_line
%m日志消息内容LogMsg::_payload
%n换行符固定\n

格式化项(FormatItem子类)
类名功能描述输出示例
MsgFormatItem提取日志消息内容“创建套接字失败”
LevelFormatItem提取日志级别“ERROR”
NameFormatItem提取日志器名称“root”
ThreadFormatItem提取线程ID“0x1234”
TimeFormatItem格式化时间戳“14:30:45”
FileFormatItem提取源码文件名“main.cpp”
LineFormatItem提取源码行号“42”
TabFormatItem插入制表符\t
NewLineFormatItem插入换行符\n
OtherFormatItem原样输出非格式字符串“[” 或 “]”

日志消息结构(LogMsg)
struct LogMsg {size_t _line;           // 行号(如:22)time_t _ctime;          // 时间戳(如:12345678)std::thread::id _tid;   // 线程ID(如:0x12345678)std::string _name;      // 日志器名称(如:"logger")std::string _file;      // 文件名(如:"main.cpp")std::string _payload;   // 日志内容(如:"创建套接字失败")LogLevel::value _level; // 日志级别(如:ERROR)
};

工作流程示例

输入格式
"[%d{%H:%M:%S}] %m%n"

解析结果

items = {{OtherFormatItem(), "["},      // 原样输出"["{TimeFormatItem(), "%H:%M:%S"}, // 格式化时间{OtherFormatItem(), "] "},     // 原样输出"] "{MsgFormatItem(), ""},         // 提取消息内容{NewLineFormatItem(), ""}      // 换行
};

输出结果

[14:30:45] 创建套接字失败

在这之前我们要先实现格式化子项类的实现:

格式化子项类实现

在这里插入图片描述

   class FometterItem{public:using ptr = std::shared_ptr<FometterItem>;                           // 重命名virtual void format(std::ostream &ost, const logMsg &Msg) = 0; // 接口};// 派生格式化子类基项--消息,等级,时间,行号,线程ID,日志器名称,制表符,换行,其他class MsgFormetItem : public FometterItem{public:void format(std::ostream &out, const logMsg &Msg) override{out << Msg._playload;}};class LevelFormetItem : public FometterItem{public:void format(std::ostream &out, const logMsg &Msg) override{out << Loglevel::toString(Msg._level);}};class TimeFormetItem : public FometterItem{public:TimeFormetItem(const std::string &fmt = "%H:%M:%S"): _time_fmt(fmt){}void format(std::ostream &out, const logMsg &Msg) override{struct tm t;localtime_r(&Msg._ctime, &t);char tmp[32] = {0};strftime(tmp, 31, _time_fmt.c_str(), &t);out << tmp;}private:std::string _time_fmt; //%H:%M:%S};class FileFormetItem : public FometterItem{public:void format(std::ostream &out, const logMsg &Msg) override{out << Msg._file_name;}};class LineFormetItem : public FometterItem{public:void format(std::ostream &out, const logMsg &Msg) override{out << Msg._line;}};class ThreadFormetItem : public FometterItem{public:void format(std::ostream &out, const logMsg &Msg) override{out << Msg._tid;}};class LoggerFormetItem : public FometterItem{public:void format(std::ostream &out, const logMsg &Msg) override{out << Msg._logger_name;}};class TabFormetItem : public FometterItem{public:void format(std::ostream &out, const logMsg &Msg) override{out << "\t";}};class NLineFormetItem : public FometterItem{public:void format(std::ostream &out, const logMsg &Msg) override{out << "\n";}};class OtherFormetItem : public FometterItem{public:OtherFormetItem(const std::string &str): _str(str){}void format(std::ostream &out, const logMsg &Msg) override{out << _str;}private:std::string _str;};

格式项子类主要的功能就是从对应的msg消息主题中找到自己对应的那部分,然后把这个部分放到一块空间中

日志消息格式化类实现

日志消息格式化类实现主要是:我们会根据自己的需求去创建一个我们想要的日志格式,日志消息格式化类会把我们输入的格式化字符串进行分类别处理,然后会有一个基类数组存储这些不同的子类,最后,我们遍历这个父类数组,让数组成员调用他们各自的format函数完成打印。
在这里插入图片描述

   class Formetter{public:using ptr = std::shared_ptr<Formetter>;Formetter(const std::string &pattenstr = "[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n"): _pattenstr(pattenstr){// 处理消息字符串assert(parsePatten());}std::string format(const logMsg &msg){std::stringstream ss;format(ss, msg);return ss.str();}void format(std::ostream &ost, const logMsg &msg){for (auto &it : _item){it->format(ost, msg);}}private:// 消息字符串处理函数bool parsePatten(){std::vector<std::pair<std::string, std::string>> fmt_order;size_t pos = 0;std::string key, val;while (pos < _pattenstr.size()){// 1.处理原始字符串if (_pattenstr[pos] != '%'){val.push_back(_pattenstr[pos++]);continue;}// 处理双百分号情况if (pos < _pattenstr.size() && _pattenstr[pos + 1] == '%'){val.push_back('%');pos += 2;continue;}// 处理完原始字符串,将原始字符串if (val.empty() == false){fmt_order.push_back(std::make_pair("", val));val.clear(); // 清空值,为后面的格式化字符串做铺垫}// 2.格式化字串格式了,指向%pos += 1;if (pos == _pattenstr.size()){std::cout << "%之后,没有对应格式化的格式化字符串\n";return false;}key = _pattenstr[pos];pos += 1;                                              // 检查是否有字串格式if (pos < _pattenstr.size() && _pattenstr[pos] == '{') // 此时就是一个字串格式{pos += 1; // 指向字串格式的内容while (pos < _pattenstr.size() && _pattenstr[pos] != '}'){val.push_back(_pattenstr[pos++]);} // 格式化字串处理完毕// 走到了末尾,跳出了循环if (pos == _pattenstr.size()){std::cout << "子规则匹配出错!{}\n";return false; // 没有找到},代表格式是错误的}pos += 1; // 到了一个新的处理位置}// 将字串格式作为valfmt_order.push_back(std::make_pair(key, val));key.clear();val.clear();}for (auto &it : fmt_order){_item.push_back(createItem(it.first, it.second));}return true;}// 根据不同的格式化字符创建不同的格式化子项对象FometterItem::ptr createItem(const std::string &key, const std::string &val){/*%d 表示日期 包含子格式{%H:%M:%S}%t 线程id%c 表示日志器名称%f 表示源码文件名%l 表示源码行号%p 表示日志级别%T 表示缩进%m 表示主体消息%n 表示换行*/if (key == "d")return std::make_unique<TimeFormetItem>(val);if (key == "t")return std::make_unique<ThreadFormetItem>();if (key == "c")return std::make_unique<LoggerFormetItem>();if (key == "f")return std::make_unique<FileFormetItem>();if (key == "l")return std::make_unique<LineFormetItem>();if (key == "p")return std::make_unique<LevelFormetItem>();if (key == "T")return std::make_unique<TabFormetItem>();if (key == "m")return std::make_unique<MsgFormetItem>();if (key == "n")return std::make_unique<NLineFormetItem>();if (key == "")return std::make_unique<OtherFormetItem>(val);std::cout << "没有对应的格式化字符: %" << key << std::endl;abort();return FometterItem::ptr();}std::string _pattenstr; // 格式化字符串std::vector<FometterItem::ptr> _item;};

一些小点

localtime_r

在这里插入图片描述

localtime_r 是 C/C++ 标准库中用于将时间戳(time_t)转换为本地时间(struct tm)的线程安全函数。它是 localtime 的线程安全版本,常用于多线程环境:

struct tm *localtime_r(const time_t *timer, struct tm *result);

strftime

strftime 是 C/C++ 标准库中用于将时间信息(struct tm)格式化为字符串的函数。它是 “string format time” 的缩写,属于 <ctime> 头文件。以下是详细说明:


函数原型

size_t strftime(char* str, size_t count, const char* format, const struct tm* timeptr);
  • 参数
    • str:输出缓冲区(存放结果字符串)
    • count:缓冲区最大容量(防止溢出)
    • format:格式化字符串(类似 printf 的格式)
    • timeptr:指向 tm 结构体的指针
  • 返回值:成功时返回写入的字符数(不含终止符),失败返回 0

2. 格式化符号对照表

符号说明示例
%Y四位年份2023
%y两位年份23
%m月份(01-12)07
%d日(01-31)15
%H24小时制小时(00-23)14
%M分钟(00-59)05
%S秒(00-61)30
%A完整星期名Monday
%a缩写星期名Mon
%B完整月份名July
%b缩写月份名Jul
%c本地日期时间表示Mon Jul 15 14:05:30 2023
%%百分号字符%

这样我们就可以理解这段代码了:

void format(std::ostream &out, const logMsg &Msg) override{struct tm t;localtime_r(&Msg._ctime, &t);char tmp[32] = {0};strftime(tmp, 31, _time_fmt.c_str(), &t);out << tmp;}
关键字:网站推广东莞_管理咨询的作用_网站制作需要多少钱_app联盟推广平台

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: