log4j2或者logback配置模版实现灵活输出服务名
介绍
在我们使用log4j2或者logback打印日志时,输出的内容中通常是一定要加上服务名的。以log4j2为例:
<!--输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT"><!-- 输出日志的格式 --><PatternLayout pattern="server-case %d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n"/>
</Console>
服务名为server-case,输出的内容为
server-case 2023-09-15 17:44:38 INFO ServerCaseApplication:648 - No active profile set, falling back to default profiles: default
server-case 2023-09-15 17:44:39 INFO TomcatWebServer:108 - Tomcat initialized with port(s): 7081 (http)
server-case 2023-09-15 17:44:39 INFO Http11NioProtocol:173 - Initializing ProtocolHandler ["http-nio-7081"]
server-case 2023-09-15 17:44:39 INFO StandardService:173 - Starting service [Tomcat]
...
但是有种情况,有的项目要部署在甲方内网或者连接甲方的资源。项目是同一套代码,但要服务于不同的甲方,所以一个项目会有不同的服务名的情况。
这样的话,服务名就不能写死,要根据不同的服务名来输出。
问题
有的人可能会想到直接设置一个对象实现EnvironmentAware接口中的setEnvironment(Environment environment),来获取environment来获取spring.application.name
但这样有问题,设置的这个对象是要在spring上下文中进行加载后才能获得environment,所以在这个对象加载之前的日志输出还是拿不到environment的
@Component
public class Test implements EnvironmentAware {private Environment environment;@Overridepublic void setEnvironment(final Environment environment) {this.environment = environment;System.setProperty("applicationName", Objects.requireNonNull(environment.getProperty("spring.application.name")));}
}
<!--输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT"><!-- 输出日志的格式 --><PatternLayout pattern="${sys:applicationName} %d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n"/>
</Console>
${sys:applicationName} 2023-09-18 10:18:34 INFO ServerCaseApplication:55 - Starting ServerCaseApplication on lukuan with PID 21472 (D:\idea_work_my\gitee\cook-frame\server\server-case\target\classes started by lukuan in D:\idea_work_my\gitee\cook-frame)
${sys:applicationName} 2023-09-18 10:18:34 INFO ServerCaseApplication:648 - No active profile set, falling back to default profiles: default
${sys:applicationName} 2023-09-18 10:18:34 INFO TomcatWebServer:108 - Tomcat initialized with port(s): 7081 (http)
${sys:applicationName} 2023-09-18 10:18:34 INFO Http11NioProtocol:173 - Initializing ProtocolHandler ["http-nio-7081"]
${sys:applicationName} 2023-09-18 10:18:34 INFO StandardService:173 - Starting service [Tomcat]
${sys:applicationName} 2023-09-18 10:18:34 INFO StandardEngine:173 - Starting Servlet engine: [Apache Tomcat/9.0.46]
${sys:applicationName} 2023-09-18 10:18:34 INFO [/]:173 - Initializing Spring embedded WebApplicationContext
${sys:applicationName} 2023-09-18 10:18:34 INFO ServletWebServerApplicationContext:285 - Root WebApplicationContext: initialization completed in 795 ms_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\ / | 3.5.1
service-case 2023-09-18 10:18:35 INFO ThreadPoolTaskExecutor:181 - Initializing ExecutorService 'applicationTaskExecutor'
service-case 2023-09-18 10:18:35 INFO PropertySourcedRequestMappingHandlerMapping:69 - Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]
service-case 2023-09-18 10:18:35 INFO Http11NioProtocol:173 - Starting ProtocolHandler ["http-nio-7081"]
service-case 2023-09-18 10:18:35 INFO TomcatWebServer:220 - Tomcat started on port(s): 7081 (http) with context path ''
可以看到比较靠前的日志输出中的applicationName变量是没有被替换成真正的服务名的。
那怎么办呢?
分析
所以我们要从SpringBoot的启动过程入手
SpringApplication#run(String… args)
public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);Banner printedBanner = printBanner(environment);context = createApplicationContext();prepareContext(context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}listeners.started(context);callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;
}
突破点在environment的创建这步骤,所以分析prepareEnvironment(listeners, applicationArguments)看能不能实现我们想要的需求
SpringApplication#prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {// Create and configure the environmentConfigurableEnvironment environment = getOrCreateEnvironment();configureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);//经过debug发现当这步方法执行完后,environment.getProperty("spring.application.name")是可以获取值listeners.environmentPrepared(environment);bindToSpringApplication(environment);if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}ConfigurationPropertySources.attach(environment);return environment;
}
listeners.environmentPrepared(environment);为关键点,environment.getProperty("spring.application.name")就是在此进行完成的,所以我们要进入分析。
SpringApplicationRunListeners#environmentPrepared
void environmentPrepared(ConfigurableEnvironment environment) {for (SpringApplicationRunListener listener : this.listeners) {listener.environmentPrepared(environment);}
}
listeners有一个实现类EventPublishingRunListener
public void environmentPrepared(ConfigurableEnvironment environment) {this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
这里是发布了一个ApplicationEnvironmentPreparedEvent事件。
既然有发布,就有监听。我们接下来分析下监听事件的逻辑
ConfigFileApplicationListener#onApplicationEvent
public void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationEnvironmentPreparedEvent) {onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);}if (event instanceof ApplicationPreparedEvent) {onApplicationPreparedEvent(event);}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();postProcessors.add(this);AnnotationAwareOrderComparator.sort(postProcessors);for (EnvironmentPostProcessor postProcessor : postProcessors) {postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());}
}
postProcessors有多个,其中ConfigFileApplicationListener就是设置spring.applicaton.name的值。至于怎么设置的,就不进去分析了,有兴趣的同学可以自行去仔细研究。
通常上述分析,我们知道了Environment设置spring.applicaton.name值的步骤,那么我们可不可以紧接着,这个设置步骤之后就进行自定义设值,然后让log4j2来取呢?
答案是当然可以!
让我们回到SpringApplicationRunListeners#environmentPrepared
SpringApplicationRunListeners#environmentPrepared
void environmentPrepared(ConfigurableEnvironment environment) {for (SpringApplicationRunListener listener : this.listeners) {listener.environmentPrepared(environment);}
}
默认listeners为一个,那我们只要自己实现一个,让listeners为2个,第一个默认执行完后,不就能紧接着执行我们的了吗,那我们分析下listeners是怎么来的
SpringApplication#getRunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = getClassLoader();// Use names and ensure unique to protect against duplicatesSet<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;
}
SpringFactoriesLoader.loadFactoryNames(type, classLoader)和自动装配的方法很像啊,那去看一眼
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
果然在文件中指定了org.springframework.boot.context.event.EventPublishingRunListener,也就是默认的SpringApplicationRunListener实现类。
然后在分析createSpringFactoriesInstances看看是如何加载的
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,ClassLoader classLoader, Object[] args, Set<String> names) {List<T> instances = new ArrayList<>(names.size());for (String name : names) {try {Class<?> instanceClass = ClassUtils.forName(name, classLoader);Assert.isAssignable(type, instanceClass);Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);T instance = (T) BeanUtils.instantiateClass(constructor, args);instances.add(instance);}catch (Throwable ex) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);}}return instances;
}
这里就是创建对象的过程了,用的是有参构造方法需要两个参数constructor, args,所以我们参考EventPublishingRunListener的结构来实现即可
实现
自定义SpringApplicationRunListener的实现类CustomEventPublishingRunListener
CustomEventPublishingRunListener
public class CustomEventPublishingRunListener implements SpringApplicationRunListener, Ordered {private static final String SPRING_APPLICATION_NAME = "spring.application.name";private final SpringApplication application;private final String[] args;public CustomEventPublishingRunListener(SpringApplication application, String[] args){this.application = application;this.args = args;}@Overridepublic int getOrder() {return 1;}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {String applicationName = environment.getProperty(SPRING_APPLICATION_NAME);if (StringUtil.isNotEmpty(applicationName)) {System.setProperty("applicationName", applicationName);}}
}
- 有参构造方法中的
SpringApplication application, String[] args参数为固定的 - getOrder返回的值要为1,因为
EventPublishingRunListener中的getOrder返回值为0
在自动装配文件spring.factories指定位置
org.springframework.boot.SpringApplicationRunListener=\com.example.runlistener.CustomEventPublishingRunListener
在log4j2的xml文件中进行取值
<!--输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT"><!-- 输出日志的格式 --><PatternLayout pattern="${sys:applicationName} %d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n"/>
</Console>
输出结果
service-case 2023-09-18 14:57:25 INFO ServerCaseApplication:55 - Starting ServerCaseApplication on lukuan with PID 20004 (D:\idea_work_my\gitee\cook-frame\server\server-case\target\classes started by lukuan in D:\idea_work_my\gitee\cook-frame)
service-case 2023-09-18 14:57:25 INFO ServerCaseApplication:648 - No active profile set, falling back to default profiles: default
service-case 2023-09-18 14:57:25 INFO TomcatWebServer:108 - Tomcat initialized with port(s): 7081 (http)
service-case 2023-09-18 14:57:25 INFO Http11NioProtocol:173 - Initializing ProtocolHandler ["http-nio-7081"]
service-case 2023-09-18 14:57:25 INFO StandardService:173 - Starting service [Tomcat]
service-case 2023-09-18 14:57:25 INFO StandardEngine:173 - Starting Servlet engine: [Apache Tomcat/9.0.46]
service-case 2023-09-18 14:57:25 INFO [/]:173 - Initializing Spring embedded WebApplicationContext
service-case 2023-09-18 14:57:25 INFO ServletWebServerApplicationContext:285 - Root WebApplicationContext: initialization completed in 716 ms_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\ / | 3.5.1
service-case 2023-09-18 14:57:26 INFO ThreadPoolTaskExecutor:181 - Initializing ExecutorService 'applicationTaskExecutor'
service-case 2023-09-18 14:57:26 INFO PropertySourcedRequestMappingHandlerMapping:69 - Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]
service-case 2023-09-18 14:57:26 INFO Http11NioProtocol:173 - Starting ProtocolHandler ["http-nio-7081"]
service-case 2023-09-18 14:57:26 INFO TomcatWebServer:220 - Tomcat started on port(s): 7081 (http) with context path ''
....
可以看到实现了我们想要的功能
相关文章:
log4j2或者logback配置模版实现灵活输出服务名
介绍 在我们使用log4j2或者logback打印日志时,输出的内容中通常是一定要加上服务名的。以log4j2为例: <!--输出控制台的配置--> <Console name"Console" target"SYSTEM_OUT"><!-- 输出日志的格式 --><Patter…...
使用HTTP爬虫ip中的常见误区与解决方法
在如今的互联网时代,为了保障个人隐私和实现匿名浏览,许多人选择使用HTTP爬虫ip。然而,由于缺乏了解和使用经验,常常会出现一些误区。本文将为大家介绍使用HTTP爬虫ip过程中常见的误区,并提供相应的解决方法࿰…...
MySQL学习笔记3
MySQL的源码编译安装: 1、参考MySQL的源码安装官方文档: 2、源码安装定制选项: 3、源码安装三部曲:配置、编译、安装。 4、软件安装包: mysql-boost-5.7.43.tar.gz 5、安装需求: 安装需求具体配置安装目…...
快速掌握ES6
什么是ES6 ES6(ECMAScript 6),也被称为ES2015,是JavaScript的第六个版本,于2015年发布。ES6引入了许多新的语法和功能,旨在提高JavaScript的开发效率和代码质量。 ES6的一些主要特性和改进包括࿱…...
电池厂提供excel电池曲线zcv到mtk电池曲线zcv转换
#encoding:utf8 #电池厂提供excel电池曲线zcv到mtk电池曲线zcv转换 import pandas as pd import openpyxl import math # 读取Excel文件 df pd.read_excel("a55-zcv.xlsx") for j in range(0,10): if(j<3): offset0 #T0~T2 if(j3): offset…...
重写和重载、抽象类和接口
文章目录 前言一、重载与重写1.重载(Overload)(1)条件(2)举例 2.重写(Override)(1)规则(2)举例 3.重载和重写区别 二、抽象类与接口1.抽象类&…...
Untiy UDP局域网 异步发送图片
同步画面有问题,传图片吧 using System.Text; using System.Net.Sockets; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using System.Net; using System; using System.Threading.Tasks; using Sy…...
移动端H5封装一个 ScrollList 横向滚动列表组件,实现向左滑动
效果: 1.封装组件: <template><div class"scroll-list"><divclass"scroll-list-content":style"{ background, color, fontSize: size }"ref"scrollListContent"><div class"scroll…...
Docker一键安装和基本配置
一键安装脚本 注:该脚本需要root权限 curl -sSL https://get.docker.com/ | sh非root组用户赋权 sudo groupadd docker # 若使用一键安装脚本会自动创建这个组,提示已存在 sudo gpasswd -a ${USER} docker # 将当前用户添加到docker组,也…...
MVC设计思想理解和ASP.NET MVC理解
三层模式 三层模式包括:UI层,业务逻辑层,数据访问层,模型层 MVC设计思想和ASP.NET MVC理解 MVC设计思想: MVC的思想就是把我们的程序分为三个核心的模块,这三个模块的详细介绍如下: 模型(Model) :负责封装与引用程序的业务逻辑相关的数据以及对数据的处理方法。模型层有对…...
大模型应用选择对比
大模型应用选择对比 1、知识库对比:dify、fastgpt、langchatchat 2、agent构建器选择:flowise、langflow、bisheng 3、召回率提升方案...
c++STL概述
目录 STL基本概念 STL六大组件 STL的优点 STL三大组件 容器 算法 迭代器 普通的迭代器访问vector容器元素 算法for_each实现循环 迭代器指向的元素类型是自定义数据类型 迭代器指向容器 常用容器 string容器 string的基本概念 string容器的操作 string的构造函…...
利用容器技术优化DevOps流程
利用容器技术优化DevOps流程 随着云计算的快速发展,容器技术也日益流行。容器技术可以打包和分发应用程序,并实现快速部署和扩展。在DevOps流程中,容器技术可以大大优化开发、测试、部署和运维各个环节。本文将介绍如何利用容器技术优化DevO…...
91 # 实现 express 的优化处理
上一节实现 express 的请求处理,这一节来进行实现 express 的优化处理 让 layer 提供 match 方法去匹配 pathname,方便拓展让 layer 提供 handle_request 方法,方便拓展利用第三方库 methods 批量生成方法性能优化问题 进行路由懒加载&#…...
arcgis拓扑检查实现多个矢量数据之间消除重叠区域
目录 环境介绍: 操作任务: 步骤: 1、数据库和文件结构准备 2、建立拓扑规则 3、一直下一页默认参数后,进行拓扑检查 4、打开TP_CK_Topology,会自动带出拓扑要素,红色区域为拓扑错误的地方࿱…...
基于Vue+ELement搭建登陆注册页面实现后端交互
🎉🎉欢迎来到我的CSDN主页!🎉🎉 🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚 🌟推荐给大家我的专栏《ELement》。🎯🎯 …...
JS获取经纬度, 并根据经纬度得到城市信息
在JavaScript中,获取经纬度通常需要使用定位服务,比如HTML5的Geolocation API。然而拿到坐标后,将经纬度转换为城市信息,则需要使用逆地理编码服务接口,比如百度或者高德的 API, 但是他们收费都很高, 我们可以使用一些…...
mac m1 docker安装nacos
文章目录 引言I m1安装docker1.1 Docker 下载1.2 终端Docker相关命令II docker安装nacos2.1 安装nacos2.2 镜像启动see alsoMac 查看进程端口引言 使用docker方式安装是最方便的 I m1安装docker 1.1 Docker 下载 https://docs.docker.com/docker-for-mac/apple-silicon/点击…...
位段 联合体 枚举
Hello好久不见,今天分享的是接上次结构体没有分享完的内容,这次我们讲讲位段 枚举和联合体的概念以及他们的用法。 2.1 什么是位段 位段的声明和结构是类似的,有两个不同: 1.位段的成员必须是 int、unsigned int 或signed int 。 …...
PHP循环获取Excel表头字母A-Z,当超过时输出AA,AB,AC,AD······
PHP循环获取Excel表头字母A-Z,当超过时输出AA,AB,AC,AD PHP循环生成Excel的列字母表 $count_num 26 * 27; $letter A; $arr []; while($count_num--){$arr[] $letter;$letter; }结果如下: 转为JSON更为直观: ["A","B&…...
20 分钟教你零基础部署 OpenClaw 到 Windows 电脑
1. OpenClaw 是什么? OpenClaw 是一款本地运行的 AI 自动化工具,你可以把它理解成一个 “能听懂自然语言的电脑助手”。 它不需要依赖云端服务,所有数据都存在你自己的电脑里,你只需要用中文 / 英文说一句话,它就能帮…...
如何通过自动化工具高效获取阴阳师游戏资源?完整实践指南
如何通过自动化工具高效获取阴阳师游戏资源?完整实践指南 【免费下载链接】OnmyojiAutoScript Onmyoji Auto Script | 阴阳师脚本 项目地址: https://gitcode.com/gh_mirrors/on/OnmyojiAutoScript 阴阳师自动化工具是一款功能强大的智能辅助应用,…...
ABAP - MEMORY ID 的跨程序数据共享实践
1. ABAP内存ID:跨程序数据共享的秘密武器 在SAP开发中,经常会遇到这样的场景:程序A需要某些数据,但获取这些数据的逻辑写在程序B里。传统做法可能是通过接口、数据库表或者文件来中转数据,但这些方法要么太麻烦&#x…...
滞回比较器设计实战:从理论到参数优化
1. 滞回比较器基础:从门铃到航天器的抗噪神器 第一次接触滞回比较器是在大学电子设计课上,当时教授用一个生动的例子开场:"想象你家的门铃——如果它对任何风吹草动都响个不停,你会疯掉;但如果连用力敲门都没反应…...
vLLM-v0.17.1实操手册:vLLM服务升级策略与滚动更新最佳实践
vLLM-v0.17.1实操手册:vLLM服务升级策略与滚动更新最佳实践 1. vLLM框架概述 vLLM是一个专为大型语言模型(LLM)设计的高性能推理和服务库,最新发布的v0.17.1版本带来了多项性能优化和功能增强。这个开源项目最初由加州大学伯克利分校的研究团队开发&am…...
A-59F 多功能语音处理模组:覆盖全场景人群,让每一次语音都清晰无噪
在门禁对讲、会议扩音、车载通话、导游喊话、监护设备、智能工牌等各类语音设备中,啸叫刺耳、环境嘈杂、回音不断、拾音模糊、通话断续是所有人共同的痛点。一款真正解决问题的核心硬件 ——A-59F 多功能语音处理模组,它集成扩音防啸叫、AI ENC 降噪、AE…...
NaViL-9B图文问答入门:Web界面支持拖拽上传+历史记录回溯功能
NaViL-9B图文问答入门:Web界面支持拖拽上传历史记录回溯功能 1. 平台介绍 NaViL-9B是一款原生多模态大语言模型,由专业研究机构开发。它不仅能像传统语言模型一样处理纯文本问答,还具备强大的图片理解能力。这意味着你可以上传一张图片&…...
大语言模型训练中的显存占用与优化方法简述
在进行大语言模型(LLM)的微调或预训练时,显存(VRAM)不足通常是首要面临的问题。为了在有限的硬件资源下完成训练,了解显存的具体去向以及相应的优化技术是比较基础的工作。 从模型训练的流程来看ÿ…...
UG/NX二次开发必备:C#和C++项目DLL自动签名与拷贝全攻略(附避坑指南)
UG/NX二次开发实战:C#与C项目DLL签名与部署全流程解析 在工业设计软件领域,Siemens NX(原Unigraphics)的二次开发能力一直是工程师扩展功能、提升效率的重要途径。而DLL文件的数字签名环节,则是确保开发成果能在正版NX…...
OpenClaw定时任务管理:ollama-QwQ-32B实现智能提醒系统
OpenClaw定时任务管理:ollama-QwQ-32B实现智能提醒系统 1. 为什么需要智能提醒系统 作为一个长期被各种截止日期折磨的技术从业者,我一直在寻找一个能够真正理解我需求的提醒工具。传统的日历应用虽然能设置固定时间的提醒,但缺乏灵活性——…...
