Use Spring boot application properties in log4j2.xml - spring-boot

I am working on a web application based on spring boot and want to use log4j2 as the logger implementation.
Everything works fine with the logging configuration defined in a log4j2-spring.xml file.
What is not working: I want to use property placeholders in the log4j2-spring.xml file that should be resolved from properties defined in the application.yml file used for configuring spring boot.
Is this possible? If yes, how?

Direct substitution of properties in log4j2-spring.xml via property placeholder is not possible as the log4j2-spring.xml is outside the ambit of Spring, and used purely for configuration purpose.
However, you can leverage the Log4j2 out-of-box feature of property substitution as outlined here.
Step 1 -
Specify the property name and its variable in log4j2-spring.xml as below
<Configuration status="warn">
<Properties>
<Property name="someProp">${bundle:test:someKey}</Property>
</Properties>
<!--other configs -->
</Configuration>
Step 2 - Use the above defined property in the log configuration e.g. suffix to log file name
<Appenders>
<File name="file" fileName="/path/to/logs/app-${someProp}.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %-40c{1.} - %m%n"/>
</File>
</Appenders>
Step 3 - Create a bundle (viz. properties file) to hold the properties value e.g. test.properties
# properties for log4j2
someKey=someValue
someKey1=someValue1
In your case this file will contain the values in yaml which you seek to use in log4j2 configuration. In case those properties are used in application as well, they will be duplicated in yaml and the bundle (i.e. properties file) which should be acceptable compromise given spring can not inject them in log4j2 configuration.
Let know in comments in case of any more information is required.

I've faced similiar problem with injecting Spring Boot YAML properties into log4j xml configuration, and I found a solution for Spring Boot 1.5.X (and probably 2.0, I didn't test it) which is a little bit hacky and operates on system properties lookup but it certainly works.
Let say you have profile "dev" in your application and some property to inject, then your application-dev.yml looks like this:
property:
toInject: someValue
In your xml configuration log4j2-spring-dev.xml you put something like this:
<Properties>
<property name="someProp">${sys:property.toInject}</property>
</Properties>
Now you have to somehow transfer this spring property to system property. You have to do that after application environment will be prepared and before logging system will initialize. In Spring Boot there is a listener LoggingApplicationListener, which initialize whole logging system and it's triggered by event ApplicationEnvironmentPreparedEvent, so let's create listener with order with higher precedence than LoggingApplicationListener:
public class LoggingListener implements ApplicationListener, Ordered {
#Override
public int getOrder() {
return LoggingApplicationListener.DEFAULT_ORDER - 1;
}
#Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
ConfigurableEnvironment environment = ((ApplicationEnvironmentPreparedEvent) event).getEnvironment();
List<String> activeProfiles = Arrays.asList(environment.getActiveProfiles());
if (!activeProfiles.contains("dev")) {
return;
}
String someProp = environment.getProperty("property.toInject")
validateProperty(someProp);
System.setProperty("property.toInject", someProp);
}
}
Now register this listener in your application:
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.addListeners(new LoggingListener());
application.run(args);
}
And that's it. Your Spring Boot properties should be "injected" in your log4j2 configuration file. This solution works with classpath properties and --spring.config.location properties. Note, it would not work with with some external configuration system like Spring Cloud Config.
Hope it helps

If you use mvn, you could use the mvn resource plugin. This will let you achieve your goal in build time.
Link: https://maven.apache.org/plugins/maven-resources-plugin/examples/filter.html

Related

log4j2 the logger get by Slf4j LoggerFactory.getILoggerFactory can only read default log4j.xml without profile

Use log4j2 in SpringBoot project with test profile,
org.slf4j.LoggerFactory.getLogger("xxx") can get test profile LoggerContext and read configuration from log4j2-test.xml.
But
org.slf4j.LoggerFactory.getILoggerFactory().getLogger("xxx") can only get current LoggerContext and read configuration from log4j2.xml.
LoggerFactory.getLogger("xxx"); // log4j2-test.xml
LoggerFactory.getILoggerFactory().getLogger("xxx"); // log4j2.xml
Is it a bug of log4j2?
I tested LoggerFactory.getILoggerFactory().getLogger("xxx") with Logback, Logback can choose log4j2-test.xml properly.
SpringBoot version: 2.4.5
Add some background to help more people: ParSeq framework prints logs by LoggerFactory.getILoggerFactory().getLogger("xxx").
No, this would not be a bug in Log4j. Log4j knows nothing about Spring profiles and does not incorporate them in its logic of locating a configuration file.
The methods in LoggerFactory that you are calling are static. That means they are implemented by SLF4J. The SLF4J source shows that getLogger("XXX") does
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
which is exactly the same as what you are manually doing in your second call, so I cannot see how there could be any difference between them.
I also doubt that Logback can choose log4j2-test.xml properly since that would be an invalid configuration file for Logback.
It should be a bug of log4j2:
JIRA_LOG4J2-3083

Spring boot : load profile specific application.properties from file system

