• 【Log日志】springboot项目中集成Log日志详解


    一、Log日志介绍

    我们先看看springboot项目启动时的那么多信息是什么呢?如下图:

    在这里插入图片描述
    注意INFO! 这个呢其实就是日志。它是springboot的日志框架,默认集成Logback .
    默认情况下,Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台。

    从上图可以看到,日志输出内容元素具体如下:

    • 时间日期:精确到毫秒
    • 日志级别:ERROR, WARN, INFO, DEBUG or TRACE
    • 进程ID
    • 分隔符:— 标识实际日志的开始
    • 线程名:方括号括起来(可能会截断控制台输出)
    • Logger名:通常使用源代码的类名
    • 日志内容

    1.Log 日志组件主要作用及用途

    日志最大的功能点:对于学习程序,测试的工程师来说,日志能够定位问题,解决问题。

    (1)定位问题 日志可以帮助程序员调试问题,帮助测试人员定位问题
    (2)记录一切 日志帮助我们记录程序功能都干了什么,无论是正常的输入输出还是出现异常,都可以用日志记录!
    (3)记录分析用户行为 统计分析师用来记录用户的一起行为,用于分析用户的习惯和商业价值
    (4)备份和还原实时数据 数据库工程师用来作为一种特殊的数据库

    2.日志的级别Level

    日志也有轻重缓急之分,日志级别从低到高分为:

    TRACE < DEBUG < INFO < WARN < ERROR < FATAL

    日志级别使用场景
    DEBUGdebug级别用来记录详细的信息,方便定位问题进行调试,在生产环境我们一般不开启DEBUG
    INFO用来记录关键代码点的信息,以便代码是否按照我们预期的执行,生产环境通常会设置INFO级别
    WARN记录某些不预期发生的情况,如磁盘不足
    ERROR由于一个更严重的问题导致某些功能不能正常运行时记录的信息
    FATAL当发生严重错误,导致应用程序不能继续运行时记录的信息

    日志级别越高输出的日志信息越少,不会输出比它级别低的信息!

    如果设置为 WARN ,则低于 WARN 的信息都不会输出。

    Spring Boot中默认配置ERROR、WARN和INFO级别的日志输出到控制台。

    我们可以通过启动应用程序 --debug 标志来启用“调试”模式(开发的时候推荐开启),以下两种方式皆可:

    • 在运行命令后加入--debug标志,如:$ java -jar springTest.jar --debug
    • 在application.properties中配置debug=true,该属性置为true的时候,核心Logger(包含嵌入式容器、hibernate、spring)会输出更多内容,但是你自己应用的日志并不会输出为DEBUG级别。

    级别控制

    所有支持的日志记录系统都可以在Spring环境中设置记录级别(例如在application.properties中或者application.yml)
    格式为:logging.level.* = LEVEL

    1. logging.level:日志级别控制前缀,*为包名或Logger名
    2. LEVEL:选项TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF

    如:

    logging.level.com.itfuture=INFO:com.itfuture包下所有classINFO级别输出
    logging.level.root=WARN:root日志以WARN级别输出
    
    • 1
    • 2

    3.日志的输出Import

    通常日志以文本流的形式存储在磁盘,也可以把日志存储在关系型数据库中或 No Sql 中

    • 文本
    • 关系型数据库
    • No Sql
    • Console 控制台

    一般日志组件都可以自定义输出格式。

    3.1 快速使用

    一般这样写:
    在这里插入图片描述

    如果每次都写这行代码会很麻烦,可以使用注解,但是需要使用lombok(导入lombok依赖):
    在这里插入图片描述

    可以使用{} 占位符来拼接字符串,而不需要使用+来连接字符串:

    log.info("name:{},age:{}",name,age)
    
    • 1

    3.2 日志文件输出

    默认情况下,Spring Boot将日志输出到控制台,不会写到日志文件。

    使用Spring Boot在application.properties或application.yml配置,这样只能配置简单的场景,保存路径、日志格式等,复杂的场景(区分 info 和 error 的日志、每天产生一个日志文件等)满足不了,只能自定义配置

    如:

    # log日志配置
    logging:
      #level 日志等级 指定命名空间的日志输出
      level:
        com.itfuture.e.controller: ERROR
      #pattern 指定输出场景的日志输出格式
      pattern:
        console: "%d - %msg%n"
      path: "/var/log" #废弃,不推荐
      #file 指定输出文件的存储路径
      file:
        path: "./"
        max-history: 7 #日志保留天数
        clean-history-on-start: false
        name: "e-Spring.log" # 日志文件名称
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    总结

    如果要编写除控制台输出之外的日志文件,则需在application.properties中设置logging.filelogging.path属性。

    logging.file,设置文件的一些配置,比如清除历史、日志大小等,可以是绝对路径,也可以是相对路径。如:logging.file=my.log
    logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=/var/log
    如果只配置 logging.file,会在项目的当前路径下生成一个 xxx.log 日志文件。
    如果只配置 logging.path,在 /var/log文件夹生成一个日志文件为 spring.log.

    注:二者不能同时使用,如若同时使用,则只有logging.file生效 默认情况下,日志文件的大小达到10MB时会切分一次,产生新的日志文件,默认级别为:ERROR、WARN、INFO

    3.3 自定义配置

    根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:

    Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
    Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
    Log4j2:log4j2-spring.xml, log4j2.xml
    JDK (Java Util Logging):logging.properties

    Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项(下面会提到)。
    默认的命名规则,并且放在 src/main/resources 下面即可

    如果你即想完全掌控日志配置,但又不想用logback.xml作为Logback配置的名字,application.yml可以通过logging.config属性指定自定义的名字:

    logging.config=classpath:logging-config.xml
    
    • 1

        虽然一般并不需要改变配置文件的名字,但是如果你想针对不同运行时Profile使用不同的日志配置,这个功能会很有用。
        一般不需要这个属性,而是直接在logback-spring.xml中使用springProfile配置,不需要logging.config指定不同环境使用不同配置文件。springProfile配置在下面介绍。

    根据下面文件实例进行分析:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true" scanPeriod="60 seconds" debug="false">
    	<!--设置上下文名称-->
    	<!--每个logger都关联到logger上下文,默认上下文名称为“default”。
    	但可以使用设置成其他名字,用于区分不同应用程序的记录。
    	一旦设置,不能修改,可以通过%contextName来打印日志上下文名称
    	-->
       <contextName>logback</contextName>
       
       <!--输出到控制台-->
       <!--
       appender用来格式化日志输出节点,有俩个属性name和classclass用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。
       -->
       <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
           <!--<encoder>表示对日志进行编码-->
           <encoder>
               <!--格式化输出:%d{HH: mm:ss.SSS}表示日期,%thread表示线程名,%-5level:日志级别,并且使用5个字符靠左对齐,
               %logger{36}:日志输出者的名字,%msg:日志消息,%n是平台换行符
               -->
               <pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
               <charset>UTF-8</charset>
           </encoder>
       </appender>
    
       <!--按天生成日志-->
       <!--随着应用的运行时间越来越长,日志也会增长的越来越多-->
       <!--RollingFileAppender用于切分文件日志-->
       <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高,
        所以我们使用下面的策略,可以避免输出 Error 的日志-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--过滤 Error-->
            <level>ERROR</level>
            <!--匹配到就禁止-->
            <onMatch>DENY</onMatch>
            <!--没有匹配到就允许-->
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
            如果同时有<File><FileNamePattern>,那么当天日志是<File>,明天会自动把今天
            的日志改名为今天的日期。即,<File> 的日志都是当天的。
        -->
        <File>${logback.logdir}/info.${logback.appname}.log</File>
        <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
            <FileNamePattern>${logback.logdir}/info.${logback.appname}.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--只保留最近90天的日志-->
            <maxHistory>90</maxHistory>
            <!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
            <!--<totalSizeCap>1GB</totalSizeCap>-->
        </rollingPolicy>
        <!--日志输出编码格式化-->
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</pattern>
        </encoder>
    </appender>
    
    
    <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>Error</level>
        </filter>
        <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
            如果同时有<File><FileNamePattern>,那么当天日志是<File>,明天会自动把今天
            的日志改名为今天的日期。即,<File> 的日志都是当天的。
        -->
        <File>${logback.logdir}/error.${logback.appname}.log</File>
        <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
            <FileNamePattern>${logback.logdir}/error.${logback.appname}.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--只保留最近90天的日志-->
            <maxHistory>90</maxHistory>
            <!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
            <!--<totalSizeCap>1GB</totalSizeCap>-->
        </rollingPolicy>
        <!--日志输出编码格式化-->
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</pattern>
        </encoder>
    </appender>
    
    	<!--<loger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>-->
    	<!--name:用来指定受此loger约束的某一个包或者具体的某一个类。
    		level:用来设置打印级别
    		addtivity:是否向上级loger传递打印信息。默认是true
    	-->
       <logger name="com.fishpro.log" additivity="false">
           <appender-ref ref="console"/>
           <appender-ref ref="logFile"/>
       </logger>
       
       <!-- 设置Spring&Hibernate日志输出级别 -->
       <logger name="org.springframework" level="WARN"/>
       <logger name="org.mybatis" level="WARN"/> 
       <logger name="com.ibatis" level="DEBUG"/> 
       <logger name="com.ibatis.common.jdbc.SimpleDataSource" level="DEBUG"/> 
       <logger name="com.ibatis.common.jdbc.ScriptRunner" level="DEBUG"/> 
       <logger name="com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate" level="DEBUG"/>
    
    
          
       <logger name="java.sql.Connection" level="DEBUG"/>  
       <logger name="java.sql.Statement" level="DEBUG"/> 
       <logger name="java.sql.PreparedStatement" level="DEBUG"/> 
       <logger name="com.ruidou.baoqian.mapper" level="DEBUG"/>
    
       <!-- 开发环境下的日志配置 -->
       <!--root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性。-->
       <!-- 默认是DEBUG。可以包含零个或多个元素,标识这个appender将会添加到这个loger。-->
       <root level="error">
           <!--appender将会添加到这个loger-->
           <appender-ref ref="console"/>
           <appender-ref ref="logFile"/>
       </root>
    </configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121

    多环境日志输出

    <configuration>
        ...
        
        <!-- 测试环境+开发环境. 多个使用逗号隔开. -->
        <springProfile name="test,dev">
            <logger name="com.example.demo.controller" level="DEBUG" additivity="false">
                <appender-ref ref="consoleLog"/>
            </logger>
        </springProfile>
        
        <!-- 生产环境. -->
        <springProfile name="prod">
            <logger name="com.example.demo.controller" level="INFO" additivity="false">
                <appender-ref ref="consoleLog"/>
            </logger>
        </springProfile>
    </configuration>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4. Spring Boot 日志组件 Log Plugin

    当然自己开发日志组件也是可以的,实际上也不是很难,在一些特殊场景,很多公司都是用自己开发的日志组件,但是对于大多数应用来说,我们使用标准的日志组件就可以解决我们的问题。

    Spring Boot 日志组件最为常见的包括了

    Logback Spring Boot 约定的默认配置
    log4j
    log4j2
    slf4j
    JUL

    大部分场景我们都是推荐 Spring Boot 自带的日志logback

    二、Spring Boot Logback

    Logback是由log4j创始人设计的又一个开源日志组件。目前,logback分为三个模块:logback-core,logback-classic和logback-access。是对log4j日志展示进一步改进

    在 Spring Boot 中,logback 是基于 slf4j 实现的。

    slf4j的全称是Simple Loging Facade For Java,即它仅仅是一个为Java程序提供日志输出的统一接口,并不是一个具体的日志实现方案,他能够实现大部分 日志组件。

    logback 中使用了 slf4j,logback-classic.jar,logback-core.jar 实现了 logback 日志功能。

    1.依赖配置 Pom.xml

    网上不少人贴出了 logback 的依赖配置,实际上从依赖包中可以看出,默认是不需要单独配置 logback 依赖的。不需要单独配置、不需要单独配置、不需要单独配置

    2.使用 YML 配置 logback

    2.1 编写一个 Controller 层类作为测试用 IndexController

    
    @Controller
    public class IndexController {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        @RequestMapping("/")
        String index(){
            logger.debug("This is a debug message");
            logger.info("This is an info message");
            logger.warn("This is a warn message");
            logger.error("This is an error message");
            return "index";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    根据日志输出结果可以看出,我们没有 DEBUG 的日志,也就是说 logger.debug("This is a debug message"); 没有被输出到控制台。

    实际上,Spring Boot 是一种约定大于配置,也就是说,它本身有一个配置文件叫base.xml,在这个配置文件里面,已经默认配置了输出的日志级别 让我们来看看 base.xml ,如下面的 xml 代码所示,root level=“INFO” 已经定义了日志输出级别为 INFO 。这就是为什么,我看到控制台没有输出 DEBUG 那条日志。

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!--
    Base logback configuration provided for compatibility with Spring Boot 1.1
    -->
    
    <included>
      <include resource="org/springframework/boot/logging/logback/defaults.xml" />
      <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
      <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
      <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
      <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
      </root>
    </included>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.2 在 application.yml 中配置
    如下配置,设置level的级别为 DEBUG

    logging:
      level:
        com.fishpro.log: debug
    
    • 1
    • 2
    • 3

    运行结果可知,可以看出已经有了 DEBUG 日志。

    2.3 在 application.yml 详细配置

    logging:
      #level 日志等级 指定命名空间的日志输出
      level:
        com.fishpro.log: debug
      #file 指定输出文件的存储路径
      file: logs/app.log
      #pattern 指定输出场景的日志输出格式
      pattern:
        console: "%d %-5level %logger : %msg%n"
        file: "%d %-5level [%thread] %logger : %msg%n"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    同时我们可以看到在项目根路径下多处理 logs 文件夹,下面有 app.log 文件,app.log 文件同时记录了日志的输出。

    2.4 不同环境中配置

    我们可以在 dev、prod、test 等环境中配置不同的 log 配置项目,我们只需要根据我们的配置文件来设置即可。

    关于如何在 Spring Boot 中配置不同的环境实现 开发环境(dev)、测试环境(test)、生成环境(prod)分离,请参考Spring Boot 中多环境配置

    3.使用 logback-spring.xml 来配置 logback 日志

    如果需要更为详细的自定义 logback 日志,那么我们需要使用 xml 配置文件来配置。

    使用logback-spring.xml来自定义日志的配置,主要需要了解 logback-spring.xml 详细功能点,能够配置哪些内容。

    3.1指定 logback-spring.xml 路径

    首先我们要知道 logback-spring.xml 文件放在哪里。

    默认 logback-spring.xml 路径在 resource 文件夹下,即你只要在 resource 文件夹下新建文件 logback-spring.xml 即可。

    3.2使用 logback-spring.xml 自定义配置
    这里我们使用自定义配置 实现:
        1.把日志输出到指定的目录,并按照日期 yyyy-MM-dd 的格式按天存储,注意如果没有特别指定,这里的目录 applog 就是指项目的根目录(如果您的项目是多模块配置并不是指模块的目录)
        2.配置 xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true" scanPeriod="60 seconds" debug="false">
       <contextName>logback</contextName>
       <!--输出到控制台-->
       <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
           <encoder>
               <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
               <pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
               <charset>UTF-8</charset>
           </encoder>
       </appender>
    
       <!--按天生成日志-->
       <appender name="logFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
           <Prudent>true</Prudent>
           <!-- 过滤器,只打印ERROR级别的日志 -->
           <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
               <!--日志文件输出的文件名-->
               <FileNamePattern>
                   applog/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log
               </FileNamePattern>
               <!--日志文件保留天数-->
               <MaxHistory>15</MaxHistory>
           </rollingPolicy>
           <layout class="ch.qos.logback.classic.PatternLayout">
               <Pattern>
                   %d{yyyy-MM-dd HH:mm:ss} -%msg%n
               </Pattern>
           </layout>
       </appender>
    
       <logger name="com.fishpro.log" additivity="false">
           <appender-ref ref="console"/>
           <appender-ref ref="logFile"/>
       </logger>
       
       <!-- 设置Spring&Hibernate日志输出级别 -->
       <logger name="org.springframework" level="WARN"/>
       <logger name="org.mybatis" level="WARN"/> 
       <logger name="com.ibatis" level="DEBUG"/> 
       <logger name="com.ibatis.common.jdbc.SimpleDataSource" level="DEBUG"/> 
       <logger name="com.ibatis.common.jdbc.ScriptRunner" level="DEBUG"/> 
       <logger name="com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate" level="DEBUG"/>
    
    
          
       <logger name="java.sql.Connection" level="DEBUG"/>  
       <logger name="java.sql.Statement" level="DEBUG"/> 
       <logger name="java.sql.PreparedStatement" level="DEBUG"/> 
       <logger name="com.ruidou.baoqian.mapper" level="DEBUG"/>
    
       <!-- 开发环境下的日志配置 -->
       <root level="error">
           <appender-ref ref="console"/>
           <appender-ref ref="logFile"/>
       </root>
    </configuration>
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    运行结果
    运行后发现,工程根目录多出来 applog 文件夹,applog/2022-11-18/2022-11-18.log 按照日期分类来记录文件,这很重要。

    优先级
    有趣的是,当我们把 xml 文件同 application.yml 都进行配置的时候,他执行了 logback-spring.xml 中的配置,所有 logback-spring.xml 配置是优先的

    想要知道logback-spring.xml配置的具体意义,请阅读本篇第 ·3.3 自定义配置

  • 相关阅读:
    k8s-List机制
    Harmony import和export
    为什么学完了 C#觉得自己什么都干不了?
    【从小白到大白01】c++类和对象
    【React源码】(十一)fiber 树渲染
    微信小程序开发引入RUM,实现小程序监控
    卷积神经网络创新点思考
    一文帮你搞定H5、小程序、Taro长列表曝光埋点
    关于java业务层是否抛异常
    SpreadJS 15.1 CN 与 SpreadJS 15.1 EN
  • 原文地址:https://blog.csdn.net/weixin_43431218/article/details/127888354