万字长文 - Python 日志记录器logging 百科全书 - 高级配置之 日志分层

在这里插入图片描述

万字长文 - Python 日志记录器logging 百科全书 - 高级配置之 日志分层

前言

在 Python 的logging模块中,它不仅提供了基础的日志功能,还拥有一系列高级配置选项来满足复杂应用的日志管理需求。

说到logging 模块的高级配置,必须提及日志分层logging.config配置日志异步操作等关键功能。它们每一项都为开发者提供了强大的调试和监控环境,对于构建可维护和高效的日志系统至关重要。

在接下来的三篇logging高级配置 文章中,我将为读者朋友们介绍 Python logging 模块中的三个高级配置的具体应用:日志分层logging.config 以及 日志异步操作,探讨它们如何优化日志处理流程,并提升应用的整体性能。

本文将首先聚焦于 logging 模块中的日志分层概念,解析其如何使开发者能够构建具有层级结构的日志记录系统,并高效地管理和过滤日志信息。

知识点📖📖

模块释义
loggingPython 的日志记录工具,标准库
logging.getLogger获取日志记录器实例

导入模块

import logging
import logging.handlers

文章脉络:

  • 点击直达:万字长文 - Python 日志记录器logging 百科全书 之 基础配置
  • 点击直达:Python 日志记录器logging 百科全书 之 日志回滚
  • 点击直达:万字长文 - Python 日志记录器logging 百科全书 之 日志过滤
  • 点击直达:万字长文 - Python 日志记录器logging 百科全书 - 高级配置之 日志分层

理解日志分层

日志分层在管理复杂应用的日志中发挥着重要作用。

作用

日志分层在复杂应用程序中提供了高度的组织性和灵活性,允许针对不同模块或组件进行细致的日志管理,从而优化调试、维护效率,并增强整体日志系统的性能和可读性。


将 Python 的 logging 模块中的日志分层类比为 Python 中的类继承(父类和子类关系),这样可以有助于理解日志记录器(logger)之间的继承和行为传播机制。

  1. 继承:在类的继承中,子类继承父类的属性和方法。类似地,在日志分层中,子记录器会继承父记录器的配置,如日志级别和关联的处理器(handlers)。

  2. 重写和自定义:就像子类可以重写或扩展父类的方法和属性,子记录器也可以有自己的日志级别和处理器,甚至可以完全覆盖父记录器的设置。

  3. 层级关系:正如类可以有多层继承关系,日志记录器也可以形成层级结构,允许复杂和细致的控制。

  4. 传播行为:在类继承中,子类的行为可以反映父类的特征,而在日志分层中,日志消息的传播(默认情况下)会从子记录器向上到父记录器,除非显式地将 propagate 属性设置为 False

需要注意的是,这只是一个比喻,实际的实现细节和概念上仍有区别。例如,类继承是面向对象编程中的一个核心概念,涉及到更广泛的编程模式,而日志分层主要是关于信息传递和处理的策略。

简单总结如下:

特性主要作用与实现方式备注
组织性为每个模块或组件设置独立的子记录器日志分层核心优势,提供清晰的日志结构
继承和覆盖子记录器继承父记录器的配置,可根据需要覆盖日志分层核心优势,提高配置灵活性
灵活性为不同模块设置不同的日志级别多日志处理器可实现,但日志分层的优势是更加结构化和直观
性能优化控制哪些消息被记录,减少不必要的日志输出多日志处理器可实现,但日志分层的优势是更加结构化和直观
问题追踪快速定位大型应用中的问题所在模块日志记录自带buff,但日志分层的优势在于它使得定位问题变得更加容易

疑惑

在学习了前面的 万字长文 - Python 日志记录器logging 百科全书 之 日志过滤 和 初步了解到日志分层的作用之后,可能会有读者朋友们有疑问:

?既然能够通过单个记录器和多处理器满足复杂日志需求的情况下,为什么还需要使用分层的日志记录器方法???

以下是我在深入了解日志分层 的作用之后的回答:

  • 单个日志记录器配合多个处理器:在这种情况下,我们可以根据日志的类型(如错误、警告、信息等)将日志定向到不同的处理器(如文件、控制台等)。这种方法在应用结构相对简单,或者当日志需求主要围绕不同类型的日志处理时很有效。
  • 使用分层多个日志记录器和处理器:这种方法允许按模块或组件分别记录日志。每个模块或组件可以有其自定义的日志级别和处理器。这种方式适用于更复杂的应用程序,其中不同部分可能有不同的日志需求。

