标准日志插件项目【C/C++】

news2024/10/28 2:47:55

   博客主页:花果山~程序猿-CSDN博客

文章分栏:项目日记_花果山~程序猿的博客-CSDN博客

关注我一起学习,一起进步,一起探索编程的无限可能吧!让我们一起努力,一起成长!

在这里插入图片描述

目录

一,项目介绍

日志插件技术实现

同步日志

异步日志

相关技术补充

C不定参宏

C不定参函数

C++不定参函数

设计模式

工厂模式

建造者模式

代理模式

项目模块设计

日志框架设计 

日志格式模块(format)

日志落地模块(sink)

日志管理器模块(logger)

 扩展

双缓冲区异步任务处理器(Asynlogger)

异步日志器模块

日志宏

性能测试

模块关系图

改bug心得

结语


嗨!收到一张超美的图,愿你每天都能顺心!

 

一,项目介绍

  • 企业开发中对于运行中的程序不适合使用调试器调试。⽣产环境的产品为了保证其稳定性及安全性是不允许开发⼈员附加调试器去排查问题, 可以借助⽇志系统来打印⼀些⽇志帮助开发⼈员解决问题。
  • 问题无法复现。上线客⼾端的产品出现bug⽆法复现并解决, 可以借助⽇志系统打印⽇志并上传到服务端帮助开发⼈员进⾏分析。
  • 对于⼀些⾼频操作(如定时器、⼼跳包)在少量调试次数下可能⽆法触发我们想要的⾏为,通过断点的暂停⽅式,我们不得不重复操作⼏⼗次、上百次甚⾄更多,导致排查问题效率是⾮常低下, 可 以借助打印⽇志的⽅式查问题 。
  • 分布式、多线程/多进程代码中, 出现bug⽐较难以定位, 可以借助⽇志系统打印log帮助定位bug。

本项⽬主要实现⼀个⽇志系统, 其主要⽀持以下功能:

  • 支持日志等级选择
  • 支持用户(开发人员)日志格式自定义
  • 支持日志可靠落地,方向如:控制台,文件,滚动文件等
  • 支持同步,异步模式选择
  • 支持多线程程序并发写日志

核心技术

  • 类层次设计(继承 & 多态)
  • C++(如:多线程,智能指针等)
  • 双缓冲区
  • 生产消费者模型
  • 设计模式(单例,工厂,建造者,代理模式)

日志插件技术实现

现在我们日志输出方式主要是3种方式:

  • printf,cout,等输出函数到控制台
  • 对于⼤型商业化项⽬, 为了⽅便排查问题,我们⼀般会将⽇志输出到⽂件或者是数据库系统⽅便查询和分析⽇志, 主要分为同步⽇志异步⽇志⽅式

同步日志

同步⽇志是指当输出⽇志时,必须等待⽇志输出语句执⾏完毕后,才能执⾏后⾯的业务逻辑语句,⽇志输出语句与程序的业务逻辑语句将在同⼀个线程运⾏。每次调⽤⼀次打印⽇志API就对应⼀次系统调⽤write写⽇志⽂件异步日志。

缺点:

  • 在高并发情况下,由于写日志IO的系统操作,效率过低;
  • 同时write操作有可能让线程阻塞,无法执行业务逻辑。

异步日志

         异步日志就是将日志输出操作分离出,让一个线程负责日志输出操作,而业务线程只负责在写。业务线程只需要在内存中向日志缓冲区写入日志(日志的生产者),而日志线程就只负责从日志缓冲区中获取日志,完成日志的输出操作(日志的消费者)。这之间的关系就是一个典型的生产-消费者模型。

相关技术补充

C不定参宏

#include <stdio.h>

// 定义一个不定参宏,用于打印消息
#define LOG(format, ...) printf(format, __VA_ARGS__)

// 项目中通过它来简化接口调用
#define DEBUG(fmt, ...) \
    RoolLogger()->Debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)

int main() {
    // 使用不定参宏打印不同数量的参数
    LOG("Hello, world!\n");
    LOG("The value of x is %d\n", 42);
    LOG("x = %d, y = %d, z = %d\n", 10, 20, 30);

    return 0;
}

C不定参函数

void Debug(const std::string &fmt, ...)
{
       // 1.从不定参中获取字符串
       char custom_log[1024] = {0};
       va_list v_li;        // 本质是 char*
       va_start(v_li, fmt); // 将记录全部不定参数,到v_li中
       vsnprintf(custom_log, sizeof custom_log, fmt.c_str(), v_li); //通过接口转到char[]中
       va_end(v_li);
       print("%s%n", custom_log);
 }

