spring启动时如何自定义日志实现

一、现象

最近在编写传统的springmvc项目时,遇到了一个问题:虽然在项目的web.xml中指定了log4j的日志启动监听器Log4jServletContextListener,且开启了日志写入文件,但是日志文件中只记录业务代码中我们声明了日志记录器的日志,无法记录spring上下文启动的日志。

测试方法如下:

package com.vat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


/**
 * @author:whh
 * @date: 2024-02-01 21:02
 * <p></p>
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationcontext.xml"})
public class MvcTest {
    @Autowired
    ApplicationContext ctx;
    private static Logger LOGGER  = LoggerFactory.getLogger(MvcTest.class);
    @Test
    public void fun(){
        LOGGER.debug("123");
        System.out.println(ctx);
    }
}

观察下面的运行结果截图,我们发现红色字体是spring上下文的启动日志而灰色字体是我们打印的日志,而且在日志文件中也只记录了我们打印的日志和druid数据源的关闭日志。实时上spring上下文的启动日志也非常重要,通常用于我们观察我们的应用是否启动正常,有没有一些bean没有被spring初始化到。但是从上面看其启动日志并未记录到日志文件中。
在这里插入图片描述

二、思考

遇到这个问题后,便使用debug模式追踪spring上下文的启动。

首先这个测试类有一个注解@RunWith(SpringJUnit4ClassRunner.class)
即我们可以猜测是通过SpringJUnit4ClassRunner来启动spring上下文的,接着我们看下这个类的源码。

public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {

	private static final Log logger = LogFactory.getLog(SpringJUnit4ClassRunner.class);

	private static final Method withRulesMethod;

	// Used by RunAfterTestClassCallbacks and RunAfterTestMethodCallbacks
	private static final String MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME = "org.junit.runners.model.MultipleFailureException";

	static {
		boolean junit4dot9Present = ClassUtils.isPresent(MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME,
			SpringJUnit4ClassRunner.class.getClassLoader());
		if (!junit4dot9Present) {
			throw new IllegalStateException(String.format(
				"Failed to find class [%s]: SpringJUnit4ClassRunner requires JUnit 4.9 or higher.",
				MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME));
		}

注意看第一行是使用静态常量初始化了一个logger对象并且这个Log的类型是org.apache.commons.logging.Log类型,学过类加载的我们都知道,静态常量是在编译器就会进行初始化。

接着进入getFactory方法,首先会获取一个类加载器,我们可以看出是
lanucherAppClassLoader。
在这里插入图片描述

接着往下走会往缓存中获取LogFactory,第一次肯定是null的。
在这里插入图片描述

接下来加载commons-logging.properties的配置文件,但是咱们没有配置,所以是空的。
在这里插入图片描述
最终创建了一个默认的LogFactoryImpl。
在这里插入图片描述

调用的LogFactoryImpl来创建Log

在这里插入图片描述
后面到discoverLogImplementation方法,确定具体的日志实现。

private Log discoverLogImplementation(String logCategory)
        throws LogConfigurationException {
        if (isDiagnosticsEnabled()) {
            logDiagnostic("Discovering a Log implementation...");
        }

        initConfiguration();

        Log result = null;

        // See if the user specified the Log implementation to use
        String specifiedLogClassName = findUserSpecifiedLogClassName();

        if (specifiedLogClassName != null) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("Attempting to load user-specified log class '" +
                    specifiedLogClassName + "'...");
            }

            result = createLogFromClass(specifiedLogClassName,
                                        logCategory,
                                        true);
            if (result == null) {
                StringBuffer messageBuffer =  new StringBuffer("User-specified log class '");
                messageBuffer.append(specifiedLogClassName);
                messageBuffer.append("' cannot be found or is not useable.");

                // Mistyping or misspelling names is a common fault.
                // Construct a good error message, if we can
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);
                informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);
                throw new LogConfigurationException(messageBuffer.toString());
            }

            return result;
        }

最终确定为jdk的日志实现。

由于spring也是使用的org.apache.commons.logging.Log作为日志适配器,,所以会使用缓存的日志工厂LogFactoryImpl,即其日志实现也是jdk的日志,,所以和咱们自己配置log4j实现根本无关也就造成了spring上下文的日志未写入文件。

三、问题解决

怎么解决这个问题呢?这边有两个方案。
方案1:刚刚我们在调试模式模式的时,实例化LogFatory时会加载一个commons-logging.properties然后在该配置文件中加入属性
org.apache.commons.logging.LogFactory=XXX,其中XXX是自定义的LogFactory,然后在该LogFactory中指定日志的实现,但是这种方式还是有些复杂的。

方案2:使用spring的jcl方案,其实这个jcl方案是我参考了springboot启动时的日志实现原理,所以咱们只要引入下面的依赖,版本可以看实际情况,本项目的spring版本为4.2.4。

 <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jcl</artifactId>
      <version>5.3.5</version>
    </dependency>

其原理是基于spi机制,通过LogAdapter进行检测具体的日志实现。
在这里插入图片描述

LogAdapter的static静态代码块会确定具体的日志实现。

在这里插入图片描述

四、结果

集成spring jcl后,咱么在启动测试类,观察结果返现控制台即文件输出日志都换成了咱们指定的log4j日志实现。
在这里插入图片描述

而且咱们使用jetty的maven插件,启动咱们的项目,也是有相同效果的。
在这里插入图片描述

五、最后

完整项目,大家可以到我的码云自行下载:

https://gitee.com/whhdev/vat/tree/master

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTML静态网页成品作业(HTML+CSS)——电影加勒比海盗介绍设计制作(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

数据结构从入门到精通——树和二叉树

树和二叉树 前言一、树概念及结构1.1树的概念1.2 树的相关概念&#xff08;重要&#xff09;1.3 树的表示1.4 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 二、二叉树概念及结构2.1二叉树概念2.2现实中的二叉树2.3 特殊的二叉树2.4 二叉树的性质2.5 二叉…

PCB差分通孔的数值建模方法

目录 0 引言 1 基于CST的3D通孔模型 2 通孔模型的近似等效计算 3 利用ADS进行电路仿真分析 4 总结 0 引言 当数据速率超过10Gbps时&#xff0c;PCB上的通孔所带来的寄生参数会成为影响数据误码率的关键因素之一&#xff0c;虽然通过三维电磁场求解器提取过孔的行为模型&…

rust入门(1)创建项目

安装 vscode 安装插件 rust-analyzerNative Debug vscode 配置自动格式化代码 settings.json{"editor.defaultFoldingRangeProvider": null,"[rust]": {"editor.defaultFormatter": "rust-lang.rust-analyzer", // Makes the magi…

Python 井字棋游戏

井字棋是一种在3 * 3格子上进行的连珠游戏&#xff0c;又称井字游戏。井字棋的游戏有两名玩家&#xff0c;其中一个玩家画圈&#xff0c;另一个玩家画叉&#xff0c;轮流在3 * 3格子上画上自己的符号&#xff0c;最先在横向、纵向、或斜线方向连成一条线的人为胜利方。如图1所示…

静态时序分析:SDC约束命令set_output_delay详解

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 目录 指定延迟值 指定端口、引脚列表 指定参考时钟 简单使用 指定时钟下降沿 指定参考端口、引脚 包含源、网络延迟 指定电平敏感 指定上升、下降沿 指…

Redux Toolkit

本文作者为 360 奇舞团前端开发工程师 阅读本文章前&#xff0c;需要先了解下 redux 的基本概念与用法&#xff0c;Redux Toolkit 是建立在 Redux 基础之上的工具包&#xff0c;因此需要对 Redux 的基本概念有一定的了解&#xff0c;包括 Action、Reducer、Store、Middleware 等…

C#四部曲(知识补充)

Unity跨平台原理 .Net相关 只要编写的时候遵循.NET的这些规则&#xff0c;就能在.NET平台下通用 各种源码→根据.NET规范编写→(虚拟机)生成CIL中间码(保存在程序集中)→转成操作系统原代码 跨语言← 跨平台↓ Unity跨平台原理&#xff08;Mono&#xff09; c#脚本→MonoC#编…

低压线性恒流LED恒流驱动芯片SM15633EH:用于洗墙灯和线条灯

洗墙灯和线条灯是两种常见的LED照明产品&#xff0c;它们都需要使用LED恒流驱动芯片来确保稳定、可靠的电流供应&#xff0c;从而保证LED的使用寿命和亮度。 对于洗墙灯而言&#xff0c;由于其发出的光线需要覆盖较大的区域&#xff0c;因此需要使用较大功率的LED芯片&#xf…

Linux操作系统与Windows文件互传(FTP)

一、开启Ubuntu下的FTP服务 打开Ubuntu的终端窗口&#xff0c;然后执行如下命令来安装 FTP服务。 sudo apt-get install vsftpd等待软件安装完成后&#xff0c;用输入以下命令打开vsftpd.conf文件 sudo vim /etc/vsftpd.conf 找到下图的两个使能语句改成如图即可(记住保存后再…

新版哥白尼系统快速下载哨兵数据

在新版哥白尼系统下载数据,总是failed或者速度很慢,如何实现MB/s的下载速度,只需要四步就可以解决: 1 登录系统,找到想下载的数据,点开product info按扭,​​​​​​ 2. 找到数据的Product id和Name, #Product id:https://zipper.dataspace.copernicus.eu/odata/v1/P…

Selenium操作浏览器,弹出文件选择框,实现自动选定“目标文件”

前言 本文是该专栏的第20篇,后面会持续分享python爬虫干货知识,记得关注。 我们在使用selenium操作目标页面的时候,可能会遇到如下图所示的情景。 在用selenium操作并点击页面元素的时候,会弹出一个文件选择框,需要我们选择目标文件,并点击确认按钮,目标文件才能上传成…

Stream-JDK8

Stream概念 代码示例 package com.zz.stream;import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors;/*** 使用Stream流* 找出姓张并且是三个字的名字*/ public class Test {public static void main(Stri…

cesiumlab白模效果一

效果 步骤 1、准备shp面数据 2、打开cesiumlab软件转换 选择shp面数据 设置高度&#xff0c;如果shp面中有高度字段&#xff0c;可以用高度字段&#xff0c;如果没有&#xff0c;可以用固定高度 设置贴图&#xff0c;这边用的是第二张效果&#xff0c;当然也可以用自己的贴图…

‘UnityEngine.Application‘ does not contain a definition for isBatchMode

unity 2017.4.37f1. 解决办法: Try to replace Application.isBatchMode with UnityEditorInternal.InternalEditorUtility.inBatchMode

MySQL实战:问题排查与监控

常见问题 有更合适的索引不走&#xff0c;怎么办&#xff1f; MySQL在选取索引时&#xff0c;会参考索引的基数&#xff0c;基数是MySQL估算的&#xff0c;反映这个字段有多少种取值&#xff0c;估算的策略为选取几个页算出取值的平均值&#xff0c;再乘以页数&#xff0c;即…

AI新工具(20240312) Midjourney官方发布角色一致性功能;免费且开源的简历制作工具;精确克隆语调、控制声音风格

1: Midjourney角色一致性功能 使人物画像在多方面高度一致成为可能。 Midjourney的角色一致性功能的使用方法如下&#xff1a; ⭐在你的输入指令后面加上 --cref URL&#xff0c;其中URL是你选择的角色图像的链接。 ⭐你可以通过 --cw 参数来调整参照的强度&#xff0c;范围…

Selenium控制已运行的Edge和Chrome浏览器(详细启动步骤和bug记录)

文章目录 前期准备1. 浏览器开启远程控制指令&#xff08;1&#xff09;Edge&#xff08;2&#xff09;Chrome 2. 执行python代码&#xff08;1&#xff09;先启动浏览器后执行代码&#xff08;2&#xff09;通过代码启动浏览器 3. 爬取效果3. 完整代码共享3.1 包含Excel部分的…

旅游景区公共广播 园区广播 公路服务区广播

旅游景区公共广播 园区广播 公路服务区广播 旅游景区公共广播 旅游景区公共广播(又称背景音乐)简称BGM&#xff0c;它的主要作用是掩盖噪声并创造一种轻松和谐的气氛&#xff0c;是一种创造轻松愉快环境气氛的音乐。掩盖环境噪声&#xff0c;创造与旅游景区相适应的气氛&#…

ubuntu20.04上获取Livox Avia雷达点云数据

若拿到手的Livox Avia激光雷达不知道它的ip信息&#xff0c;可以在官网上LiDAR Sensors - Livox下载上位机软件Livox Viewer&#xff0c;查看IP&#xff0c;下载window版本就可以。雷达通过网线连上电脑后&#xff0c;该软件就可以自动识别出来。按照下图步骤&#xff0c;就可以…