分层日志在大型和复杂的应用程序中提供了更好的组织性和灵活性,使得对不同模块或组件的日志管理更加精细和高效。


应用场景

日志分层的应用场景主要体现在大型、多模块的应用程序和微服务架构中,以及多团队协作的开发项目里,它帮助各个模块独立地控制日志记录,简化问题追踪和调试,提高维护效率。

  1. 大型应用程序:在大型应用程序(如电子商务平台、企业级软件等)中,应用通常分为多个模块或组件。每个组件可以有自己的日志记录器,这些记录器可以根据组件的具体需求进行配置。
  2. 多团队开发:在多团队协作的项目中,每个团队可能负责应用的不同部分。日志分层允许每个团队为其负责的部分单独配置日志。
  3. 微服务架构:在微服务架构中,每个微服务可以配置自己的日志记录器。这样可以确保每个服务的日志信息都是独立和清晰的。
  4. 调试和维护:在应用程序的维护和调试过程中,可以针对出现问题的特定模块调整日志级别,从而获取更详细的日志信息。
  5. 统一基础配置:在一个复杂的应用中,各个组件可能有不同的日志需求。使用分层结构,可以在顶层记录器上定义通用的日志策略,然后根据需要为特定子记录器定制日志行为。这种方式使得日志系统更易于维护和更新。

日志分层的要点

命名规范和核心概念

以下内容来自官方文档:


getLogger() 返回对具有指定名称的记录器实例的引用(如果已提供),或者如果没有则返回 root 。名称是以句点分隔的层次结构。多次调用 getLogger()具有相同的名称将返回对同一记录器对象的引用。在分层列表中较低的记录器是列表中较高的记录器的子项。例如,给定一个名为 foo 的记录器,名称为 foo.bar 、 和 foo.bam 的记录器都是 foo 子项。

记录器具有 有效等级 的概念。如果未在记录器上显式设置级别,则使用其父记录器的级别作为其有效级别。如果父记录器没有明确的级别设置,则检查 父级。依此类推,搜索所有上级元素,直到找到明确设置的级别。根记录器始终具有明确的级别配置(默认情况下为 WARNING )。在决定是否处理事件时,记录器的有效级别用于确定事件是否传递给记录器相关的处理器。

子记录器将消息传播到与其父级记录器关联的处理器。因此,不必为应用程序使用的所有记录器定义和配置处理器。一般为顶级记录器配置处理器,再根据需要创建子记录器就足够了。(但是,你可以通过将记录器的 propagate 属性设置为 False 来关闭传播。)


我提炼如下,简要说明了关于日志分层中的日志记录器行为和层次结构的核心概念:

  • 记录器实例引用:使用getLogger()函数可以获取具有特定名称的记录器实例。如果使用相同的名称多次调用getLogger(),将返回同一个记录器对象的引用。

  • 命名规范:记录器(logger)的名称通常遵循点号分隔的层级结构,类似于 Python 包和模块的命名方式。例如,'foo.database' 表示 foo 下的 database 子模块的记录器。

  • 层级关系:记录器的层级结构通过命名来实现。在这个层级中,一个记录器可以是另一个记录器的子项。例如,如果有一个名为 'foo' 的记录器,那么 'foo.bar''foo.bar.baz' 就是它的子记录器。

  • 有效级别:如果调用 getLogger() 时不提供名称,将返回根记录器。根记录器是所有记录器的最顶层父记录器,它的默认级别为 WARNING

  • 消息传播:子记录器的日志消息会传播到其父记录器的处理器,除非设置了propagate属性为False。通常只需为顶级记录器配置处理器,子记录器可以继承这些处理器。

这种层级结构和命名规范为大型和复杂的应用程序提供了一种高效和灵活的日志管理方式。通过恰当地命名和配置记录器,可以轻松地管理应用程序的不同部分所生成的日志,确保日志信息的清晰和有序。


命名不规范的问题

