How to set Spring Boot's logging levels programmatically? - java

I know how to set the log level via environment variables and application properties.
Is there a way to set them programmatically?
I would like to set log levels for particular test classes (which are using SpringJUnit4ClassRunner and #SpringApplicationConfiguration), but not all of them, and without having a separate properties file for every combination.
I tried defining a non-lazy bean to add a new PropertySource to the environment; the method was called but it had no effect.
#Bean
#Lazy(false)
public PropertySource testProperties(ConfigurableEnvironment environment) {
PropertySource properties = new MapPropertySource("testProperties", Collections.singletonMap(
"logging.level.org.springframework.security", "DEBUG"
));
environment.getPropertySources().addFirst(properties);
return properties;
}

You can use #TestPropertySource on your test classes. Unlike the bean-based approach that you tried, #TestPropertySource will add the property source to the environment before the context starts which allows the properties to be picked up when the logging system is initialized.
Something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(YourApplication.class)
#TestPropertySource(properties = "logging.level.org.springframework.security:DEBUG")
public class YourApplicationTests {
// …
}

Credit to Boris the Spider for the hints.
First, add an element <jmxConfigurator /> to the logback.xml.
Then, this code will work:
#Before
public void configureLogging() throws JMException {
ObjectName name = new ObjectName(String.format("ch.qos.logback.classic:Name=default,Type=%s", JMXConfigurator.class.getName()));
JMXConfiguratorMBean logbackMBean = JMX.newMBeanProxy(ManagementFactory.getPlatformMBeanServer(), name, JMXConfiguratorMBean.class);
logbackMBean.setLoggerLevel("org.springframework.security", "DEBUG");
}
Sadly, there doesn't appear to be a better way to build the object name: the Package hierarchy isn't traversable and the string "default" isn't accessible anywhere I can find.

Related

How to decouple System.getenv from static field in java

I've a legacy code as below
#Service
public class CLConf {
static final String CI_ENV = System.getenv("ENV").toUpperCase(); <-- NPE when doing junits
// other config variables
}
This works when we run the code normally in application. but I've issues creating test cases that use class CLConf something like below.
#ExtendWith(SpringExtension.class)
#SpringBootTest
#ContextConfiguration(classes = CLConf.class) <<----------- ISSUE while loading bcz env is not set yet
public class CLServiceTest {
//test cases
}
currently it throws NPE because of .toUpperCase()
Since CI_ENV is directly being set from System.getenv, while writing class for Junits, this class is loaded using #ContextConfiguration. And I'm not sure where to set env variables for Test cases so that it doesn't break while loading CLConf.
How do I configure/do setup for test cases OR how do i decouple System.getenv so that I can supply my own config while testing?
Note: there are many System.getenv in class CLConf, above is just minified class.
You can provide test configurations using the locations or value attribute of the TestPropertySource annotation, directly defining your test properties in a file or selectly override properties like stated in the documentation:
#TestPropertySource is a class-level annotation that is used to
configure the locations() of properties files and inlined properties()
to be added to the Environment's set of PropertySources for an
ApplicationContext for integration tests.
Test property sources have higher precedence than those loaded from
the operating system's environment or Java system properties as well
as property sources added by the application declaratively via
#PropertySource or programmatically (e.g., via an
ApplicationContextInitializer or some other means). Thus, test
property sources can be used to selectively override properties
defined in system and application property sources

Spring boot properties to be loaded at initialization and respect all and control #Aspect based on the value from property file