C++不定参函数

// 模板函数 func 的定义
template <class T>
void func(const T& t)
{
	cout << t << endl;
	cout << endl;
}
 
//非模板函数 func 的定义
void func()
{
	cout << 0 << endl;
}
 
//1. 模板变参函数 func 的定义
template<class T , class ...Ags>
//void func(const T& t, Ags... args)  // 一个一个地解包参数
 void func(const T& t, Ags&&... args)    // 
{
	cout << t << " 剩余包数:" << sizeof...(args) << endl ;
	//func(args...); // 剩余参数包
	func(forward<Ags>(args)...); //...的位置有讲究,
}


//2. 使用变参为类传参
class MyClass {
public:
    MyClass(int a, int b, char _c) : x(a), y(b), c(_c) {}
    // 其他成员...
private:
    int x, y;
    char c;
};

templete <class T, class ...Args>
void setclass(Args &&... args) {
    return std::make_shared<T>(std::forword<Args>(args)...);
// 假设 Args 是 int, int,char。不定参展开传参,展开示例如下:
//auto ptr = std::make_shared<MyClass>(std::forward<int>(10), std::forward<int>(20), std::forword<char>('k'));


int main()
{
	func();
	func(1, 'A');
	func(1, 'A', "hello word");
    setclass<MyClass>("10", "20", 'k');
}

设计模式

设计模式是前辈们对代码开发经验的总结,是解决特定问题的⼀系列套路。它不是语法规定,⽽是⼀套⽤来提⾼代码可复⽤性、可维护性、可读性、稳健性以及安全性的解决⽅案。

设计原则:

从整体上来理解六⼤设计原则,可以简要的概括为⼀句话,⽤抽象构建框架,⽤实现扩展细节,具体到每⼀条设计原则,则对应⼀条注意事项:

  • 单⼀职责原则告诉我们实现类要职责单⼀
  • ⾥⽒替换原则告诉我们不要破坏继承体系
  • 依赖倒置原则告诉我们要⾯向接⼝编程
  • 接⼝隔离原则告诉我们在设计接⼝的时候要精简单⼀
  • 迪⽶特法则告诉我们要降低耦合
  • 开闭原则是总纲,告诉我们要对扩展开放,对修改关闭

这里只简单提及所用到的模式,详细了解设计模式,请自行寻找各模式的例子。

工厂模式

⼯⼚模式是⼀种创建型设计模式, 它提供了⼀种创建对象的最佳⽅式。在⼯⼚模式中,我们创建对象时不会对上层暴露创建逻辑,⽽是通过使⽤⼀个共同结构来指向新创建的对象,以此实现创建-使⽤的分离。()

⼯⼚模式可以分为:

简单⼯⼚模式: 简单⼯⼚模式实现由⼀个⼯⼚对象通过类型决定创建出来指定产品类的实例。假设有个⼯⼚能⽣产出⽔果,当客⼾需要产品的时候明确告知⼯⼚⽣产哪类⽔果,⼯⼚需要接收⽤⼾提供的类别信息,当新增产品的时候,⼯⼚内部去添加新产品的⽣产⽅式。

缺点:这个模式的结构和管理产品对象的⽅式⼗分简单, 但是它的扩展性⾮常差,当我们需要新增产品的时候,就需要去修改⼯⼚类新增⼀个类型的产品创建逻辑,违背了开闭原则。

⼯⼚⽅法模式: 在简单⼯⼚模式下新增多个⼯⼚,多个产品,每个产品对应⼀个⼯⼚。假设现在有A、B 两种产品,则开两个⼯⼚,⼯⼚ A 负责⽣产产品 A,⼯⼚ B 负责⽣产产品 B,⽤⼾只知道产品的⼯⼚名,⽽不知道具体的产品信息,⼯⼚不需要再接收客⼾的产品类别,⽽只负责⽣产产品。

缺点:⼯⼚⽅法模式每次增加⼀个产品时,都需要增加⼀个具体产品类和⼯⼚类,这会使得系统中类的个数成倍增加,在⼀定程度上增加了系统的耦合度。

抽象工厂模式:⼯⼚⽅法模式通过引⼊⼯⼚等级结构,解决了简单⼯⼚模式中⼯⼚类职责太重的问题,但由于⼯⼚⽅法模式中的每个⼯⼚只⽣产⼀类产品,可能会导致系统中存在⼤量的⼯⼚类,势必会增加系统的开销。

建造者模式

建造者模式是⼀种创建型设计模式, 使⽤多个简单的对象⼀步⼀步构建成⼀个复杂的对象,能够将⼀个复杂的对象的构建与它的表⽰分离,提供⼀种创建对象的最佳⽅式。主要⽤于解决对象的构建过于复杂的问题。

代理模式

代理模式指代理控制对其他对象的访问, 也就是代理对象控制对原对象的引⽤。在某些情况下,⼀个对象不适合或者不能直接被引⽤访问,⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤。

项目模块设计

日志框架设计 

⽇志等级模块:对输出⽇志的等级进⾏划分,以便于控制⽇志的输出,并提供等级枚举转字符串功能。

  • OFF:关闭
  • DEBUG:调试,调试时的关键信息输出。
  • INFO:提⽰,普通的提⽰型⽇志信息。
  • WARN:警告,不影响运⾏,但是需要注意⼀下的⽇志。
  • ERROR:错误,程序运⾏出现错误的⽇志
  • FATAL:致命,⼀般是代码异常导致程序⽆法继续推进运⾏的⽇志

日志消息模块:存储日志必要的信息,如:时间,所在文件,行,日志等级,日志用户自定义部分,所在线程ID。

好了,我们有了组件日志的”材料“后开始制作日志,那我们该怎么将日志数据排列?

日志格式模块(format)

功能:用户给予日志格式化规则,如:%d%T[%t]%T[%p]%T[%c]%T%f:%l%T%m%n,日志格式器模块会生成的日志格式,然后日志依据格式构建日志信息,最后返回日志字符串。

设计模式:简单工厂模式

设计思想

  1. 抽象出一个格式化子类的基类,同时声明虚函数fomat为子类形成日志的共同方法,由子类重写。
  2. 基于基类,派生出 时间,所在文件,行,日志等级等必要信息的子类;并重写format接口。
  3. 创建formatter 工厂类,根据用户规定的日志格式化规则,形成日志信息构建流水线(vector<format>)(实现方法:通过基类指针就能调用各个子类对象的fomat函数); 向外部提供获取日志字符串接口(GetResult)。

在外部构造日志时,直接调用formatter工厂类,由工厂类对外交互。

关系图:

format模块代码:

 Log-Project/logs/format.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)