I am trying to load external properties files based on the current active profile
and the properties files I have defined as below :
default -> resources/config/application.properties (for dev)
qa -> c:\external-configuration\config\application-qa.properties
prod -> c:\external-configuration\config\application-prod.properties
how spring can be configured to read all these application*.properties from different sources?
I tried to define a PropertySourcesPlaceholderConfigurer as below but spring could resolve the properties value based on the active profile, I always get the default values defined in application.properties
#Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
properties.setLocation(new FileSystemResource("c:\external-configuration\config\application-qa.properties"),new FileSystemResource("c:\external-configuration\config\application-prod.properties"));
properties.setIgnoreResourceNotFound(false);
return properties;
}
First specify which profile you want to load with spring.profiles.active. Secondly, as it isn't one of the default locations, add spring.config.additional-location to add additional locations to scan. So when you start your line should look like
java -jar <your-jar>.jar --spring.profiles.active=prod --spring.config.additional-location=file:C:/external-configuration/config/
This is also documented in the Spring Boot documentation.
And remove your custom PropertySourcesPlaceholderConfigurer as that isn't needed.
You can also use java annotation for property resource and use server environment (active profile) to identify which properties file to load.
Like, this code snippet here, it will look for property 'envTarget' and in case its not found or null, it will use default 'qa':
#PropertySource({
"classpath:application-${envTarget:qa}.properties"
})

Where does spring boot configure default application.properties

By default Spring Boot will automatically load properties from classpath:/application.properties
I want to know where is this auto configuration source code.
I want to exclude from my app.
IE: #EnableAutoConfiguration(exclude=XXXXAutoconfiguration.class)
The reason is:
Because I cannot override the default application.properties by an external property using #PropertySource
#SpringBootApplication
#ComponentScan(basePackages = {"com.test.green.ws"})
#PropertySource(value = {"classpath:/application.properties", "file:/opt/green-ws/application.properties"})
public class GreenWSApplication {
public static void main(String[] args) {
SpringApplication.run(GreenWSApplication.class, args);
}
}
There are many ways to override property keys without disabling the whole externalized configuration feature; and that's actually the goal.
You can see here the order the properties are considered in. For example, you can add that external properties file in a config folder right next to the packaged JAR, or even configure the file location yourself.
Now if you really want to disable all of that (and the Boot team strongly suggests not to do that), you can register your own EnvironmentPostProcessor (see here) and remove PropertySources from MutablePropertySources, which you can fetch with configurableEnvironment. getPropertySources().
There's no easier way to do that because:
this comes really early in the application init phase, before auto-configurations
this is not something you should do, as it will have many side effects

Set/override Spring / Spring Boot properties at runtime

At the project with Spring Boot we use application.properties but need to configure some of these properties (like port number of logging level) based on an external configuration. We access the configuration via API so it is known only at runtime.
Is there a way to override or set some Spring properties at runtime (for example using a bean) and if yes how can this be achieved?
You could do this with Spring Cloud Config
Just for the purpose of illustration, here's a relatively quick way to see dynamic property overrides at runtime:
First, for your bean to be able to pick up changed properties, you need to annotate it with
#RefreshScope
Add the spring cloud dependency to your spring boot app, eg for gradle
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter', version: '1.1.1.RELEASE'
( NB You also need the spring boot actuator dependency.)
With the app running, you can view your current config at eg
http://localhost:8080/env
eg if you have a property 'my.property' in application.properties, you'll see something like:
"applicationConfig: [classpath:/application.properties]": {
"my.property": "value1",
etc
To change the value, POST my.property=value2 to /env as application/x-www-form-urlencoded
eg
curl -X POST http://localhost:8080 -d my.property=value2
GET /env again and you'll see the new value appears under the "manager" section
To apply the changed properties, do an empty POST to /refresh. Now your bean will have the new value.
Could you use system properties to pass in the variable? If you configure the PropertyPlaceholderConfigurer you can set the precedence of system properties vs file properties.
For example, something like:
#Bean public PropertyPlaceholderConfigurer placeHolderConfigurer() {
PropertyPlaceholderConfigurer props = new PropertyPlaceholderConfigurer()
props.setSystemPropertiesMode( PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE )
props.setLocations(new
PathMatchingResourcePatternResolver().getResources("classpath:/**.properties"));
props
}
The above would load your .properties file, but we set the priority to be system variables first, so if you set a system variable that will override the same variable in the config.
Alternatively, looking at the docs, Spring recommends defining a search order in your Environment:
[PropertyPlaceholderConfigurer is still appropriate for use when]
existing configuration makes use of the "systemPropertiesMode" and/or "systemPropertiesModeName" properties. Users are encouraged to
move away from using these settings, and rather configure property
source search order through the container's Environment; however,
exact preservation of functionality may be maintained by continuing to
use PropertyPlaceholderConfigurer.
Hopefully one of the above should sort out what you need?

Spring Integration - How to create an optional jms:message-driven-channel-adapter?

I'm looking for a way to conditionally set up jms:message-driven-channel-adapter in spring 3.0 & spring integration 2.2.
I would like to have an entry in a property file like: "create.message.driven.channel.adapter=true" for each environment and I would like spring to decide whether to set up the channel or not based solely on the entry from the property file.
Is there a way to accomplish this using only spring xml configuration and a property file?
You can't do it exactly the way you describe. With Spring 3.1, you could do it with Spring Profiles...
<beans>
...
<beans profile="foo">
<jms:message-driven-adapter ... />
</beans>
</beans>
Then run with ... -Dspring.profiles.active=foo.
You could do it with JavaConfig
#Bean
public Object foo() {
// if property set, return an MDA, otherwise a String
}
Or, probably the easiest, so long as you don't explicitly start() the context, you could use
<jms:message-driven-adapter ...
auto-startup="${start.message.driven.channel.adapter}" />
In which case, the bean would be defined, but it just wouldn't be started so it wouldn't even open a JMS connection. You would also need a property placeholder configurer pointed at your properties file.
But, auto-startup only applies to starting on refresh(), an explicit context.start() will still start it.

Resources