We are loading properties from an external file using #PropertySources. Now I want to enable/disable #Aspect based on a property. I tried using #ConditionalOnExpression which didn't work. I tried the same by creating a bean of propertyplaceholderconfig. Even in the same case, it didn't work. Then I tried #profile which also didn't work initially.
What I Figured out is that these variables are not initialized at the starting when propertysource or propertyplaceholder bean is used at startup. Some variables are always ignored like (logging.file). But #Value works fine. In order to set these variables, I've to pass them as JVM parameters.
So my questions are:
1. How can I make spring to always read specified property files at startup and respect all of them?
2. Which is the best way to enable/disable #Aspect. Using #profile or #ConditionalOnExpression or something else?
Currently, we are setting logging.file in the main method since this also behaves the same way. But you guys know that it's not the proper way as I may end up adding the properties one by one like this. I want to put all the properties into external files such that spring reads those files and sets its properties.
Our properties structure:
common.properties #This has all common properties
service.properties #Property specific to a service. This will also contain existing property from common.properties which will be overridden.
I understand that I can use profiles. But, we want to keep the properties outside such you need to restart service if you are changing the properties. I also don't want to pass the variables as JVM parameters then I've to pass most of the variables in this way. Passing -Dspring.config.location is also difficult as common.properties and service.properties are used and 'service.properties' filename varies for each service.
sample codes:
Mainclass:
#PropertySources({
#PropertySource(value = "file:${property_path}/common.properties", ignoreResourceNotFound = false),
#PropertySource(value = "file:${property_path}/service1.properties", ignoreResourceNotFound = true) })
public class MainClass {
static String logDirectory = ApplicationContext.getGlobalProperty("logging.file");
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MainClass.class);
Properties properties = new Properties();
properties.put("logging.file", logDirectory);
springApplication.setDefaultProperties(properties);
springApplication.run(args);
}
}
Application Context:
#Configuration
#EnableAutoConfiguration
public class ApplicationContext implements EnvironmentAware {
private static Environment environment;
#Override
public void setEnvironment(Environment environment) {
ApplicationContext.environment = environment;
}
public static String getGlobalProperty(String propertyName) {
return environment.getProperty(propertyName);
}
}
Here you can see any way I've used environment to get property. Is there any way to set the property using the environment such that while spring boot initialization itself the properties are populated?
We can also implement ApplicationContextInitializer and override initialize method to read properties. But how can I make it read 2 property files and override the duplicate property with the latest value? Reference(I'm not sure how to implement my requirements in this way.). Even in this case doesn't sound like you are trying to kill a mosquito with a hammer?
Current working Solution:
#Aspect
#Profile("!production")
#Configuration
public class ControllerAspect {
#pointcut(....)
} //Here also I've to pass spring.profiles.active as JVM params.
//setting the value in common.properties or service1.properties is not working.
I'm a newbie to spring boot so please let me know for additional clarifications.
It seems Spring by default loads some properties at initialization and unless until you specifically write logic to overwrite them (like the one I wrote in MainClass.java) there is no option to override those. Some of these include (logging.file, key used in #ConditionalonExpression).
Some tricks with their own challenges:
Specify the properties in application.properties in your classpath. The variables loaded at the earlier stages are always read from this file. challenge: I've tight coupled all my properties into the jar and in order to change the values I've to recompile and relaunch the Jar.
Use profiles and define application.properties as application-profile.properties. challenge: I've to create so many profiles and still the previous challenge exists.
Pass the property value as JVM parameter as -Dproperty.key=value. challenge:seriously? How many properties am I supposed to send as JVM parameter?
Implement ApplicationContextInitialize and override initialize method.challenge:Overriding Spring's default behaviour is not recommended as well as isn't it an overkill to use this just for reading property file?
Solution:
Use -Dspring.config.location to specify the property files. In this case, always spring reads the properties only from the specified location(s). You can provide multiple property files as well. Refer this for much more details. It seems if you give property locations as Directories spring loads them in reverse order. But if you specify files it follows the order specified.
Note: All these can be combined together. To know about precedence refer this.

Spring System Property Resolver Customization:

I am working on a project that requires me to take environment variables or system properties within a java spring application and modify them before they are injected into beans. The modification step is key for this application to work.
My current approach to this is to set the variables as system environment variables and then use a custom placeholder configurer to access the aforementioned variables and create new properties from them that the beans can access. There is a perfect tutorial for this (except it uses databases).
I have a POC using this approach working fine, but I think there might be an easier solution out there. Perhaps there is an approach to extend the default placeholder configurer to "hook in" custom code to do the necessary modifications for all properties in the entire application. Maybe there is a way to run code immediately after properties are gathered and before data is injected into beans.
Does spring provide an easier way to do this?
Thanks for your time
Simply put, the easiest way to accomplish this is to follow the directions under the section "Manipulating property sources in a web application" in the spring documentation for property management.
In the end, you reference a custom class from a web.xml through a context-param tag:
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.some.something.PropertyResolver</param-value>
</context-param>
This forces spring to load this code before any beans are initialized. Then your class can do something like this:
public class PropertyResolver implements ApplicationContextInitializer<ConfigurableWebApplicationContext>{
#Override
public void initialize(ConfigurableWebApplicationContext ctx) {
Map<String, Object> modifiedValues = new HashMap<>();
MutablePropertySources propertySources = ctx.getEnvironment().getPropertySources();
propertySources.forEach(propertySource -> {
String propertySourceName = propertySource.getName();
if (propertySource instanceof MapPropertySource) {
Arrays.stream(((EnumerablePropertySource) propertySource).getPropertyNames())
.forEach(propName -> {
String propValue = (String) propertySource.getProperty(propName);
// do something
});
}
});
}
}

Spring data multiple datasources from multiple files

I have 2 (or more) different configuration properties file located in the project and I want to load them for different datasources.
I tried to do as following:
#Bean
#ConfigurationProperties(locations = {"#{myconfigroot.getRootFolder()}/datasource1.properties"}
public static DataSource getFirstDatasource() {
return DataSourceBuilder.create().build();
}
But obviously this won't work as the ConfigurationProperties annotation locations property doesn't go through the spEL. (Or may be I write it wrong?) myconfigroot.getRootFolder() is a static method which returns the path to the datasource1.properties.
Please advice. Thanks.
===== Edited =======
I believe this is a common problem when somebody want their application want to load different configuration files. Due to some reasons the file location and name can't be put in the startup script or command line, or, the path can only be determined in runtime, that would require spring to load them during the bean creation.
I tried once using PropertySourcePlaceHolderConfigurer but seems not work either.
Anybody can share some lights?
Latest Spring boot (version 1.3.5) doesn’t support SpEL in this case.
See JavaDoc of annotation #ConfigurationProperties
Note that contrary to {#code #Value}, SpEL expressions are not
evaluated since property values are externalized.
I found a way to customize Spring boot default behavior as follows:
For example, I have database.properties file in somewhere, for some reason I cannot get the location before runtime.
username=mike
password=password
Accordingly, define POJO mapping to properties:
#Component
#ConfigurationProperties(locations = "myConfiguration")// myConfiguration is customized placeholder
public class MyProperties{
String username;
String password;
//Getters, Setters…
}
Then, to extend default StandardEnvironment:
public class MyEnvironment extends StandardEnvironment {
#Override
public String resolvePlaceholders(String location) {
if (location.equals("myConfiguration")) {
//Whatever you can do, SpEL, method call...
//Return database.properties path at runtime in this case
return getRootFolder() + "datasource.properties";
} else {
return super.resolvePlaceholders(text);
}
}
}
Last, apply it in Spring boot main method entry:
#SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
new SpeedRestApplication()
.configure(new SpringApplicationBuilder(SpeedRestApplication.class).environment(new MyEnvironment()))//Replace default StandardEnvironment
.run(args);
}
}
Once Spring boot starts up, the MyProperties bean name and password fields are injected from database.properties. Then you could wire the MyProperties bean to other beans as configuration.
Hope it helps!
I finally got it work by using the following mechanism:
public class DatasourcePostProcessor implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Properties p = new Properties();
p.load(new FileInputStream(new File(getRootFolder() + "/datasource1.properties")));
Map<String, Object> propMap = new HashMap<>();
for (Map.Entry<Object, Object> entry : p.entrySet()) {
propMap.put(entry.getKey().toString(), entry.getValue());
}
MapPropertySource source = new MapPropertySource("datasource1", propMap);
environment.getPropertySources().addLast(source);
}
}
and register the environment post processor into the spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=com.myorg.test.DatasourcePostProcessor
Anyway, hope this helps people and accept the first anwer as it enlight me. Also post the following references from the google search that found during research:
Where I found how to wire the property source with the environment: https://github.com/spring-projects/spring-boot/issues/4595
Where I found how to load the customized properties file: How to configure a custom source to feed Spring Boot's #ConfigurationProperties

