随笔记录-springboot_LoggingApplicationListener+LogbackLoggingSystem

环境:springboot-2.3.1

加载日志监听器初始化日志框架

SpringApplication#prepareEnvironment
SpringApplicationRunListeners#environmentPrepared
EventPublishingRunListener#environmentPrepared
SimpleApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)
获取各类监听器(其中就有一个 LoggingApplicationListener 监听器),执行onApplicationEvent方法
 LoggingApplicationListener监听器

如何获取各类监听器?该怎么添加自定义监听器?
看源码。是通过this.defaultRetriever.applicationListeners匹配过滤后得到的,这个是通过spring.factories机制获取org.springframework.context.ApplicationListener 得到并在 SpringApplication 构造函数中调用设置
spring.factories配置监听器
因此,自定义时,在自己项目classpath路径创建META-INF/spring.factories文件,内容为
org.springframework.context.ApplicationListener=自定义监听器的全限定名
示例
上述监听器是参考AnsiOutputApplicationListener。自定义监听器最终能够实现org.springframework.context.ApplicationListener<E extends ApplicationEvent>接口即可(一般需要排序,再加上org.springframework.core.Ordered接口),具体依据实际需求来定义。

刚启动时,LoggingApplicationListener 就会执行 onApplicationEnvironmentPreparedEvent --> initialize
初始化日志框架

日志框架加载

LoggingApplicationListener#initialize
执行初始化

LoggingSystemProperties#apply(LogFile logFile) 中预置了一些系统属性,所以在一些日志中才有${XXX}引入有效,如${PID}
预置系统属性
LoggingApplicationListener#initializeSystem
加载具体日志系统
默认读取classpath日志配置文件,如logback日志系统默认配置(下图所示,读取第一个存在的配置;否则在此基础上,读取含-spring的对应配置,如logback-spring.xml);若是配置了logging.config,直接读取该配置文件。
在这里插入图片描述
读取默认配置文件读取spring后缀配置

logback日志

在logback-spring.xml中,使用${key:-默认值}形式表示获取变量key的值或者默认值(通过:-分隔),其中key就是普通字符串,像那些xxx.xxx.xxx形式的可以,本身名称就是这个(一般而言将json结构扁平化处理就是这种格式),并不是代表获取json结构(如Map嵌套)中对应层的值(刚开始以为可以这样的😂 看源码实际并不支持)

LogbackLoggingSystem#loadConfiguration
LogbackLoggingSystem#configureByResourceUrl
GenericConfigurator#doConfigure(java.net.URL)
… 经过一些列doConfigure重载方法
GenericConfigurator#doConfigure(java.util.List<ch.qos.logback.core.joran.event.SaxEvent>)
EventPlayer#play


Interpreter#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)

这里根据标签获取对应的Action来解析属性,如xml中的标签对应着[configuration]、[configuration][property]、[configuration][appender]、[configuration][root]、[configuration][logger][appender-ref]等key,都有关联这个一个处理类

Interpreter#callBeginAction

property标签对应[configuration][property],关联PropertyAction类

PropertyAction#begin
InterpretationContext#subst
OptionHelper#substVars(java.lang.String, PropertyContainer, PropertyContainer)
NodeToStringTransformer#substituteVariable
NodeToStringTransformer#transform
NodeToStringTransformer#compileNode
NodeToStringTransformer#handleVariable

compileNode方法和handleVariable存在着递归:节点属于变量就会调用handleVariable;属于文本就直接拼接

NodeToStringTransformer#lookupKey

private String lookupKey(String key) {
	// 从ch.qos.logback.core.joran.spi.InterpretationContext.getProperty上下文中获取
    String value = this.propertyContainer0.getProperty(key);
    if (value != null) {
        return value;
    } else {
        if (this.propertyContainer1 != null) {
        	// 从ch.qos.logback.classic.LoggerContext.getProperty上下文中获取
            value = this.propertyContainer1.getProperty(key);
            if (value != null) {
                return value;
            }
        }

        // 从系统属性System.getProperty获取
        value = OptionHelper.getSystemProperty(key, (String)null);
        if (value != null) {
            return value;
        } else {
        	// 从系统环境变量System.getenv获取
            value = OptionHelper.getEnv(key);
            return value != null ? value : null;
        }
    }
}

logback出现“变量名_IS_UNDEFINED”情况,就是在handleVariable方法处理时,既没有变量值(lookupKey方法返回null),又没有默认值,就出现这个问题

logback-spring.xml示例(部分说明来自网络)