日志落地模块(sink)

功能:用户给予一段日志信息,日志落地模块根据日志输出等级,将日志信息落地到输出到控制台,文件,滚动文件甚至是数据库。

设计模式:简单工厂模式

设计思路

  1. 抽象出一个log落地基类,并同时声明log虚函数,让子类重写。
  2. 基于log基类,派生出控制台,文件,滚动文件(文件超过一定大小,自动创建新文件)等其他方向的子类,并根据落地方向不同重写log函数。
  3. 支持落地扩展。用户可以自己添加相应的落地方向模块,而工厂类创建不用修改。
  4. 创建logSink工厂类,向外部提供一个创建落地对象的接口createlogsink,用户即可通过传入落地方向的类,进行创建具体对象,让创建与表示分离。

代码:

Log-Project/logs/sink.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)

日志管理器模块(logger)

功能:整合日志格式模块 & 日志管理器模块,向外提供根据日志等级输出的接口。

设计模式:工厂模式 

管理成员:

  1. 日志器名称(日志器唯一标识符,在全局日志器中会对日志器进行管理)
  2. 日志格式化模块对象
  3. 日志落地方向对象数组(日志落地方向可能存在多个)
  4. 日志落地操作锁(在多线程情况下,保证日志写操作线程安全,避免日志交叉)
  5. 日志最小输出等级(小于该等级日志则不输出)

管理函数:

  1. debug,info,warn,fatal等级的日志输出操作(根据传入日志message使用日志格式化对象形成日志,然后调用日志落地接口log,完成日志落地)
  2. 抽象日志落地log

实现方式:

  1. 抽象logger基类(派生出 同步日志器 & 异步日志器 子类)
  2. 由于同步日志器异步日志器之间的区别是落地操作(log)的不同,因此我们将落地操作(log)抽象出来,在子类完成不同落地操作。最后通过logger基类指针,来调用不同日志器派生类重写的log操作。

代码:

 Log-Project/logs/logger.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)

 扩展

功能:设计一个建造者类,简化用户设置日志参数,和用户使用日志器的复杂度。

设计模式:建造者模式

管理成员:由于是建造者类,其将管理成员为日志管理器的管理成员