在 Python 的 logging 模块中,如果顶级日志记录器的命名与子记录器的命名不遵循统一的层级结构,会出现以下几个问题:

  1. 继承失效:日志记录器之间的层级关系是通过它们的命名来确定的。如果顶级记录器和子记录器的命名不遵循统一的层级前缀,子记录器将无法正确继承顶级记录器的配置(如处理器、级别等)。这意味着我们需要为每个子记录器单独配置处理器和其他设置,增加了配置复杂性。
  2. 日志传播问题:通常,日志消息会从子记录器传播到父记录器。如果子记录器的命名不遵循正确的层级结构,这种传播可能无法发生,导致日志消息不会被预期的父记录器(和其关联的处理器)处理。
  3. 可维护性和可读性降低:在大型项目中,清晰和一致的日志记录器命名极为重要。不遵循统一的命名规范会使代码难以理解和维护,特别是在涉及多个开发者和模块的情况下。

为了避免这些问题,所以需要使用一致的层级命名约定。例如,顶级日志记录器命名为 'foo',那么子记录器应该以 'foo.' 作为前缀,如 'foo.bar''foo.baz' 等,以确保正确的继承和日志消息传播。

什么时候使用 propagate

可能会有读者朋友们有疑问:

?既然设置logger.propagate = False,那为什么还需要使用日志分层呢? 一开就使用 非日志封层的logger不是更合适吗?

其实要回答这个疑问,就应该重新审视日志分层的作用和应用场景,以及为什么即使在某些情况下禁用了传播,日志分层仍然是有价值的。

使用 logger.propagate = False 的情况,

  1. 避免重复日志记录:当在不希望某个子记录器(logger)的日志被其父记录器也处理时。例如,当已经为子记录器配置了特定的处理器(handler)并且不希望同样的日志消息再次由更高级别的记录器处理,这时设置 propagate = False 可以防止日志消息向上传播。

  2. 特定日志处理:当我们希望对某个模块或组件的日志进行特殊处理,与应用程序的其他部分区别开来。例如,我们可能有一个记录安全相关日志的记录器,我们不希望这些日志被常规的应用程序日志处理器处理。

  3. 独立日志流:在构建库或框架时,可能希望库的日志独立于使用该库的应用程序的日志系统。在这种情况下,可以为库设置一个专用的记录器,并设置 propagate = False,以防止库的日志消息污染或干扰主应用程序的日志。

  4. 性能考虑:在一些性能敏感的应用中,防止不必要的日志传播可以减少一些处理开销,特别是当有大量的日志消息和复杂的日志处理器配置时。

即使在上面的这些情况下,分层结构依然有助于维护清晰的组织架构。我们可以为特定的模块创建专用的子记录器,给予它独立的处理器和格式化器,而不必在每个模块中创建和配置新的独立记录器。


示例代码

这里是一份能用的伪代码,用于帮助读者朋友们更好的理解日志分层的具体应用。

代码:

