diff --git a/.gitignore b/.gitignore
index b425f09..0573d41 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,4 +32,5 @@ build/
.vscode/
### Mac OS ###
-.DS_Store
\ No newline at end of file
+.DS_Store
+/mise.toml
diff --git a/README.md b/README.md
index fe331ee..1bde162 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,17 @@
# Spring Boot Properties Logger
-_Properties Logger_ is a module for Spring Boot 3+ apps which early logs properties
+_Properties Logger_ is a module for Spring Boot 4+ apps which early logs properties
detected by Spring Boot and their values resolved by Spring Boot.
## Requirements
The _properties Logger_ module is designed to work with :
-- Spring Boot 3.3+ (an so java 17+)
+- Spring Boot 4.0+ (an so java 17+)
- Servlet web application
- Reactive web application (not tested)
- Batch application application (not tested)
- CommandLineRunner application (not tested)
-- Spring boot version < 3.3 (not tested)
## Usage
@@ -31,29 +30,30 @@ NB :
The module module _Properties Logger_ logs its message with properties and their values
at the info level : so its **log level must be at least INFO**. DEBUG (or TRACE) give (much) more
- informations.
+ informations : `logging.level.io.github.fbibonne.springaddons.boot = INFO`
## Result
You should see this kind of output in your log (in the console by default in a Spring Boot app) :
```text
-2024-06-25T12:01:00.858+02:00 INFO 42091 --- [ main] io.github.fbibonne.springaddons.boot.propertieslogger.EnvironmentPreparedEventForPropertiesLogging :
+2025-08-25T12:01:00.858+02:00 INFO 42091 --- [ main] io.github.fbibonne.springaddons.boot.propertieslogger.EnvironmentPreparedEventForPropertiesLogging :
================================================================================
Values of properties from sources :
- Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'
====
-springdoc.show-actuator = true
-springdoc.swagger-ui.path = /
-springdoc.pathsToMatch = /**
-springdoc.swagger-ui.oauth.clientId =
-server.forward-headers-strategy = framework
-spring.security.oauth2.resourceserver.jwt.jwk-set-uri =
-spring.datasource.username = user
-spring.datasource.password = ******
-springdoc.swagger-ui.syntax-highlight.activated = false
-management.endpoint.health.show-details = always
-logging.level.root = INFO
+logging.level.root = INFO ### FROM "logging.level.root" from property source "commandLineArgs" ###
+management.endpoint.health.show-details = always ### FROM class path resource [application.properties] - 10:43 ###
+server.forward-headers-strategy = framework ### FROM class path resource [application.properties] - 5:35 ###
+springdoc.pathsToMatch = /** ### FROM class path resource [application.properties] - 3:26 ###
+springdoc.show-actuator = true ### FROM class path resource [application.properties] - 1:27 ###
+springdoc.swagger-ui.oauth.clientId = ### FROM class path resource [application.properties] - 4:39 ###
+springdoc.swagger-ui.path = / ### FROM class path resource [application.properties] - 2:29 ###
+springdoc.swagger-ui.syntax-highlight.activated = false ### FROM class path resource [application.properties] - 9:51 ###
+spring.application.pid = 379695 ### FROM "spring.application.pid" from property source "applicationInfo" ###
+spring.datasource.password = ****** ### FROM System Environment Property "spring.datasource.password" ###
+spring.datasource.username = user ### FROM class path resource [application.properties] - 7:30 ###
+spring.security.oauth2.resourceserver.jwt.jwk-set-uri = ### FROM class path resource [application.properties] - 6:57 ###
================================================================================
. ____ _ __ _ _
@@ -63,7 +63,7 @@ logging.level.root = INFO
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
- :: Spring Boot :: (v3.3.1)
+ :: Spring Boot :: (v4.0.0)
```
The first part of the log message generated by the module (before the separator `====` )
diff --git a/pom.xml b/pom.xml
index b29053c..060a0a1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,18 +7,28 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.5
+ 4.0.2
io.github.fbibonne
boot-properties-logger-starter
- 1.5.0
+ 2.0.0
jar
+
+ 1.0.0
17
17
UTF-8
+ 3.4.0
+ 3.12.0
+ 3.2.8
+ 0.10.0
Spring Boot Properties Logger
@@ -69,6 +79,12 @@
+
+ org.jspecify
+ jspecify
+ ${jspecify.version}
+ compile
+
org.springframework.boot
spring-boot
@@ -107,7 +123,7 @@
org.apache.maven.plugins
maven-source-plugin
- 3.3.1
+ ${maven-source-plugin.version}
attach-sources
@@ -120,7 +136,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.10.1
+ ${maven-javadoc-plugin.version}
attach-javadocs
@@ -133,7 +149,7 @@
org.apache.maven.plugins
maven-gpg-plugin
- 3.2.8
+ ${maven-gpg-plugin.version}
bc
@@ -150,7 +166,7 @@
org.sonatype.central
central-publishing-maven-plugin
- 0.8.0
+ ${central-publishing-maven-plugin.version}
true
central
diff --git a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java
index e959aa0..416f440 100644
--- a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java
+++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java
@@ -1,5 +1,6 @@
package io.github.fbibonne.springaddons.boot.propertieslogger;
+import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.*;
@@ -24,12 +25,12 @@ public record EnvironmentPreparedEventForPropertiesLogging() implements Applicat
private static final Set DEFAULT_PROPS_WITH_HIDDEN_VALUES = Set.of("password", "pwd", "token", "secret", "credential", "pw");
private static final Set DEFAULT_PREFIX_FOR_PROPERTIES = Set.of("debug", "trace", "info", "logging", "spring", "server", "management", "springdoc", "properties");
- private static final Set DEFAULT_SOURCES_IGNORED = Set.of(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
+ public static final Set DEFAULT_SOURCES_IGNORED = Set.of(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
private static final boolean DEFAULT_PROPERTIES_LOGGER_DISABLED = false;
private static final String KEY_FOR_PROPS_WITH_HIDDEN_VALUES = "properties.logger.with-hidden-values";
private static final String KEY_FOR_PREFIX_FOR_PROPERTIES = "properties.logger.prefix-for-properties";
- private static final String KEY_FOR_SOURCES_IGNORED = "properties.logger.sources-ignored";
+ public static final String KEY_FOR_SOURCES_IGNORED = "properties.logger.sources-ignored";
public static final String KEY_FOR_DISABLED = "properties.logger.disabled";
@Override
@@ -61,19 +62,29 @@ private void doLogProperties(CustomAbstractEnvironment abstractEnvironment) {
final AllowedPrefixForProperties allowedPrefixForProperties = new AllowedPrefixForProperties(getPropertyOrDefaultAndTrace(abstractEnvironment, KEY_FOR_PREFIX_FOR_PROPERTIES, Set.class, DEFAULT_PREFIX_FOR_PROPERTIES));
final IgnoredPropertySources ignoredPropertySources = new IgnoredPropertySources(getPropertyOrDefaultAndTrace(abstractEnvironment, KEY_FOR_SOURCES_IGNORED, Set.class, DEFAULT_SOURCES_IGNORED));
- PropertiesLogger propertiesLogger = new PropertiesLogger(propertiesWithHiddenValues, allowedPrefixForProperties, ignoredPropertySources);
- propertiesLogger.doLogProperties(abstractEnvironment);
+ PropertiesLogger propertiesLogger = new PropertiesLogger(propertiesWithHiddenValues, allowedPrefixForProperties, ignoredPropertySources, abstractEnvironment);
+ propertiesLogger.doLogProperties();
}
private T getPropertyOrDefaultAndTrace(PropertyResolver environment, String key, Class clazz, T defaultValue) {
+ T result;
try {
- T result = environment.getProperty(key, clazz, defaultValue);
- log.trace(() -> key + " -> " + result);
- return result;
- } catch (Exception e) {
- log.info(()-> "Error while getting property " + key + " : " + e.getMessage()+System.lineSeparator()+"Will use default value");
- return defaultValue;
+ result = getPropertyOrDefaultAndTraceThrowingException(environment, key, clazz, defaultValue);
+ } catch (RuntimeException e) {
+ logExceptionWhenGettingProperty(key, e);
+ result= defaultValue;
}
+ return result;
+ }
+
+ private static void logExceptionWhenGettingProperty(String key, RuntimeException e) {
+ log.info(()-> "Error while getting property " + key + " : " + e.getMessage()+System.lineSeparator()+"Will use default value");
+ }
+
+ private static T getPropertyOrDefaultAndTraceThrowingException(PropertyResolver environment, String key, Class clazz, T defaultValue) throws RuntimeException{
+ T result = environment.getProperty(key, clazz, defaultValue);
+ log.trace(() -> key + " -> " + result);
+ return result;
}
static final class CustomAbstractEnvironment implements PropertyResolver {
@@ -81,6 +92,7 @@ static final class CustomAbstractEnvironment implements PropertyResolver {
private static final Method getPropertyAsRawString = resolveMethod(AbstractPropertyResolver.class, "getPropertyAsRawString", String.class);
private final ConfigurableEnvironment delegate;
+ @Nullable
private AbstractPropertyResolver propertyResolver;
@@ -90,10 +102,11 @@ private static Method resolveMethod(Class> targetType, String methodName, Clas
return method;
}
- private CustomAbstractEnvironment(ConfigurableEnvironment delegate) {
+ CustomAbstractEnvironment(ConfigurableEnvironment delegate) {
this.delegate = delegate;
}
+ @Nullable
private AbstractPropertyResolver invokeGetPropertyResolver(ConfigurableEnvironment delegate) {
if (delegate instanceof AbstractEnvironment abstractEnvironment) {
var abstractPropertyResolverCondidate = ReflectionUtils.invokeMethod(getPropertyResolver, abstractEnvironment);
@@ -110,7 +123,7 @@ public boolean containsProperty(String key) {
}
@Override
- public String getProperty(String key) {
+ public @Nullable String getProperty(String key) {
return delegate.getProperty(key);
}
@@ -153,6 +166,7 @@ public MutablePropertySources getPropertySources() {
return delegate.getPropertySources();
}
+ @Nullable
public String getPropertySafely(String key) {
try{
return delegate.getProperty(key);
@@ -164,10 +178,12 @@ public String getPropertySafely(String key) {
}
}
+ @Nullable
private String getPropertyAsRawString(String key) {
return invokeGetPropertyAsRawString(key);
}
+ @Nullable
private String invokeGetPropertyAsRawString(String key) {
if (this.propertyResolver == null) {
this.propertyResolver = invokeGetPropertyResolver(this.delegate);
diff --git a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/IgnoredPropertySources.java b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/IgnoredPropertySources.java
index 88ac678..eda03e9 100644
--- a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/IgnoredPropertySources.java
+++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/IgnoredPropertySources.java
@@ -1,10 +1,25 @@
package io.github.fbibonne.springaddons.boot.propertieslogger;
+import org.springframework.core.env.PropertySource;
+
import java.util.Set;
import java.util.stream.Stream;
record IgnoredPropertySources(Set sources) {
- public Stream stream() {
+ private Stream stream() {
return sources.stream();
}
+
+ /**
+ * Return true if the library is configured to ignore the propertySource passed as a parameter.
+ *
+ * The propertySource must be ignored if its name is listed in the configuration property whose key is
+ * {@link EnvironmentPreparedEventForPropertiesLogging#KEY_FOR_SOURCES_IGNORED} (default names are
+ * {@link EnvironmentPreparedEventForPropertiesLogging#DEFAULT_SOURCES_IGNORED}
+ * @param propertySource : propertySource to be ignored or not
+ * @return true if propertySource must be ignored
+ */
+ public boolean isIgnored(PropertySource> propertySource) {
+ return stream().anyMatch(propertySource.getName()::contains);
+ }
}
diff --git a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/OriginFinder.java b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/OriginFinder.java
new file mode 100644
index 0000000..27d7f4f
--- /dev/null
+++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/OriginFinder.java
@@ -0,0 +1,37 @@
+package io.github.fbibonne.springaddons.boot.propertieslogger;
+
+import org.springframework.boot.context.properties.source.ConfigurationProperty;
+import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
+import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
+import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyNameException;
+import org.springframework.core.env.MutablePropertySources;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+public record OriginFinder(List configurationPropertySources) {
+
+ public OriginFinder(MutablePropertySources propertySources) {
+ this(propertySources.stream()
+ .map(ConfigurationPropertySource::from)
+ .filter(Objects::nonNull)
+ .toList());
+ }
+
+ public Optional findOriginFor(String key) {
+ try {
+ final ConfigurationPropertyName configurationPropertyName = ConfigurationPropertyName.of(key);
+ return configurationPropertySources.stream()
+ .map(c -> c.getConfigurationProperty(configurationPropertyName))
+ .filter(Objects::nonNull)
+ .map(ConfigurationProperty::getOrigin)
+ .filter(Objects::nonNull)
+ .map(Object::toString)
+ .map("FROM "::concat)
+ .findFirst();
+ } catch (InvalidConfigurationPropertyNameException e) {
+ return Optional.of("WARNING ! "+e.getMessage() + " : see org.springframework.boot.context.properties.source.ConfigurationPropertyName");
+ }
+ }
+}
diff --git a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLogger.java b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLogger.java
index b213a78..a808727 100644
--- a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLogger.java
+++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLogger.java
@@ -1,23 +1,17 @@
package io.github.fbibonne.springaddons.boot.propertieslogger;
-import org.springframework.boot.ansi.AnsiPropertySource;
-import org.springframework.boot.env.RandomValuePropertySource;
-import org.springframework.boot.origin.OriginLookup;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
-import org.springframework.jndi.JndiPropertySource;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.function.Consumer;
+import java.util.*;
import java.util.function.Predicate;
-import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Class doing the real stuff for logging properties. The collect of properties to log, their value and the logging is done
- * inside the {@link this#doLogProperties(EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment)} method.
+ * inside the {@link this#doLogProperties()} method.
* The state of the class stores {@link PropertySource} found, and a reference to an operator providing the origin of a property
* which is found while scanning propertySources
*/
@@ -30,96 +24,144 @@ class PropertiesLogger {
final PropertiesWithHiddenValues propertiesWithHiddenValues;
final AllowedPrefixForProperties allowedPrefixForProperties;
final IgnoredPropertySources ignoredPropertySources;
- final Set propertySourceNames = new HashSet<>();
- final StringBuilder stringWithPropertiesToDisplay = new StringBuilder();
+ final EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment;
+ final OriginFinder originFinder;
- UnaryOperator originFinder = k->"";
-
- PropertiesLogger(PropertiesWithHiddenValues propertiesWithHiddenValues, AllowedPrefixForProperties allowedPrefixForProperties, IgnoredPropertySources ignoredPropertySources) {
+ PropertiesLogger(PropertiesWithHiddenValues propertiesWithHiddenValues, AllowedPrefixForProperties allowedPrefixForProperties, IgnoredPropertySources ignoredPropertySources, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment) {
this.propertiesWithHiddenValues = propertiesWithHiddenValues;
this.allowedPrefixForProperties = allowedPrefixForProperties;
this.ignoredPropertySources = ignoredPropertySources;
+ this.abstractEnvironment = abstractEnvironment;
+ this.originFinder = new OriginFinder(abstractEnvironment.getPropertySources());
}
/**
* This method :
*
- * - lists all propertySources of the environment and exclude all those which cannot be processed (see method {@link this#isEnumerable(PropertySource)}
- * or are listed to be ignored (see property {@code properties.logger.sources-ignored}
+ * - lists all propertySources of the environment and exclude all those which won't be processed :
+ *
+ * - propertySources which will be processed must satisfy two conditions : be a subtype of {@link EnumerablePropertySource} AND
+ * must not be ignored (see {@link IgnoredPropertySources#isIgnored(PropertySource)}
+ * - debug message is logged if the propertySource is ignored
+ * - warn message is logged if the propertySource is a {@link org.springframework.boot.ansi.AnsiPropertySource} (whose processing is not implmented).
+ * in such case, it will be ignored
+
+ * - If propertySource has an unknown type, a warning is logged
+ *
+ *
* - for each propertySource not excluded, list all property keys then exclude {@code null} keys and non-allowed prefixed ones (see property {@code properties.logger.prefix-for-properties}
* - order distinct keys with alphabetical order (natural order of {@link String}
* - for each key, compute an expression {@code key = value ### FROM value_origin ###} where {@code value} is the value of the key resolved against
- * the environment (or masked if key is listed in property {@code properties.logger.with-hidden-values}) and {@code value_origin} is the
- * propertySource which contains the value to which the key is resolved (if field {@link this#originFinder} can resolve it)
+ * the environment ({@link PropertiesLogger#abstractEnvironment}) or masked if key is listed in property {@code properties.logger.with-hidden-values}.
* - log the list of used propertySources to find keys, the ordered list of properties and their values and origin when available
*
*
- * @param abstractEnvironment : the environment of the application wrapped as an {@link io.github.fbibonne.springaddons.boot.propertieslogger.EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment}
- * to safely resolve properties with unknown placeholders
*/
- public void doLogProperties(final EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment) {
- log.debug(() -> "Start logging properties with prefix " + allowedPrefixForProperties + " for all properties sources except " + ignoredPropertySources + ". Values masked for properties whose keys contain " + propertiesWithHiddenValues);
-
- abstractEnvironment.getPropertySources().stream()
- .mapMulti(this::asEnumerablePropertySourceIfHasToBeProcessed)
- .flatMap(this::toPropertyNames)
- .distinct()
- .filter(this::nonNullKeyWithPrefix)
- .sorted()
- .forEach(key -> resolveValueThenAppendToDisplay(key, abstractEnvironment));
-
- stringWithPropertiesToDisplay
- .append(SEPARATION_LINE)
- .insert(0, System.lineSeparator())
- .insert(0, " ====");
- insertPropretySourceNamesOnePerLine(propertySourceNames, stringWithPropertiesToDisplay);
- stringWithPropertiesToDisplay.insert(0, """
-
- Values of properties from sources :
- """
- )
- .insert(0, SEPARATION_LINE)
- .insert(0, System.lineSeparator());
+ public void doLogProperties() {
+ debugStarting();
+ final StringBuilder stringWithPropertiesToDisplay = new StringBuilder();
+
+ Map propertyNamesBySource = propertyNamesBySourceFromEnvironment();
+ final Set propertySourceNames = propertyNamesBySource.keySet();
+
+ stringWithPropertiesToDisplay.append(toKeyValuesBlock(propertyNamesBySource))
+ .append(System.lineSeparator())
+ .append(SEPARATION_LINE);
+
+ stringWithPropertiesToDisplay.insert(0, headerBlock(propertySourceNames));
log.info(stringWithPropertiesToDisplay::toString);
}
- private void insertPropretySourceNamesOnePerLine(Set propertySourceNames, StringBuilder stringWithPropertiesToDisplay) {
- propertySourceNames.forEach(name -> stringWithPropertiesToDisplay.insert(0, System.lineSeparator()).insert(0, name).insert(0, "- "));
+ private String headerBlock(Set propertySourceNames) {
+ return """
+
+ %1$s
+ Values of properties from sources :
+ %2$s
+ ====
+ """.formatted(SEPARATION_LINE, insertPropretySourceNamesOnePerLine(propertySourceNames));
+ }
+
+ private String toKeyValuesBlock(Map propertyNamesBySource) {
+ return distinctPropertiesNames(propertyNamesBySource)
+ .filter(this::keyWithAllowedPrefix)
+ .sorted()
+ .map(this::toDisplayedLine)
+ .collect(Collectors.joining(System.lineSeparator()));
+ }
+
+ private static Stream distinctPropertiesNames(Map propertyNamesBySource) {
+ return propertyNamesBySource.values().stream()
+ .flatMap(Arrays::stream)
+ .distinct();
+ }
+
+ private Map propertyNamesBySourceFromEnvironment() {
+ Map propertyNamesBySource = new HashMap<>();
+ for (PropertySource> propertySource : this.abstractEnvironment.getPropertySources()) {
+ asEnumerablePropertySourceIfHasToBeProcessed(propertySource)
+ .ifPresent(enumerablePropertySource ->
+ propertyNamesBySource.put(enumerablePropertySource.getName(), enumerablePropertySource.getPropertyNames())
+ );
+ }
+ return propertyNamesBySource;
+ }
+
+ private void debugStarting() {
+ log.debug(() -> "Start logging properties with prefix " + allowedPrefixForProperties + " for all properties sources except "
+ + ignoredPropertySources + ". Values masked for properties whose keys contain " + propertiesWithHiddenValues);
}
- private void asEnumerablePropertySourceIfHasToBeProcessed(PropertySource> propertySource, Consumer> downstream) {
- var mustBeProcessed = isEnumerable(propertySource) && isNotIgnored(propertySource);
- if (mustBeProcessed) {
- propertySourceNames.add(propertySource.getName());
- downstream.accept((EnumerablePropertySource>) propertySource);
+ private String insertPropretySourceNamesOnePerLine(Set propertySourceNames) {
+ return propertySourceNames.stream().sorted().map(name -> "- " + name).collect(Collectors.joining(System.lineSeparator()));
+ }
+
+ private Optional> asEnumerablePropertySourceIfHasToBeProcessed(PropertySource> propertySource) {
+ if (mustBeProcessed(propertySource)) {
+ return Optional.of((EnumerablePropertySource>) propertySource);
}
+ return Optional.empty();
+ }
+
+ private boolean mustBeProcessed(PropertySource> propertySource) {
+ return PropertySourceType.isEnumerable(propertySource) && isNotIgnored(propertySource);
}
private boolean isNotIgnored(PropertySource> propertySource) {
- if (ignoredPropertySources.stream().anyMatch(propertySource.getName()::contains)) {
- log.trace(() -> propertySource + " is listed to be ignored");
+ if (ignoredPropertySources.isIgnored(propertySource)) {
+ traceIgnored(propertySource);
return false;
}
return true;
}
- private void resolveValueThenAppendToDisplay(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environement) {
- stringWithPropertiesToDisplay.append(key).append(" = ")
- .append(resolveValueThenMaskItIfSecret(key, environement))
- .append(originFinder.apply(key))
- .append(System.lineSeparator());
+ private static void traceIgnored(PropertySource> propertySource) {
+ log.trace(() -> propertySource + " is listed to be ignored");
+ }
+
+ private String toDisplayedLine(String key) {
+ return key + " = " + resolveValueThenMaskItIfSecret(key, this.abstractEnvironment)
+ + this.originFinder.findOriginFor(key).map(PropertiesLogger::originAsLine).orElse("");
}
- private String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environment) {
- if (propertiesWithHiddenValues.stream().anyMatch(isValueContainedIgnoringCaseIn(key))) {
+ private static String originAsLine(String origin) {
+ return " ### " + origin + " ###";
+ }
+
+ private @Nullable String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environment) {
+ if (mustBeMasked(key)) {
return MASK;
}
return environment.getPropertySafely(key);
}
+ private boolean mustBeMasked(String key) {
+ return propertiesWithHiddenValues.stream().anyMatch(isValueContainedIgnoringCaseIn(key));
+ }
+
/**
* Implementation of a String#containsIgnoreCase based the algorithm of String#contains
* but using String#equalsIgnoreCase to test equality
@@ -130,9 +172,6 @@ private String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEve
*/
Predicate super String> isValueContainedIgnoringCaseIn(String container) {
return value -> {
- if (value==null){
- return false;
- }
if (value.isEmpty()){
return true;
}
@@ -146,69 +185,21 @@ Predicate super String> isValueContainedIgnoringCaseIn(String container) {
};
}
- private Stream toPropertyNames(EnumerablePropertySource> propertySource) {
- log.trace(() -> "Flat properties for " + propertySource.getName());
- return Arrays.stream(propertySource.getPropertyNames());
- }
-
- /**
- * Method which filter propertySource which can be processed to find properties to log.
- * propertySource which can be processed are a subtype of {@link EnumerablePropertySource} : if not method log a
- * debug message or a warning message for cases not implemented yet (AnsiPropertySource and JndiPropertySource). If
- * propertySource is of type {@link org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource},
- * it will be referenced to resolve origin of property values later (see {@link this#originFinder}. If propertySource
- * has an unknown type, a warning is logged.
- * @param propertySource : instance of {@link PropertySource} which is checked for being processed or not.
- * @return true if the propertySource can be processed.
- */
- private boolean isEnumerable(PropertySource> propertySource) {
- if (propertySource instanceof AnsiPropertySource ansiPropertySource) {
- log.warn(()->"Processing of AnsiPropertySource "+ ansiPropertySource+" not yet implemented : properties exclusively from this property source will be ignored");
- return false;
- }
- if (propertySource instanceof PropertySource.StubPropertySource) {
- log.debug(() -> propertySource + " is a stub property source : it does not contain properties : will be ignored");
- return false;
- }
- if (propertySource instanceof RandomValuePropertySource) {
- log.debug(()-> propertySource + " is a RandomValuePropertySource : will be ignored");
- return false;
- }
- if (propertySource instanceof JndiPropertySource){
- log.warn(()-> propertySource + " is a JndiPropertySource : it is not enumerable : will be ignored");
- return false;
- }
- if (propertySource instanceof EnumerablePropertySource>){
- log.trace(() -> propertySource + " is a EnumerablePropertySource : is a candidate to find keys");
- return true;
- }
- if (propertySource instanceof OriginLookup>) {
- if ("org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource".equals(propertySource.getClass().getCanonicalName())){
- log.debug(()-> propertySource + " will be used as the originFinder but will be ignored as a property source");
- final OriginLookup originLookup = (OriginLookup) propertySource;
- this.originFinder=key->{
- var origin=originLookup.getOrigin(key);
- return origin==null?"":" ### FROM "+ origin+" ###";
- };
- }else{
- log.debug(()-> propertySource + " will be ignored as a property source and also as OriginLookup");
- }
- return false;
+ private boolean keyWithAllowedPrefix(String key) {
+ log.trace(() -> "Check if property " + key + " can be displayed");
+ boolean keyWithAllowedPrefix = isKeyWithAllowedPrefix(key);
+ if (!keyWithAllowedPrefix) {
+ debugNotAllowedPrefix(key);
}
- log.warn(()-> propertySource + " is unknown : will be ignored");
- return false;
+ return keyWithAllowedPrefix;
}
- private boolean nonNullKeyWithPrefix(String key) {
- log.trace(() -> "Check if property " + key + " can be displayed");
- if (key == null) {
- return false;
- }
- if (allowedPrefixForProperties.anyMatch(key::startsWith)) {
- return true;
- }
+ private static void debugNotAllowedPrefix(String key) {
log.debug(() -> key + " doesn't start with a logable prefix");
- return false;
+ }
+
+ private boolean isKeyWithAllowedPrefix(String key) {
+ return allowedPrefixForProperties.anyMatch(key::startsWith);
}
}
diff --git a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java
new file mode 100644
index 0000000..9d8e001
--- /dev/null
+++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java
@@ -0,0 +1,92 @@
+package io.github.fbibonne.springaddons.boot.propertieslogger;
+
+import org.springframework.boot.ansi.AnsiPropertySource;
+import org.springframework.boot.env.RandomValuePropertySource;
+import org.springframework.boot.origin.OriginLookup;
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.jndi.JndiPropertySource;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public enum PropertySourceType {
+ ANSI_PROPERTY_SOURCE,
+ JDNI_PROPERTY_SOURCE_TYPE,
+ ENUMERABLE_PROPERTY_SOURCE,
+ OTHERS, UNKNOWN;
+
+ private static final LocalLogger log = new LocalLogger(PropertySourceType.class);
+
+ public static boolean isEnumerable(PropertySource> propertySource) {
+ PropertySourceType propertySourceType = of(propertySource);
+ propertySourceType.log(propertySource);
+ return propertySourceType.isEnumerable();
+ }
+
+ private static PropertySourceType of(PropertySource> propertySource) {
+ return isEnumerableInstance(propertySource) ? ENUMERABLE_PROPERTY_SOURCE : ofNonEnumerablePropertySource(propertySource);
+ }
+
+ private static PropertySourceType ofNonEnumerablePropertySource(PropertySource> propertySource) {
+ PropertySourceType propertySourceType;
+ if (isAnsiPropertySource(propertySource)){
+ propertySourceType = ANSI_PROPERTY_SOURCE;
+ } else if (propertySource instanceof JndiPropertySource) {
+ propertySourceType = JDNI_PROPERTY_SOURCE_TYPE;
+ }else {
+ propertySourceType = ofOtherPropertySource(propertySource);
+ }
+ return propertySourceType;
+ }
+
+ private static PropertySourceType ofOtherPropertySource(PropertySource> propertySource) {
+ PropertySourceType propertySourceType;
+ if (isOfOtherInstance(propertySource)) {
+ propertySourceType = OTHERS;
+ } else {
+ propertySourceType = UNKNOWN;
+ }
+ return propertySourceType;
+ }
+
+ private static boolean isOfOtherInstance(PropertySource> propertySource) {
+ return propertySource instanceof PropertySource.StubPropertySource
+ || propertySource instanceof RandomValuePropertySource
+ || propertySource instanceof OriginLookup>;
+ }
+
+ private static boolean isAnsiPropertySource(PropertySource> propertySource) {
+ return propertySource instanceof AnsiPropertySource;
+ }
+
+ private static boolean isEnumerableInstance(PropertySource> propertySource) {
+ return propertySource instanceof EnumerablePropertySource>;
+ }
+
+ private void log(PropertySource> propertySource) {
+ withLogLevel().accept(() -> logMessage(propertySource));
+ }
+
+ private String logMessage(PropertySource> propertySource) {
+ return switch (this) {
+ case ANSI_PROPERTY_SOURCE ->
+ "Processing of AnsiPropertySource " + propertySource + " not yet implemented : properties exclusively from this property source will be ignored";
+ case OTHERS, JDNI_PROPERTY_SOURCE_TYPE, UNKNOWN -> propertySource + " is not enumerable : will be ignored";
+ case ENUMERABLE_PROPERTY_SOURCE ->
+ propertySource + " is a EnumerablePropertySource : is a candidate to find keys";
+ };
+ }
+
+ private Consumer> withLogLevel() {
+ return switch (this) {
+ case ANSI_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, UNKNOWN -> log::warn;
+ case OTHERS -> log::debug;
+ case ENUMERABLE_PROPERTY_SOURCE -> log::trace;
+ };
+ }
+
+ private boolean isEnumerable() {
+ return this == ENUMERABLE_PROPERTY_SOURCE;
+ }
+}
diff --git a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/package-info.java b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/package-info.java
new file mode 100644
index 0000000..2c509e9
--- /dev/null
+++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.github.fbibonne.springaddons.boot.propertieslogger;
+
+import org.jspecify.annotations.NullMarked;
\ No newline at end of file
diff --git a/src/test/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLoggerTest.java b/src/test/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLoggerTest.java
index 668de58..853ecfe 100644
--- a/src/test/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLoggerTest.java
+++ b/src/test/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLoggerTest.java
@@ -2,6 +2,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
+import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
@@ -26,14 +27,15 @@ class PropertiesLoggerTest {
"'stringFirst', 'StrinG', true",
"'stringLast', 'Lasts', false",
"'stringFirst', 'astring', false",
- "'null.is.not.contained', , false",
"'empty.is.contained', '', true",
"'self.is.contained', 'self.is.contained', true",
"'self.is.contained', 'SelF.iS.conTained', true",
"'longer.is.not.contained','longer.is.not.contained+', false"
})
void isValueContainedIgnoringCaseInTest(String container, String value, boolean expected) {
- PropertiesLogger propertiesLogger = new PropertiesLogger(null, null, null);
+ PropertiesLogger propertiesLogger = new PropertiesLogger(null, null, null,
+ new EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment(new MockEnvironment())
+ );
assertThat(propertiesLogger.isValueContainedIgnoringCaseIn(container).test(value)).isEqualTo(expected);
}
}
\ No newline at end of file
diff --git a/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java b/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java
index 71bd93d..5370854 100644
--- a/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java
+++ b/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java
@@ -28,24 +28,24 @@ class WebAppIntegrationTest {
@Test
@DisplayName("Properties should be correctly displayed and sorted when starting in Web context")
void contextLoads(CapturedOutput output) {
- // ### FROM "spring.application.pid" from property source "applicationInfo" ###
+ //### FROM "spring.application.pid" from property source "applicationInfo" ###
assertThat(output.toString()).contains(("""
main] i.g.f.s.b.p.PropertiesLogger : %n\
================================================================================
Values of properties from sources :
+ - Config resource 'class path resource [additional-file.properties]' via location 'classpath:additional-file.properties'%n\
- Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'%n\
- Config resource 'file [src/test/resources/otherProps/application.properties]' via location 'file:src/test/resources/otherProps/application.properties'%n\
+ - Inlined Test Properties%n\
- applicationInfo%n\
- - Config resource 'class path resource [additional-file.properties]' via location 'classpath:additional-file.properties'%n\
- commandLineArgs%n\
- - Inlined Test Properties%n\
- systemProperties%n\
====%n\
from.system.properties = true ### FROM "from.system.properties" from property source "systemProperties" ###
io.github.fbibonne.secret = ****** ### FROM class path resource [application.properties] - 2:29 ###
io.github.fbibonne.shared = additionalPropsInClasspath ### FROM URL [file:src/test/resources/otherProps/application.properties] - 1:29 ###
io.github.fbibonne.specific.additional-file = additional-file.properties ### FROM class path resource [additional-file.properties] - 2:47 ###
- io.github.fbibonne.specific.additionalPropsInClasspath = additionalPropsInClasspath
+ io.github.fbibonne.specific.additionalPropsInClasspath = additionalPropsInClasspath ### WARNING ! Configuration property name 'io.github.fbibonne.specific.additionalPropsInClasspath' is not valid : see org.springframework.boot.context.properties.source.ConfigurationPropertyName ###
io.github.fbibonne.specific.applicationproperties = application.properties ### FROM class path resource [application.properties] - 4:53 ###
io.github.fbibonne.test = ok ### FROM class path resource [application.properties] - 1:27 ###
logging.level.io.github.fbibonne.springaddons.boot = trace ### FROM "logging.level.io.github.fbibonne.springaddons.boot" from property source "Inlined Test Properties" ###