管理函数:

  1. 对外提供设置日志器名称,日志器格式化规则,日志等级,创建日志落地方向等接口,一步步构造日志器部件。

 实现方式:

  1. 抽象builderlogger类,提供日志器初始化部件接口(派生出 局部日志器建造者 & 全局日志器建造者 子类)
  2. 局部日志器与全局(单例)日志器建造者,区别在于前者无法突破作用域,而全局可以在程序的任一位置访问,并给予查看,管理,建造日志器。因此我们需要将builderlogger类中build抽象出来,让派生类来完成各自创建操作。
  3. 全局日志器建造者类需要使用单例模式,同时需要维护一个以名称查找的日志器unorder_map容器,以便于管理全局的日志器。

代码:

Log-Project/logs/builder.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)

双缓冲区异步任务处理器(Asynlogger)

        异步日志器相比与同步日志器,线程在调用日志器输出日志时,异步将日志信息存放在内存中然后继续业务逻辑,并不直接调用系统接口,进行IO操作;存在在内存中的日志信息会由异步日志落地线程专门来将日志信息落地。 

设计思想:异步处理线程 + 数据池

使⽤者将需要完成的任务添加到任务池中,由异步线程来完成任务的实际执⾏操作。

任务池的设计思想:双缓冲区阻塞数据池

优势:避免了空间的频繁申请释放,且尽可能的减少了⽣产者与消费者之间锁冲突的概率,提⾼了任务处理效率。

问题:为什么不采用任务队列的方式处理日志

答:

1. 任务队列锁冲突频繁,逻辑复杂。假设使用同一个任务队列,生产者(写日志线程)与生产者(写日志线程)存在锁冲突;生产者(写日志线程)与消费者(落地线程)存在锁冲突,锁冲突概率高。

2. 任务队列一般采用链表的方式,这样存在频繁的控制申请释放,空间无法复用。

⽽双缓冲区不同,双缓冲区是处理器将⼀个缓冲区中的任务全部处理完毕后,然后交换两个缓冲区,重新对新的缓冲区中的任务进⾏处理,虽然同时多线程写⼊也会冲突,但

是冲突并不会像每次只处理⼀条的时候频繁(⽣产者与消费者之间的锁冲突也只有交换缓冲区时,会有锁冲突),且不涉及到空间的频繁申请释放所带来的消耗。

缓冲区类设计

功能:提供缓冲区交换接口; 支持多日志一次落地; 支持缓冲区动态扩容

代码:

Log-Project/logs/buffer.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)

异步日志器模块

异步⽇志器类继承⾃⽇志器类, 并在同步⽇志器类上拓展了异步消息处理器。当我们需要异步输出⽇志的时候, 需要创建异步⽇志器和消息处理器, 调⽤异步⽇志器的log、error、info、fatal等函数输出不同级别⽇志。

相对与同步日志器,异步日志器需要实现:

  1. 重写log函数,让日志信息写入到缓冲区中。 
  2. 提供默认异步日志线程的缓冲区交换,读取缓冲区,最终完成日志落地功能逻辑的回调函数

代码:

   Log-Project/logs/logger.hpp · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)

日志宏

提供全局的⽇志器获取接⼝。 使⽤代理模式通过全局函数或宏函数来代理Logger类的log、debug、info、warn、error、fatal等接

⼝,以便于控制源码⽂件名称和⾏号的输出控制,简化⽤⼾操作。 当仅需标准输出⽇志的时候可以通过主⽇志器来打印⽇志。 且操作时只需要通过宏函数直接进⾏输出即可。(简化用户使用复杂度)

// 定义宏,用于自动获取文件名和行号,并且可以接受任意的日志记录器对象名,方便用户不用操作全局日志器
#define LOG_DEBUG(logger_name, fmt, ...) \
    GetLogger(logger_name)->Debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)