# -*- coding: utf-8 -*-import logging
import logging.handlers
import sysimport requests# 全局基础配置, 日志格式化配置
formatter = logging.Formatter('%(levelname)-7s - %(asctime)s - %(name)s - %(message)s')class RemoteLogHandler(logging.Handler):"""自定义远程处理器"""def __init__(self, remote_url, logger):super().__init__()self.remote_url = remote_urlself.error_logger = loggerself.setFormatter(formatter)def emit(self, record):# 发送日志记录到远程服务器log_entry = self.format(record)  # 格式化日志记录try:response = requests.post(self.remote_url, data=log_entry)response.raise_for_status()except Exception as e:record.msg = f"Original message: {record.msg}, Failed to send log to remote: {str(e)}"print('error_logger 等级是 ', self.error_logger.level)self.error_logger.handle(record)def setup_logger(*handlers, name, level=logging.INFO):"""用于设置特定记录器的函数,支持多个处理器.Args:*handlers(logging.Handler): 日志处理器name(str): 日志记录器名称level(int): 日志等级Returns:日志记录器."""logger = logging.getLogger(name)logger.setLevel(level)for handler in handlers:handler.setFormatter(formatter)logger.addHandler(handler)return loggerdef setup_file_handler(filename, level=logging.DEBUG):"""setup file handlerArgs:filename(str): 日志文件的名称level(int): 日志处理器的级别, 默认为logging.DEBUGReturns:"""file_handler = logging.FileHandler(filename=filename, delay=True)file_handler.setLevel(level=level)file_handler.setFormatter(formatter)return file_handlerdef setup_stream_handler(stream=sys.stdout, level=logging.INFO):stream_handler = logging.StreamHandler(stream=stream)stream_handler.setLevel(level=level)stream_handler.setFormatter(formatter)return stream_handlerdef create_remote_log_handler(url, logger, level=logging.INFO):remote_handler = RemoteLogHandler(url, logger)remote_handler.setLevel(level=level)remote_handler.setFormatter(formatter)return remote_handlerif __name__ == '__main__':# 顶层日志记录器# 处理器配置, 文件处理器 和 控制台处理器file_handler_global = setup_file_handler(filename='ecommerce_global.log')stream_handler_global = setup_stream_handler(stream=sys.stdout)global_logger = setup_logger(file_handler_global, stream_handler_global, name='ecommerce', level=logging.DEBUG)# 创建错误记录器和处理器error_file_handler = setup_file_handler(filename='error.log', level=logging.ERROR)error_logger = setup_logger(error_file_handler, name='error', level=logging.WARNING)# 实例化RemoteLogHandlerremote_handler_global = create_remote_log_handler(url='http://127.0.0.1:5000/submit_log', logger=error_logger, level=logging.ERROR)# 订单处理系统记录器和处理器order_file_handler = setup_file_handler('orders.log', level=logging.WARNING)order_logger = setup_logger(order_file_handler, remote_handler_global, name='ecommerce.orders', level=logging.INFO)# 支付系统记录器和处理器payment_file_handler = setup_file_handler(filename='payments.log', level=logging.ERROR)payment_logger = setup_logger(payment_file_handler, remote_handler_global, name='ecommerce.payments', level=logging.WARNING)## 测试打印日志global_logger.info('Global logger configured')order_logger.warning('Order logger configured')payment_logger.error('Payment logger configured')

运行效果

如果程序没有出错的话,可以看到会创建orders.log, payments.logecommerce_global.log 三份日志文件,内容分别如下所示:

  • 可以看到 ecommerce_global.log 文件,打印的日志是包含orders.logpayments.log的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用propagate

添加以下两行代码,

  • 设置子记录器的日志消息不传播到父记录器
order_logger.propagate = False
payment_logger.propagate = False

再次运行结果如下:

  • 可以看到没有重复日志记录啦。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码释义

这份代码是一个复杂的日志系统的实现,适用于需要将日志信息记录到不同位置(如文件、控制台、远程服务器)的应用程序。展示了如何在 Python 中使用 logging 模块来创建一个分层且灵活的日志处理架构。

  1. 自定义远程日志处理器 (RemoteLogHandler)

    • 定义了一个自定义的日志处理器,用于将日志消息发送到远程服务器。
    • 如果发送失败,它会使用另一个配置的日志记录器(error_logger)来处理这些日志消息。
  2. 灵活的日志配置函数

    • 提供了多个辅助函数(setup_logger, setup_file_handler, setup_stream_handler, create_remote_log_handler),使得创建和配置日志记录器和处理器更加灵活和简洁。
  3. 分层日志记录器

    • 创建了多个日志记录器,每个记录器代表应用程序的不同部分(如全局、订单处理系统、支付系统)。
    • 每个记录器可以有自己的处理器和日志级别。

日志分层的体现

  1. 不同功能的日志记录器

    • global_logger 用于全局日志记录。
    • order_logger 专门用于订单处理系统的日志。
    • payment_logger 专门用于支付系统的日志。
  2. 层级命名

    • 日志记录器的命名体现了应用程序的层级结构。例如,ecommerce.ordersecommerce.payments 表示这些记录器是 ecommerce 的子模块。
  3. 日志传播

    • 在这个例子中,如果 propagate 属性没有被设置为 False,那么日志消息会从子记录器传播到父记录器。这意味着 order_loggerpayment_logger 的日志也可能被 global_logger 所处理,除非显式地关闭了传播。

实际应用