Set default properties in a library with spring-boot

I have many different services using spring-boot. I'd like to set up some configuration that is common for each, but allow the services to have their own properties and override them if they want. Example properties include spring.show_banner, management url ones, etc.
How can I do this? If I have the following:
service-common with src/main/resources/application.yml with default properties
service1 with src/main/resources/application.yml with its own properties
I'd like them to be merged with the service1 version taking precedence. Instead, it seems that only the first one found on the classpath is used.
(Alternatively, using #Configuration classes would be even better, but I'm not sure they can be used to define many of the properties)
There are several options available to you, all based on the order in which property sources are considered.
If your common library is responsible for creating the SpringApplication it can use setDefaultProperties. These values can be overridden by your services' application.properties.
Alternatively, your library could use #PropertySource on one of its #Configuration classes to configure, for example, library.properties as a source. Again, these properties could then be overriden in your services' application.properties.
I am not sure what you mean by merging them.
But I'm assuming that in the end, you are describing the situation where you have profile-specific configuration. Because, any properties that are specific to a certain service can be managed/injected using Spring profiles, which will always take precedence over default property files (see documentation).
For example, you can have the file application-service1.properties which would automatically be used when you run your app with the property spring.profiles.active=service1, which can be specified in the command line and other places.
If you don't specify this property, Spring Boot will fallback to the default application.properties file.
And you can of course write the common properties in both files:
application.properties:
service.url=http://localhost:8080/endpoint
service.user=admin
service.password=admin
application-service1.properties:
service.url=http://api.service.com/endpoint
service.user=admin
service.password=aosdnoni3
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
#Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
ApplicationEnvironmentPreparedEvent envEvent = (ApplicationEnvironmentPreparedEvent) event;
ConfigurableEnvironment env = envEvent.getEnvironment();
Properties props = new Properties();
//set props as desired
env.getPropertySources()
.addFirst(new PropertiesPropertySource("customname", props));
}
}
Then in src/main/resources/META-INF/spring.factories, add line:
org.springframework.context.ApplicationListener=mypackage.MyApplicationListener

Categories