#define LOG_INFO(logger_name, fmt, ...) \
    GetLogger(logger_name)->Info(__FILE__, __LINE__, fmt, ##__VA_ARGS__)

#define LOG_FAIL(logger_name, fmt, ...) \
    GetLogger(logger_name)->Fail(__FILE__, __LINE__, fmt, ##__VA_ARGS__)

#define LOG_WARN(logger_name, fmt, ...) \
    GetLogger(logger_name)->Warn(__FILE__, __LINE__, fmt, ##__VA_ARGS__)

详尽代码:

Log-Project/logs/log.h · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)

性能测试

下⾯对⽇志系统做⼀个性能测试,测试⼀下平均每秒能打印多少条⽇志消息到⽂件。

主要的测试⽅法是:每秒能打印⽇志数 = 打印⽇志条数 / 总的打印⽇志消耗时间

主要测试要素:同步/异步 & 单线程/多线程

测试项目:

  • 1000w+条指定⻓度的⽇志输出所耗时间
  • 每秒可以输出多少条⽇志
  • 每秒可以输出多少MB⽇志

测试环境:

CPU: Intel(R) Xeon(R) Gold 6278C CPU @ 2.60GHz(2 核)

RAM: 4G

OS : CentOS Linux release 7.6.1810 (Core)

测试代码:

Log-Project/bench/bench.cxx · 逆光/标准日志插件项目 - 码云 - 开源中国 (gitee.com)

测试结果:打印相同日志数量,异步多线程最快;其次是同步多线程;

模块关系图

改bug心得

出错原因:对formatter功能理解有问题
formatter功能:是根据用户给出的格式化规则,通过解析函数(pase_pattern),形成一个构造日志"队列"。
就好像:formatter是工厂,通过pase_pattern,自定义化形成一个构造日志流水线。

问题出在:我是如何理解这个流水线是:临时的(错误),还是持久化的(正确)。


首先从此次出错的问题:在多线程场景中,我的日志构造线的_items出现段错误

出现问题思考解决思路:
刚开始时,我通过cout,定位到了出错点,同时也发现新线程来使用_items时出错。我刚开始意识到,这个_items出现线程安全,我后面甚至还想要不将_items从成员变量,做成临时变量,好让每次不用考虑加锁解锁。但这样做每发一个日志,就创建一个_items流水线工具,然后就丢弃,这样会有很大的性能浪费,在这里想就有问题,就没往这边想了。(差点在错误的路上一路狂奔)

最后,发现我应该将构造日志流水线做成持久化。从上到下讲,作为一个日志插件,用户在初始化日志器后,日志输出格式也应该确定了,日志构造流水线,也应处于被只读的情况,没有线程安全一说。

因此解析日志操作不能出现在,日志器构造日志中,应在初始化操作中。

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获,请动动你发财的小手点个免费的赞,你的点赞和关注永远是博主创作的动力源泉。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2225122.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML+CSS实现超酷超炫的3D立方体相册

效果演示 HTML和CSS实现一个简单的3D立方体加载动画的相册。它使用了HTML来构建立方体的结构&#xff0c;并通过CSS来添加样式和动画效果。 HTML <div class"loader3d"><div class"cube"><div class"face"><img src&qu…

LabVIEW偏振调制激光高精度测距系统

在航空航天、汽车制造、桥梁建筑等先进制造领域&#xff0c;许多大型零件的装配精度要求越来越高&#xff0c;传统的测距方法在面对大尺寸、高精度测量时&#xff0c;难以满足工业应用的要求。绝对测距技术在大尺度测量上往往会因受环境影响大、测距精度低而无法满足需求。基于…

社交媒体视频素材平台推荐

在内容创作日益重要的今天&#xff0c;社交媒体视频素材的需求不断增加。适合各种平台的视频素材不仅可以提升内容质量&#xff0c;还能吸引更多观众。以下是一些推荐的社交媒体视频素材平台&#xff0c;帮助你找到适合的资源。 蛙学网 蛙学网 是一个专注于社交媒体视频素材的平…

Sora高端制造业WordPress外贸主题

Sora是一款专为高端制造业设计的WordPress主题&#xff0c;由国内知名wordpress开发团队简站wordpress主题开发&#xff0c;它以红色为主色调&#xff0c;适合外贸企业出海建独立站的模板。这个主题适用于WordPress 6.0及以上版本&#xff0c;并且只服务于真正有需要的用户。主…

C++ | Leetcode C++题解之第504题七进制数

题目&#xff1a; 题解&#xff1a; class Solution { public:string convertToBase7(int num) {if (num 0) {return "0";}bool negative num < 0;num abs(num);string digits;while (num > 0) {digits.push_back(num % 7 0);num / 7;}if (negative) {dig…

论文阅读(二十六):Dual Attention Network for Scene Segmentation

文章目录 1.Introduction3.DANet3.1Position Attention Module3.2Channel Attention Module 论文&#xff1a;Dual Attention Network for Scene Segmentation   论文链接&#xff1a;Dual Attention Network for Scene Segmentation   代码链接&#xff1a;Github 1.Intr…

Vue3 学习笔记(五)Vue3 模板语法详解

在 Vue3 的世界里&#xff0c;模板语法是我们构建用户界面的基石。今天&#xff0c;让我们一起深入了解 Vue3 的模板语法&#xff0c;我将用通俗易懂的语言和实用的例子&#xff0c;带你掌握这项必备技能。 1、文本插值&#xff1a;最基础的开始 想在页面上显示数据&#xff1f…

深度学习模型入门教程:从基础到应用

深度学习模型入门教程&#xff1a;从基础到应用 前言 在人工智能的浪潮中&#xff0c;深度学习作为一种强大的技术&#xff0c;正在各行各业中发挥着越来越重要的作用。从图像识别到自然语言处理&#xff0c;深度学习正在改变我们的生活和工作方式。本文将带您深入了解深度学…

OpenCV视觉分析之运动分析(3)背景减除类:BackgroundSubtractorKNN的一系列get函数的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 BackgroundSubtractorKNN类有一系列的get函数&#xff0c;下面我们一一列举他们的名字和用法。 一系列函数 函数getDetectShadows() getDetec…

CSS伪元素以及伪类和CSS特性

伪元素&#xff1a;可以理解为假标签。 有2个伪元素 &#xff08;1&#xff09;::before &#xff08;2&#xff09;::after ::before <!DOCTYPE html> <html> <head><title></title><style type"text/css">body::before{con…

使用Python Pillow库生成九宫格图片

相信很多人看到过九宫格图片&#xff0c;一张完整的大图被分割成九张小图&#xff0c;在朋友圈和微博里一度成为流行。 相比完整的大图&#xff0c;九宫格图文增添了一丝趣味和精致&#xff0c;也显得更有创意。 制作九宫格图片的工具有很多&#xff0c;下文用Python的PIL库来…

Puppeteer 与浏览器版本兼容性:自动化测试的最佳实践

Puppeteer 支持的浏览器版本映射&#xff1a;从 v20.0.0 到 v23.6.0 自 Puppeteer v20.0.0 起&#xff0c;这个强大的自动化库开始支持与 Chrome 浏览器的无头模式和有头模式共享相同代码路径&#xff0c;为自动化测试带来了更多便利。从 v23.0.0 开始&#xff0c;Puppeteer 进…

vue3完整Demo(数据绑定,数据显示,数据修改,数据提交)

需要引入的的依赖&#xff1a;jquery&#xff08;用于异步请求&#xff09; 一、数据显示的前端页面 条件查询数据并显示&#xff0c;下拉框使用的model双向绑定 二、js代码&#xff08;list页面的数据请求&#xff09; 后端传来的时间数据需要转换可以使用new Intl.DateTim…

【NOIP提高组】加分二叉树

【NOIP提高组】加分二叉树 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 设一个n个节点的二叉树tree的中序遍历为&#xff08;l,2,3,…,n&#xff09;&#xff0c;其中数字1,2,3,…,n为节点编号。每个节点都有一个分数&#xff08;均为正整…

【Java并发编程】信号量Semaphore详解

一、简介 Semaphore&#xff08;信号量&#xff09;&#xff1a;是用来控制同时访问特定资源的线程数量&#xff0c;它通过协调各个线程&#xff0c;以保证合理的使用公共资源。 Semaphore 一般用于流量的控制&#xff0c;特别是公共资源有限的应用场景。例如数据库的连接&am…

redis详细教程(2.List教程)

List是一种可以存储多个有序字符串的数据类型&#xff0c;其中的元素按照顺序排列&#xff08;可以重复出现&#xff09;&#xff0c;可以通过数字索引来访问列表中的元素&#xff0c;索引可以从左到右或者从右到左。 Redis 列表可以通过两种方式实现&#xff1a;压缩列表&…

力扣283-- 移动零

开始做梦的地方 力扣283 &#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 何解&#xff1f; 1&#xff0c;暴力枚举&#xff1a…

ElasticSearch备考 -- index rollover

一、题目 给索引my-index-000001&#xff0c;创建别名my-index&#xff0c;并设置rollover&#xff0c;满足以下三个条件的 The index was created 7 or more days ago.The index contains 5 or more documents.The index’s largest primary shard is 1GB or larger. 二、思考…

cmake命令使用

有关cmake的入门简介可参见 CMake入门教程_cmake静态test.c编译-CSDN博客 本文是进一步对cmake常用命令做进一步详述 配置项目 cmake_minimum_required 作用 配置cmake最低版本 用法 cmake_minimum_required(VERSION 3.0) project 作用&#xff1a;设置预设变量 PROJEC…

w002基于Springboot医护人员排班系统

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…