这个日志系统适用于大型或模块化的应用程序,其中需要对不同部分的日志进行精细控制。通过这种方法,我们可以确保不同部分的日志被适当地记录和处理,同时保持日志系统的整洁和可维护性。这对于故障排查、性能监控和安全分析等方面非常有用。

总体来说,这个代码示例展示了一个结构化的日志系统,它既灵活又能够适应不同的日志需求,非常适合复杂的应用场景。

总结🎈🎈

本文详细介绍了 Python logging 模块中的日志分层功能,强调了其在构建复杂应用程序中的重要性。以下是文章的主要要点总结:

  1. 日志分层的作用与优势
    • 提高组织性:允许开发者为不同模块或组件设置独立的子记录器。
    • 继承与覆盖:子记录器可以继承父记录器的配置,同时具备自定义设置的能力。
    • 易于问题追踪:通过层级结构,方便快速定位问题所在模块。
  2. 命名规范与层级关系
    • 通过点号分隔的命名规范,确保记录器之间的层级关系清晰明确。
    • 层级结构提供了继承和消息传播的机制,简化了配置并增加了灵活性。
  3. 传播行为与性能优化
    • 默认情况下,子记录器的日志会传播到父记录器,可通过设置 propagate 属性进行控制。
    • 正确使用日志分层可以减少不必要的日志输出,从而优化性能。
  4. 应用场景
    • 日志分层特别适用于大型、多模块应用程序和微服务架构。
    • 有助于多团队协作的项目中的日志管理和维护。
  5. 实用代码示例
    • 文章通过实际代码展示了如何设置和使用分层日志记录器。
    • 包括自定义处理器和日志记录器的配置方法,增强了文章的实用性和可操作性。

总体来说,这篇文章为理解和应用 Python 的 logging 模块提供了深入的指导,特别是在构建需要细粒度日志管理的复杂应用时。文章的结构清晰,通过逐步深入的方式,使得读者朋友容易跟进和理解。

后话

本次分享到此结束,

see you~~🏹🏹

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

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

相关文章

Linux latin1字符集转成UTF-8

latin1字符集,我用命令iconv转换后依旧乱码,但是本地用Notepad转成utf-8再入库数据,却是正常的 查看文件编码 vi WeakcoverReason_20231120.csv:set fileencoding使用编码转换命令,将latin1改成UTF-8 iconv -f latin1 -t UTF-8 W…

初始环境配置

目录 一、JDK1、简介2、配置步骤 二、Redis1、简介2、配置步骤 三、MySQL1、简介2、配置步骤 四、Git1、简介2、配置步骤 五、NodeJS1、简介2、配置步骤 六、Maven1、简介2、配置步骤 七、Tomcat1、简介2、配置步骤 一、JDK 1、简介 JDK 是 Oracle 提供的 Java 开发工具包&…

linux rsyslog综合实战1

本次我们通过rsyslog服务将A节点服务器上的单个日志(Path:/var/log/245-1.log)实时同步到B节点服务器目录下(Path:/opt/rsyslog/245) 1.rsyslog架构 2.环境信息 环境信息 HostnameIpAddressOS versionModuleNotersyslog1192.168.10.245CentOS Linux release 7.9.2009 (Core)rs…

类与对象(上篇)

前言 在之前我们学的C入门主要是为现在学习类与对象打基础,今天我们才算真正开始学习C了。因为类与对象的知识点比较多,所以我们将它分为三部分讲解,今天我们学习类与对象的上篇。 一、面向过程和面向对象的初步认识 1、面向过程 面向过程顾…

[github初学者教程] 分支管理-以及问题解决

作者:20岁爱吃必胜客(坤制作人),近十年开发经验, 跨域学习者,目前于新西兰奥克兰大学攻读IT硕士学位。荣誉:阿里云博客专家认证、腾讯开发者社区优质创作者,在CTF省赛校赛多次取得好成绩。跨领域…

工作记录---为什么双11当天不能申请退款?(有趣~)

为什么? 服务降级了 服务降级: 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。 分布式系统的降级…

用Java实现贪吃蛇小游戏

一、创建新项目 首先创建一个新的项目,并命名为贪吃蛇。 其次在贪吃蛇项目下创建一个名为images的文件夹用来存放游戏相关图片。 然后再在项目的src文件下创建一个com.xxx.view的包用来存放所有的图形界面类,创建一个com.xxx.controller的包用来存放启…