<?xml version="1.0" encoding="UTF-8"?>
<!-- 分级别异步文件日志输出配置 -->
<!-- 级别从高到低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日志输出规则 根据当前ROOT 级别,日志输出时,级别高于root默认的级别时 会输出 -->
<!-- 以下 每个配置的 filter 是过滤掉输出文件里面,会出现高级别文件,依然出现低级别的日志信息,通过filter 过滤只记录本级别的日志 -->
<!-- scan 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 -->
<!-- scanPeriod 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!--<contextListener class="xxx.xxxxxx.listener.LogbackStartupListener"/>-->
    <!-- SpringBoot自带的logback配置文件,包含 CONSOLE 和 FILE 两个appender -->
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <!--
    说明:
        1. 文件的命名和加载顺序有关
           logback.xml早于application.yml加载,logback-spring.xml晚于application.yml加载
           如果logback配置需要使用application.yml中的属性,需要命名为logback-spring.xml
        2. logback使用application.yml中的属性
           使用springProperty才可使用application.yml中的值 可以设置默认值
        3、springProperty的source属性对应application.yml的键,需注意的是,需要使用全小写名称(有下划线的去掉)
        4、springProperty标签在重新加载(scan为true)导致no applicable action for [springProperty], current ElementPath  is [[configuration][springProperty]],进而导致设置的变量都是无定义的
        5、通过监听器注入相关的变量
    -->

    <!--<property name="logPath4399" value="F:/temp/logs/4399"/>
    <property name="logPattern4399" value="%date{yyyy-MM-dd HH:mm:ss}|%msg%n"/>-->
    <springProperty scope="context" name="logPath4399" source="logback.prop.platform.4399.path" defaultValue="./"/>
    <springProperty scope="context" name="logPattern4399" source="logback.prop.platform.4399.pattern" defaultValue="%date{yyyy-MM-dd HH:mm:ss}|%msg%n"/>
    <springProperty scope="context" name="logMaxHistory4399" source="logback.prop.platform.4399.maxhistory" defaultValue="15"/>

    <springProperty scope="context" name="logPath4399WYY" source="logback.prop.platform.4399.app.wyy.path" defaultValue="${logPath4399}"/>
    <springProperty scope="context" name="logPattern4399WYY" source="logback.prop.platform.4399.app.wyy.pattern" defaultValue="${logPattern4399}"/>
    <springProperty scope="context" name="log4399WYYSimpleName" source="logback.prop.platform.4399.app.wyy.simplename"/>
    <springProperty scope="context" name="log4399WYYLogName" source="logback.prop.platform.4399.app.wyy.logname"/>
	
	<!-- 动态日志级别:指的是可以通过jconsole等工具进行设置 -->
    <!--<jmxConfigurator/>-->

    <!--输出到文件,只记录INFO级别信息-->
    <appender name="FILE_4399_WYY" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 4399聊天日志: {game}_{YYYYMMdd}.log的形式 -->
        <!-- file不支持%d{yyyyMMdd} -->
        <!--<file>${logPath4399WYY}${log4399WYYSimpleName}_%d{yyyyMMdd}.log</file>-->
        <!-- 日志输出格式 -->
        <encoder>
            <pattern>${logPattern4399WYY}</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--
            TimeBasedRollingPolicy基于时间的滚动策略
            timeBasedFileNamingAndTriggeringPolicy标签: 设置触发滚动时机,放在的子节点的位置,基于实践策略的触发滚动策略
            SizeAndTimeBasedRollingPolicy基于时间和文件大小的触发策略,相当于 TimeBasedRollingPolicy+timeBasedFileNamingAndTriggeringPolicy的SizeAndTimeBasedFNATP

            注: SizeAndTimeBasedRollingPolicy(TimeBasedRollingPolicy+timeBasedFileNamingAndTriggeringPolicy)必须指定%d日期和%i索引,
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logPath4399WYY}${log4399WYYSimpleName}_%d{yyyyMMdd}.log</fileNamePattern>
            <!-- 日志文档保留数量(按天滚动相当于天数) -->
            <maxHistory>${logMaxHistory4399}</maxHistory>
            <!--<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>-->
        </rollingPolicy>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <!--<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            &lt;!&ndash; 每天日志归档路径以及格式: %d{yyyy-MM-dd}指定日期格式,%i指定索引 &ndash;&gt;
            <fileNamePattern>${logPath4399WYY}${log4399WYYSimpleName}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            &lt;!&ndash; 日志文档保留数量(按天滚动相当于天数) &ndash;&gt;
            <maxHistory>15</maxHistory>
            &lt;!&ndash;<totalSizeCap>10GB</totalSizeCap>&ndash;&gt;
        </rollingPolicy>-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 此日志文件只记录info级别的 -->
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        logger标签用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
        logger标签仅有一个name属性, 一个可选的level和一个可选的additivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              如果未设置此属性,那么当前logger将会继承上级的级别。
        additivity:是否向上级logger传递打印信息,默认是true
    -->
    <logger name="com.origin.remote.chat.provider.ChatFor4399Provider.${log4399WYYLogName}" level="INFO" additivity="false">
        <!-- 引用的appender,类似于spring的ref -->
        <appender-ref ref="FILE_4399_WYY" />
    </logger>
</configuration>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
K-means聚类在SPSS中使用较为快捷方便。首先,需要指定需要划分的类的个数,即K值,选择K个数据对象作为初始聚类中心。然后,计算各个数据对象到这K个聚类中心的距离,并据此进行归类。接下来,不断重复计算聚类中心和重新归类的步骤,直到得到较优的聚类结果。在SPSS中,可以直接划分类别,仅进行分类而不进行迭代。 对于k-means聚类,在SPSS中的具体方法我没有查到相关信息。如果你需要更详细的信息,建议您查阅SPSS的官方文档或者向SPSS的技术支持团队咨询,他们会提供更准确的信息和帮助。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [学习笔记1 三大聚类方法:K-means聚类、层次聚类、DBSCAN聚类](https://blog.csdn.net/m0_63639368/article/details/124395427)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [spss--K_means快速聚类(随笔笔记)以及和Python实现K_means聚类的比较](https://blog.csdn.net/PY_smallH/article/details/120783492)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值