基于AVR单片机的心电信号获取与分析

基于AVR单片机的心电信号获取与分析是一项常见的生物医学工程应用,用于监测和分析人体的心脏活动。本文将介绍基于AVR单片机的心电信号获取与分析的原理和设计,并提供相应的代码示例。 1. 概述 心电信号是记录和分析心脏电活动的重要手段。AVR单片机是…

mysql 中with的用法(3)

有表(tb),数据如下: 请用SQL,生成如下的样式: 一、建表 CREATE TABLE tb (id varchar(3) DEFAULT NULL,pid varchar(3) DEFAULT NULL,name varchar(64) DEFAULT NULL ) INSERT INTO tb (id, pid, name) VALUES(002, 0, 浙江省)…

docker更换国内源

docker更换国内源 1、编辑Docker配置文件 在终端中执行以下命令,编辑Docker配置文件: vi /etc/docker/daemon.json2、添加更新源 在打开的配置文件中,添加以下内容: {"registry-mirrors": ["https://hub-mirror…

Chrome中设置安全来源域名

目的: 使得本地映射的域名能被浏览器安全访问,允许调用设备资源 步骤: 在Chrome中导航栏打开 chrome://flags/#unsafely-treat-insecure-origin-as-secure 填入hosts域名:如 http://h5-twzc003.local.com 参考: h…

赴日开发工程师是做什么的?

日本的软件开发岗位对技术要求和沟通能力都有较高的要求,赴日开发工程师主要负责软件设计、开发和测试,包括编写代码、测试代码和修复漏洞等工作。开发人员必须对软件架构、设计模式和业务逻辑有深入的理解,并能做出合适的技术决策。 当然&a…

时间序列与 Statsmodels:预测所需的基本概念(1)

后文:时间序列与 statsmodels:预测所需的基本概念(2)-CSDN博客 一、说明 本博客解释了理解时间序列的基本概念:趋势、季节性、白噪声、平稳性,并使用自回归、差分和移动平均参数进行预测示例。这是理解任何…

江湖再见,机器视觉兄弟们,我已经提离职了,聪明的机器视觉工程师,离职不亏本!

我闻江湖已叹息,又闻人间繁闹闹。同为布衣沦落人,相逢何必曾相识。 此生谁料事事休,道不尽人情冷暖,聚散离合总平常,不似勇气少年时。 我估计今年公司年底是发不出工资了,因为订单续不上。年终奖更是没有&…

Android 弹出自定义对话框

Android在任意Activity界面弹出一个自定义的对话框,效果如下图所示: 准备一张小图片,右上角的小X图标64*64,close_icon.png,随便找个小图片代替; 第一步:样式添加,注意:默认在value…

通过微软MediaCreationTool制作Win10系统安装U盘,安装纯净版Win10的通用教程

最近新入手了一台Lenovo的入门级主机。 为了避免以后忘记装机步骤,特写下此博客记录。 装机步骤是在Lenovo网站上看的,在这表示感谢。 https://iknow.lenovo.com.cn/detail/177365

(十二)Flask重点之session

session 自我介绍&基本使用: 在Flask中,Session是一种用于在客户端和服务器之间存储和传输数据的机制。它允许您在用户与应用程序之间保持状态,并且可以存储和检索有关特定用户的信息。 Flask使用Werkzeug库提供的SecureCookie来实现S…

LangChain 4用向量数据库Faiss存储,读取YouTube的视频文本搜索Indexes for information retrieve

接着前面的Langchain,继续实现读取YouTube的视频脚本来问答Indexes for information retrieve LangChain 实现给动物取名字,LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗…

Python接口自动化(什么是接口、接口优势、类型)

简介 经常听别人说接口测试,接口测试自动化,但是你对接口,有多少了解和认识,知道什么是接口吗?它是用来做什么的,测试时候要注意什么?坦白的说,笔者之前也不是很清楚。接下来先看一下…

Python懒羊羊

目录 系列文章 写在前面 绘图基础 懒羊羊 写在后面 系列文章 序号文章目录直达链接表白系列1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏表白代码https://want595.blog.csdn.net/article/details/1297945183跳动的爱心https://want595…