From 31e9fe8b4a28b27e3817a231ce0c7814705bdff7 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Wed, 17 Sep 2025 14:33:09 +0200 Subject: [PATCH 01/11] feat (preparing for spring 4) - introduce jspecify --- README.md | 34 +++++++++---------- pom.xml | 8 ++++- .../propertieslogger/PropertiesLogger.java | 7 ++-- .../boot/propertieslogger/package-info.java | 4 +++ 4 files changed, 30 insertions(+), 23 deletions(-) create mode 100644 src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/package-info.java 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..8f0a5b1 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-dependencies - 3.5.5 + 4.0.0-M2 io.github.fbibonne @@ -16,6 +16,7 @@ jar + 1.0.0 17 17 UTF-8 @@ -69,6 +70,11 @@ + + org.jspecify + jspecify + ${jspecify.version} + org.springframework.boot spring-boot 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..711e239 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 @@ -65,7 +65,7 @@ public void doLogProperties(final EnvironmentPreparedEventForPropertiesLogging.C .mapMulti(this::asEnumerablePropertySourceIfHasToBeProcessed) .flatMap(this::toPropertyNames) .distinct() - .filter(this::nonNullKeyWithPrefix) + .filter(this::keyWithAllowedPrefix) .sorted() .forEach(key -> resolveValueThenAppendToDisplay(key, abstractEnvironment)); @@ -199,11 +199,8 @@ private boolean isEnumerable(PropertySource propertySource) { return false; } - private boolean nonNullKeyWithPrefix(String key) { + private boolean keyWithAllowedPrefix(String key) { log.trace(() -> "Check if property " + key + " can be displayed"); - if (key == null) { - return false; - } if (allowedPrefixForProperties.anyMatch(key::startsWith)) { return true; } 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 From deb93b07374b464de0032cc01b8c9c82b9dc9908 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Mon, 8 Dec 2025 10:11:11 +0100 Subject: [PATCH 02/11] pom (spring boot 4 : upgrade) --- pom.xml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 8f0a5b1..c3d3df5 100644 --- a/pom.xml +++ b/pom.xml @@ -7,12 +7,12 @@ org.springframework.boot spring-boot-dependencies - 4.0.0-M2 + 4.0.0 io.github.fbibonne boot-properties-logger-starter - 1.5.0 + 1.6.0-SNAPSHOT jar @@ -20,6 +20,10 @@ 17 17 UTF-8 + 3.4.0 + 3.12.0 + 3.2.8 + 0.9.0 Spring Boot Properties Logger @@ -74,6 +78,7 @@ org.jspecify jspecify ${jspecify.version} + compile org.springframework.boot @@ -113,7 +118,7 @@ org.apache.maven.plugins maven-source-plugin - 3.3.1 + ${maven-source-plugin.version} attach-sources @@ -126,7 +131,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.10.1 + ${maven-javadoc-plugin.version} attach-javadocs @@ -139,7 +144,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.8 + ${maven-gpg-plugin.version} bc @@ -156,7 +161,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.8.0 + ${central-publishing-maven-plugin.version} true central From e7e230304ed48b782b780600efac1384a77fbacc Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Mon, 8 Dec 2025 10:12:37 +0100 Subject: [PATCH 03/11] refactor (clean code : reduce complexity) --- ...mentPreparedEventForPropertiesLogging.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) 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..43bcfc5 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 @@ -66,14 +66,24 @@ private void doLogProperties(CustomAbstractEnvironment abstractEnvironment) { } 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 { From e9b3e8b46cf0481d1050344a4bc802a8deab4d07 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Mon, 8 Dec 2025 10:13:05 +0100 Subject: [PATCH 04/11] refactor (spring boot 4 : jspecify) WIP --- .../EnvironmentPreparedEventForPropertiesLogging.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 43bcfc5..21509c1 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.*; @@ -91,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; @@ -104,6 +106,7 @@ private CustomAbstractEnvironment(ConfigurableEnvironment delegate) { this.delegate = delegate; } + @Nullable private AbstractPropertyResolver invokeGetPropertyResolver(ConfigurableEnvironment delegate) { if (delegate instanceof AbstractEnvironment abstractEnvironment) { var abstractPropertyResolverCondidate = ReflectionUtils.invokeMethod(getPropertyResolver, abstractEnvironment); @@ -120,7 +123,7 @@ public boolean containsProperty(String key) { } @Override - public String getProperty(String key) { + public @Nullable String getProperty(String key) { return delegate.getProperty(key); } @@ -163,6 +166,7 @@ public MutablePropertySources getPropertySources() { return delegate.getPropertySources(); } + @Nullable public String getPropertySafely(String key) { try{ return delegate.getProperty(key); @@ -174,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); From dd88c9770887495108162edf5ecbc065f28e48a6 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Mon, 5 Jan 2026 12:25:42 +0100 Subject: [PATCH 05/11] refactor (spring boot 4 : jspecify) WIP --- .../propertieslogger/PropertiesLogger.java | 43 ++-------- .../propertieslogger/PropertySourceType.java | 80 +++++++++++++++++++ 2 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java 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 711e239..ccbb281 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,5 +1,6 @@ package io.github.fbibonne.springaddons.boot.propertieslogger; +import org.jspecify.annotations.Nullable; import org.springframework.boot.ansi.AnsiPropertySource; import org.springframework.boot.env.RandomValuePropertySource; import org.springframework.boot.origin.OriginLookup; @@ -91,7 +92,8 @@ private void insertPropretySourceNamesOnePerLine(Set propertySourceNames } private void asEnumerablePropertySourceIfHasToBeProcessed(PropertySource propertySource, Consumer> downstream) { - var mustBeProcessed = isEnumerable(propertySource) && isNotIgnored(propertySource); + PropertySourceType propertySourceType = PropertySourceType.of(propertySource); + var mustBeProcessed = propertySourceType.isEnumerable() && isNotIgnored(propertySource); if (mustBeProcessed) { propertySourceNames.add(propertySource.getName()); downstream.accept((EnumerablePropertySource) propertySource); @@ -113,7 +115,7 @@ private void resolveValueThenAppendToDisplay(String key, EnvironmentPreparedEven .append(System.lineSeparator()); } - private String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environment) { + private @Nullable String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environment) { if (propertiesWithHiddenValues.stream().anyMatch(isValueContainedIgnoringCaseIn(key))) { return MASK; } @@ -162,41 +164,8 @@ private Stream toPropertyNames(EnumerablePropertySource propertySourc * @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; - } - log.warn(()-> propertySource + " is unknown : will be ignored"); - return false; + + } private boolean keyWithAllowedPrefix(String key) { 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..fe48d93 --- /dev/null +++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java @@ -0,0 +1,80 @@ +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; + +public enum PropertySourceType { + ANSI_PROPERTY_SOURCE, + STUB_PROPERTY_SOURCE, RANDOM_VALUE_PROPRERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, ENUMERABLE_PROPERTY_SOURCE, + ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, UNKNOWN; + + private static final LocalLogger log = new LocalLogger(PropertySourceType.class); + + public static PropertySourceType of(PropertySource propertySource) { + PropertySourceType propertySourceType; + if (propertySource instanceof AnsiPropertySource) { + propertySourceType = ANSI_PROPERTY_SOURCE; + } else if (propertySource instanceof PropertySource.StubPropertySource) { + propertySourceType = STUB_PROPERTY_SOURCE; + } else if (propertySource instanceof RandomValuePropertySource) { + propertySourceType = RANDOM_VALUE_PROPRERTY_SOURCE; + } else if (propertySource instanceof JndiPropertySource){ + propertySourceType = JDNI_PROPERTY_SOURCE_TYPE; + } else if (propertySource instanceof EnumerablePropertySource){ + propertySourceType = ENUMERABLE_PROPERTY_SOURCE; + } else if (propertySource instanceof OriginLookup) { + propertySourceType = ofOriginLookup((OriginLookup) propertySource); + } else { + propertySourceType = UNKNOWN; + } + propertySourceType.log(propertySource); + return propertySourceType; + } + + private static PropertySourceType ofOriginLookup(OriginLookup originLookupPropertySource) { + if ("org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource".equals(originLookupPropertySource.getClass().getCanonicalName())){ + return ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE; + } + return SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE; + } + + private void log(PropertySource propertySource) { + switch (this){ + case ANSI_PROPERTY_SOURCE -> log.warn(()->"Processing of AnsiPropertySource "+ propertySource +" not yet implemented : properties exclusively from this property source will be ignored"); + case STUB_PROPERTY_SOURCE -> log.debug(() -> propertySource + " is a stub property source : it does not contain properties : will be ignored"); + case RANDOM_VALUE_PROPRERTY_SOURCE -> log.debug(()-> propertySource + " is a RandomValuePropertySource : will be ignored"); + case JDNI_PROPERTY_SOURCE_TYPE -> log.warn(()-> propertySource + " is a JndiPropertySource : it is not enumerable : will be ignored"); + case ENUMERABLE_PROPERTY_SOURCE -> log.trace(() -> propertySource + " is a EnumerablePropertySource : is a candidate to find keys"); + case ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE -> log.debug(()-> propertySource + " will be used as the originFinder but will be ignored as a property source"); + case SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE -> log.debug(()-> propertySource + " will be ignored as a property source and also as OriginLookup"); + case UNKNOWN -> log.warn(()-> propertySource + " is unknown : will be ignored"); + } + } + } + + /* + this.originFinder=key->{ + var origin=originLookup.getOrigin(key); + return origin==null?"":" ### FROM "+ origin+" ###"; + }; + */ + public boolean isOriginFinder(){ + return switch (this){ + case ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE -> true; + case ANSI_PROPERTY_SOURCE, STUB_PROPERTY_SOURCE, ENUMERABLE_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, + RANDOM_VALUE_PROPRERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, -> false; + }; + } + + public boolean isEnumerable() { + return switch (this){ + case ANSI_PROPERTY_SOURCE, STUB_PROPERTY_SOURCE, RANDOM_VALUE_PROPRERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, + ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE -> false; + case ENUMERABLE_PROPERTY_SOURCE -> true; + }; + } +} From c17faa546ab671d2f39d14d722fc8de298ebb099 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Sun, 25 Jan 2026 07:25:25 +0100 Subject: [PATCH 06/11] refactor (spring boot 4 : complexity) OK --- .gitignore | 3 +- pom.xml | 7 +- ...mentPreparedEventForPropertiesLogging.java | 8 +- .../IgnoredPropertySources.java | 17 +- .../propertieslogger/PropertiesLogger.java | 193 ++++++++++++------ .../propertieslogger/PropertySourceType.java | 116 +++++++---- .../fbibonne/test/WebAppIntegrationTest.java | 4 +- 7 files changed, 233 insertions(+), 115 deletions(-) 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/pom.xml b/pom.xml index c3d3df5..c877ee4 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-dependencies - 4.0.0 + 4.0.2 io.github.fbibonne @@ -15,6 +15,11 @@ 1.6.0-SNAPSHOT jar + 1.0.0 17 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 21509c1..99f1ef2 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 @@ -25,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 @@ -62,8 +62,8 @@ 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) { 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/PropertiesLogger.java b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLogger.java index ccbb281..bf91189 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,12 +1,10 @@ package io.github.fbibonne.springaddons.boot.propertieslogger; import org.jspecify.annotations.Nullable; -import org.springframework.boot.ansi.AnsiPropertySource; -import org.springframework.boot.env.RandomValuePropertySource; +import org.springframework.boot.origin.Origin; 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.Arrays; import java.util.HashSet; @@ -14,11 +12,12 @@ import java.util.function.Consumer; 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 */ @@ -31,97 +30,161 @@ 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; - UnaryOperator originFinder = k->""; + 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 method : *
    - *
  1. 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}
  2. + *
  3. 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
    • + *
    • propertySource of type org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource + * won't be processed but will be used to resolve origin of property values later (see {@link this#originFinder}
    • + *
    • If propertySource has an unknown type, a warning is logged
    • + *
    + *
  4. *
  5. 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}
  6. *
  7. order distinct keys with alphabetical order (natural order of {@link String}
  8. *
  9. 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 + * the environment ({@link PropertiesLogger#abstractEnvironment}) 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)
  10. *
  11. log the list of used propertySources to find keys, the ordered list of properties and their values and origin when available
  12. *
* - * @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); + public void doLogProperties() { + debugStarting(); + final StringBuilder stringWithPropertiesToDisplay = new StringBuilder(); - abstractEnvironment.getPropertySources().stream() - .mapMulti(this::asEnumerablePropertySourceIfHasToBeProcessed) - .flatMap(this::toPropertyNames) - .distinct() + 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 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() - .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()); + .map(this::toDisplayedLine) + .collect(Collectors.joining(System.lineSeparator())); + } + private static Stream distinctPropertiesNames(Map propertyNamesBySource) { + return propertyNamesBySource.values().stream() + .flatMap(Arrays::stream) + .distinct(); + } - log.info(stringWithPropertiesToDisplay::toString); + private Map propertyNamesBySourceFromEnvironment() { + Map propertyNamesBySource = new HashMap<>(); + for (PropertySource propertySource : this.abstractEnvironment.getPropertySources()) { + PropertySourceType propertySourceType = PropertySourceType.of(propertySource); + asEnumerablePropertySourceIfHasToBeProcessed(propertySourceType, propertySource) + .ifPresent(enumerablePropertySource -> + propertyNamesBySource.put(enumerablePropertySource.getName(), enumerablePropertySource.getPropertyNames()) + ); + processOriginFinder(propertySource, propertySourceType); + } + return propertyNamesBySource; + } + + private void processOriginFinder(PropertySource propertySource, PropertySourceType propertySourceType) { + if (propertySourceType.isOriginFinder()) { + final OriginLookup originLookup = (OriginLookup) propertySource; + this.originFinder = asOriginFinder(originLookup); + } + } + + private static UnaryOperator asOriginFinder(OriginLookup originLookup) { + return key -> { + var origin = originLookup.getOrigin(key); + return originAsLine(origin); + }; } - private void insertPropretySourceNamesOnePerLine(Set propertySourceNames, StringBuilder stringWithPropertiesToDisplay) { - propertySourceNames.forEach(name -> stringWithPropertiesToDisplay.insert(0, System.lineSeparator()).insert(0, name).insert(0, "- ")); + private static String originAsLine(@Nullable Origin origin) { + return origin == null ? "" : " ### FROM " + origin + " ###"; } - private void asEnumerablePropertySourceIfHasToBeProcessed(PropertySource propertySource, Consumer> downstream) { - PropertySourceType propertySourceType = PropertySourceType.of(propertySource); - var mustBeProcessed = propertySourceType.isEnumerable() && isNotIgnored(propertySource); - if (mustBeProcessed) { - propertySourceNames.add(propertySource.getName()); - downstream.accept((EnumerablePropertySource) propertySource); + 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 String insertPropretySourceNamesOnePerLine(Set propertySourceNames) { + return propertySourceNames.stream().sorted().map(name -> "- " + name).collect(Collectors.joining(System.lineSeparator())); + } + + private Optional> asEnumerablePropertySourceIfHasToBeProcessed(PropertySourceType propertySourceType, PropertySource propertySource) { + if (mustBeProcessed(propertySourceType, propertySource)) { + return Optional.of((EnumerablePropertySource) propertySource); } + return Optional.empty(); + } + + private boolean mustBeProcessed(PropertySourceType propertySourceType, PropertySource propertySource) { + return propertySourceType.isEnumerable() && 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) + originFinder.apply(key); } private @Nullable String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environment) { - if (propertiesWithHiddenValues.stream().anyMatch(isValueContainedIgnoringCaseIn(key))) { + 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 @@ -151,30 +214,26 @@ Predicate isValueContainedIgnoringCaseIn(String container) { private Stream toPropertyNames(EnumerablePropertySource propertySource) { log.trace(() -> "Flat properties for " + propertySource.getName()); return Arrays.stream(propertySource.getPropertyNames()); + private boolean mustBeMasked(String key) { + return propertiesWithHiddenValues.stream().anyMatch(key::contains); } - /** - * 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) { - - - } private boolean keyWithAllowedPrefix(String key) { log.trace(() -> "Check if property " + key + " can be displayed"); - if (allowedPrefixForProperties.anyMatch(key::startsWith)) { - return true; + boolean keyWithAllowedPrefix = isKeyWithAllowedPrefix(key); + if (!keyWithAllowedPrefix) { + debugNotAllowedPrefix(key); } + return keyWithAllowedPrefix; + } + + 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 index fe48d93..1f01bef 100644 --- a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java +++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java @@ -7,73 +7,111 @@ 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, - STUB_PROPERTY_SOURCE, RANDOM_VALUE_PROPRERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, ENUMERABLE_PROPERTY_SOURCE, - ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, UNKNOWN; + ANSI_PROPERTY_SOURCE("AnsiPropertySource"), + STUB_PROPERTY_SOURCE("StubPropertySource"), RANDOM_VALUE_PROPRERTY_SOURCE("RandomValuePropertySource"), + JDNI_PROPERTY_SOURCE_TYPE("JndiPropertySource"), + ENUMERABLE_PROPERTY_SOURCE("EnumerablePropertySource"), + ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE("OriginLookup[with OriginProvider]"), SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE("OriginLookup"), UNKNOWN("unknown type"); + + private final String className; + + PropertySourceType(String className) { + this.className = className; + } private static final LocalLogger log = new LocalLogger(PropertySourceType.class); public static PropertySourceType of(PropertySource propertySource) { + PropertySourceType propertySourceType = isEnumerable(propertySource) ? ENUMERABLE_PROPERTY_SOURCE : ofNonEnumerablePropertySource(propertySource); + propertySourceType.log(propertySource); + return propertySourceType; + } + + private static PropertySourceType ofNonEnumerablePropertySource(PropertySource propertySource) { + return propertySource instanceof OriginLookup ? + ofOriginLookupPropertySource(propertySource) : + ofOtherPropertySource(propertySource); + } + + private static PropertySourceType ofOtherPropertySource(PropertySource propertySource) { + return isAnsiPropertySource(propertySource) ? ANSI_PROPERTY_SOURCE : ofIgnoredPropertySource(propertySource); + } + + private static PropertySourceType ofIgnoredPropertySource(PropertySource propertySource) { PropertySourceType propertySourceType; - if (propertySource instanceof AnsiPropertySource) { - propertySourceType = ANSI_PROPERTY_SOURCE; - } else if (propertySource instanceof PropertySource.StubPropertySource) { + if (propertySource instanceof PropertySource.StubPropertySource) { propertySourceType = STUB_PROPERTY_SOURCE; } else if (propertySource instanceof RandomValuePropertySource) { propertySourceType = RANDOM_VALUE_PROPRERTY_SOURCE; - } else if (propertySource instanceof JndiPropertySource){ + } else if (propertySource instanceof JndiPropertySource) { propertySourceType = JDNI_PROPERTY_SOURCE_TYPE; - } else if (propertySource instanceof EnumerablePropertySource){ - propertySourceType = ENUMERABLE_PROPERTY_SOURCE; - } else if (propertySource instanceof OriginLookup) { - propertySourceType = ofOriginLookup((OriginLookup) propertySource); } else { propertySourceType = UNKNOWN; } - propertySourceType.log(propertySource); return propertySourceType; } - private static PropertySourceType ofOriginLookup(OriginLookup originLookupPropertySource) { - if ("org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource".equals(originLookupPropertySource.getClass().getCanonicalName())){ - return ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE; - } - return SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE; + private static boolean isAnsiPropertySource(PropertySource propertySource) { + return propertySource instanceof AnsiPropertySource; + } + + private static PropertySourceType ofOriginLookupPropertySource(PropertySource propertySource) { + return isOriginFinder((OriginLookup) propertySource) ? ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE + : SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE; + } + + private static boolean isEnumerable(PropertySource propertySource) { + return propertySource instanceof EnumerablePropertySource; + } + + private static boolean isOriginFinder(OriginLookup originLookupPropertySource) { + return "org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource".equals(originLookupPropertySource.getClass().getCanonicalName()); } private void log(PropertySource propertySource) { - switch (this){ - case ANSI_PROPERTY_SOURCE -> log.warn(()->"Processing of AnsiPropertySource "+ propertySource +" not yet implemented : properties exclusively from this property source will be ignored"); - case STUB_PROPERTY_SOURCE -> log.debug(() -> propertySource + " is a stub property source : it does not contain properties : will be ignored"); - case RANDOM_VALUE_PROPRERTY_SOURCE -> log.debug(()-> propertySource + " is a RandomValuePropertySource : will be ignored"); - case JDNI_PROPERTY_SOURCE_TYPE -> log.warn(()-> propertySource + " is a JndiPropertySource : it is not enumerable : will be ignored"); - case ENUMERABLE_PROPERTY_SOURCE -> log.trace(() -> propertySource + " is a EnumerablePropertySource : is a candidate to find keys"); - case ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE -> log.debug(()-> propertySource + " will be used as the originFinder but will be ignored as a property source"); - case SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE -> log.debug(()-> propertySource + " will be ignored as a property source and also as OriginLookup"); - case UNKNOWN -> log.warn(()-> propertySource + " is unknown : will be ignored"); - } - } + 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 STUB_PROPERTY_SOURCE -> + propertySource + " is a stub property source : it does not contain properties : will be ignored"; + case RANDOM_VALUE_PROPRERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, + UNKNOWN -> propertySource + " is a " + this.className + " : it is not enumerable : will be ignored"; + case ENUMERABLE_PROPERTY_SOURCE -> + propertySource + " is a EnumerablePropertySource : is a candidate to find keys"; + case ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE -> + propertySource + " will be used as the originFinder but will be ignored as a property source"; + }; + } + + private Consumer> withLogLevel() { + return switch (this) { + case ANSI_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, UNKNOWN -> log::warn; + case STUB_PROPERTY_SOURCE, RANDOM_VALUE_PROPRERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, + ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE -> log::debug; + case ENUMERABLE_PROPERTY_SOURCE -> log::trace; + }; } - /* - this.originFinder=key->{ - var origin=originLookup.getOrigin(key); - return origin==null?"":" ### FROM "+ origin+" ###"; - }; - */ - public boolean isOriginFinder(){ - return switch (this){ + public boolean isOriginFinder() { + return switch (this) { case ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE -> true; case ANSI_PROPERTY_SOURCE, STUB_PROPERTY_SOURCE, ENUMERABLE_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, - RANDOM_VALUE_PROPRERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, -> false; + RANDOM_VALUE_PROPRERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, UNKNOWN -> false; }; } public boolean isEnumerable() { - return switch (this){ + return switch (this) { case ANSI_PROPERTY_SOURCE, STUB_PROPERTY_SOURCE, RANDOM_VALUE_PROPRERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, - ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE -> false; + ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, UNKNOWN -> false; case ENUMERABLE_PROPERTY_SOURCE -> true; }; } diff --git a/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java b/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java index 71bd93d..6c9a42c 100644 --- a/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java +++ b/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java @@ -33,12 +33,12 @@ void contextLoads(CapturedOutput output) { 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" ### From 60f4025b2d41f46f3f07b3f18f5abde852291964 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Sun, 25 Jan 2026 15:26:41 +0100 Subject: [PATCH 07/11] refactor (PropertySourceType)) OK --- .../propertieslogger/PropertiesLogger.java | 36 +++---------------- .../propertieslogger/PropertySourceType.java | 32 +++-------------- .../fbibonne/test/WebAppIntegrationTest.java | 24 ++++++------- 3 files changed, 21 insertions(+), 71 deletions(-) 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 bf91189..5f8f206 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 @@ -2,15 +2,10 @@ import org.jspecify.annotations.Nullable; import org.springframework.boot.origin.Origin; -import org.springframework.boot.origin.OriginLookup; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.PropertySource; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Predicate; +import java.util.*; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -32,8 +27,6 @@ class PropertiesLogger { final IgnoredPropertySources ignoredPropertySources; final EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment; - UnaryOperator originFinder = k -> ""; - PropertiesLogger(PropertiesWithHiddenValues propertiesWithHiddenValues, AllowedPrefixForProperties allowedPrefixForProperties, IgnoredPropertySources ignoredPropertySources, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment) { this.propertiesWithHiddenValues = propertiesWithHiddenValues; @@ -52,16 +45,14 @@ class PropertiesLogger { *
  • 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
  • - *
  • propertySource of type org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource - * won't be processed but will be used to resolve origin of property values later (see {@link this#originFinder}
  • + *
  • 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 ({@link PropertiesLogger#abstractEnvironment}) 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
  • * * @@ -115,29 +106,10 @@ private Map propertyNamesBySourceFromEnvironment() { .ifPresent(enumerablePropertySource -> propertyNamesBySource.put(enumerablePropertySource.getName(), enumerablePropertySource.getPropertyNames()) ); - processOriginFinder(propertySource, propertySourceType); } return propertyNamesBySource; } - private void processOriginFinder(PropertySource propertySource, PropertySourceType propertySourceType) { - if (propertySourceType.isOriginFinder()) { - final OriginLookup originLookup = (OriginLookup) propertySource; - this.originFinder = asOriginFinder(originLookup); - } - } - - private static UnaryOperator asOriginFinder(OriginLookup originLookup) { - return key -> { - var origin = originLookup.getOrigin(key); - return originAsLine(origin); - }; - } - - private static String originAsLine(@Nullable Origin origin) { - return origin == null ? "" : " ### FROM " + origin + " ###"; - } - 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); @@ -171,7 +143,7 @@ private static void traceIgnored(PropertySource propertySource) { } private String toDisplayedLine(String key) { - return key + " = " + resolveValueThenMaskItIfSecret(key, this.abstractEnvironment) + originFinder.apply(key); + return key + " = " + resolveValueThenMaskItIfSecret(key, this.abstractEnvironment); } private @Nullable String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environment) { 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 index 1f01bef..6b26604 100644 --- a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java +++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java @@ -15,7 +15,7 @@ public enum PropertySourceType { STUB_PROPERTY_SOURCE("StubPropertySource"), RANDOM_VALUE_PROPRERTY_SOURCE("RandomValuePropertySource"), JDNI_PROPERTY_SOURCE_TYPE("JndiPropertySource"), ENUMERABLE_PROPERTY_SOURCE("EnumerablePropertySource"), - ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE("OriginLookup[with OriginProvider]"), SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE("OriginLookup"), UNKNOWN("unknown type"); + ORIGIN_LOOKUP_PROPERTY_SOURCE("OriginLookup"), UNKNOWN("unknown type"); private final String className; @@ -32,9 +32,7 @@ public static PropertySourceType of(PropertySource propertySource) { } private static PropertySourceType ofNonEnumerablePropertySource(PropertySource propertySource) { - return propertySource instanceof OriginLookup ? - ofOriginLookupPropertySource(propertySource) : - ofOtherPropertySource(propertySource); + return propertySource instanceof OriginLookup ? ORIGIN_LOOKUP_PROPERTY_SOURCE : ofOtherPropertySource(propertySource); } private static PropertySourceType ofOtherPropertySource(PropertySource propertySource) { @@ -59,19 +57,10 @@ private static boolean isAnsiPropertySource(PropertySource propertySource) { return propertySource instanceof AnsiPropertySource; } - private static PropertySourceType ofOriginLookupPropertySource(PropertySource propertySource) { - return isOriginFinder((OriginLookup) propertySource) ? ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE - : SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE; - } - private static boolean isEnumerable(PropertySource propertySource) { return propertySource instanceof EnumerablePropertySource; } - private static boolean isOriginFinder(OriginLookup originLookupPropertySource) { - return "org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertySource".equals(originLookupPropertySource.getClass().getCanonicalName()); - } - private void log(PropertySource propertySource) { withLogLevel().accept(() -> logMessage(propertySource)); } @@ -82,36 +71,25 @@ private String logMessage(PropertySource propertySource) { "Processing of AnsiPropertySource " + propertySource + " not yet implemented : properties exclusively from this property source will be ignored"; case STUB_PROPERTY_SOURCE -> propertySource + " is a stub property source : it does not contain properties : will be ignored"; - case RANDOM_VALUE_PROPRERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, + case RANDOM_VALUE_PROPRERTY_SOURCE, ORIGIN_LOOKUP_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, UNKNOWN -> propertySource + " is a " + this.className + " : it is not enumerable : will be ignored"; case ENUMERABLE_PROPERTY_SOURCE -> propertySource + " is a EnumerablePropertySource : is a candidate to find keys"; - case ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE -> - propertySource + " will be used as the originFinder but will be ignored as a property source"; }; } private Consumer> withLogLevel() { return switch (this) { case ANSI_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, UNKNOWN -> log::warn; - case STUB_PROPERTY_SOURCE, RANDOM_VALUE_PROPRERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, - ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE -> log::debug; + case STUB_PROPERTY_SOURCE, RANDOM_VALUE_PROPRERTY_SOURCE, ORIGIN_LOOKUP_PROPERTY_SOURCE -> log::debug; case ENUMERABLE_PROPERTY_SOURCE -> log::trace; }; } - public boolean isOriginFinder() { - return switch (this) { - case ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE -> true; - case ANSI_PROPERTY_SOURCE, STUB_PROPERTY_SOURCE, ENUMERABLE_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, - RANDOM_VALUE_PROPRERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, UNKNOWN -> false; - }; - } - public boolean isEnumerable() { return switch (this) { case ANSI_PROPERTY_SOURCE, STUB_PROPERTY_SOURCE, RANDOM_VALUE_PROPRERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, - ORIGIN_FINDER_ORIGIN_LOOKUP_PROPERTY_SOURCE, SIMPLE_ORIGIN_LOOKUP_PROPERTY_SOURCE, UNKNOWN -> false; + ORIGIN_LOOKUP_PROPERTY_SOURCE, UNKNOWN -> false; case ENUMERABLE_PROPERTY_SOURCE -> true; }; } diff --git a/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java b/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java index 6c9a42c..439c304 100644 --- a/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java +++ b/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java @@ -28,7 +28,7 @@ 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\ ================================================================================ @@ -41,20 +41,20 @@ void contextLoads(CapturedOutput output) { - commandLineArgs%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 ### + from.system.properties = true + io.github.fbibonne.secret = ****** + io.github.fbibonne.shared = additionalPropsInClasspath + io.github.fbibonne.specific.additional-file = additional-file.properties io.github.fbibonne.specific.additionalPropsInClasspath = additionalPropsInClasspath - 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" ### - properties.logger.prefix-for-properties = info, logging, spring, server, management, properties, springdoc, io, from ### FROM "properties.logger.prefix-for-properties" from property source "Inlined Test Properties" ### - properties.logger.sources-ignored = systemEnvironment ### FROM "properties.logger.sources-ignored" from property source "Inlined Test Properties" ### + io.github.fbibonne.specific.applicationproperties = application.properties + io.github.fbibonne.test = ok + logging.level.io.github.fbibonne.springaddons.boot = trace + properties.logger.prefix-for-properties = info, logging, spring, server, management, properties, springdoc, io, from + properties.logger.sources-ignored = systemEnvironment spring.application.pid =\s""").formatted()) .contains((""" - spring.config.additional-location = classpath:additional-file.properties,file:src/test/resources/otherProps/application.properties ### FROM "spring.config.additional-location" from property source "commandLineArgs" ### - spring.jmx.enabled = false ### FROM "spring.jmx.enabled" from property source "Inlined Test Properties" ### + spring.config.additional-location = classpath:additional-file.properties,file:src/test/resources/otherProps/application.properties + spring.jmx.enabled = false ================================================================================%n""").formatted() ); } From 922f85583aad3c27a5fc52ef4ef1bf4f60333c3c Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Sun, 25 Jan 2026 16:11:39 +0100 Subject: [PATCH 08/11] feat (display property origin) OK - need a bit refactor --- .../boot/propertieslogger/OriginFinder.java | 37 +++++++++++++++++++ .../propertieslogger/PropertiesLogger.java | 10 ++++- .../fbibonne/test/WebAppIntegrationTest.java | 24 ++++++------ 3 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/OriginFinder.java 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 5f8f206..ac51a5c 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,7 +1,6 @@ package io.github.fbibonne.springaddons.boot.propertieslogger; import org.jspecify.annotations.Nullable; -import org.springframework.boot.origin.Origin; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.PropertySource; @@ -26,6 +25,7 @@ class PropertiesLogger { final AllowedPrefixForProperties allowedPrefixForProperties; final IgnoredPropertySources ignoredPropertySources; final EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment; + final OriginFinder originFinder; PropertiesLogger(PropertiesWithHiddenValues propertiesWithHiddenValues, AllowedPrefixForProperties allowedPrefixForProperties, IgnoredPropertySources ignoredPropertySources, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment) { @@ -33,6 +33,7 @@ class PropertiesLogger { this.allowedPrefixForProperties = allowedPrefixForProperties; this.ignoredPropertySources = ignoredPropertySources; this.abstractEnvironment = abstractEnvironment; + this.originFinder = new OriginFinder(abstractEnvironment.getPropertySources()); } /** @@ -143,7 +144,12 @@ private static void traceIgnored(PropertySource propertySource) { } private String toDisplayedLine(String key) { - return key + " = " + resolveValueThenMaskItIfSecret(key, this.abstractEnvironment); + return key + " = " + resolveValueThenMaskItIfSecret(key, this.abstractEnvironment) + + this.originFinder.findOriginFor(key).map(PropertiesLogger::originAsLine).orElse(""); + } + + private static String originAsLine(String origin) { + return " ### " + origin + " ###"; } private @Nullable String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environment) { diff --git a/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java b/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java index 439c304..5370854 100644 --- a/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java +++ b/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java @@ -41,20 +41,20 @@ void contextLoads(CapturedOutput output) { - commandLineArgs%n\ - systemProperties%n\ ====%n\ - from.system.properties = true - io.github.fbibonne.secret = ****** - io.github.fbibonne.shared = additionalPropsInClasspath - io.github.fbibonne.specific.additional-file = additional-file.properties - io.github.fbibonne.specific.additionalPropsInClasspath = additionalPropsInClasspath - io.github.fbibonne.specific.applicationproperties = application.properties - io.github.fbibonne.test = ok - logging.level.io.github.fbibonne.springaddons.boot = trace - properties.logger.prefix-for-properties = info, logging, spring, server, management, properties, springdoc, io, from - properties.logger.sources-ignored = systemEnvironment + 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 ### 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" ### + properties.logger.prefix-for-properties = info, logging, spring, server, management, properties, springdoc, io, from ### FROM "properties.logger.prefix-for-properties" from property source "Inlined Test Properties" ### + properties.logger.sources-ignored = systemEnvironment ### FROM "properties.logger.sources-ignored" from property source "Inlined Test Properties" ### spring.application.pid =\s""").formatted()) .contains((""" - spring.config.additional-location = classpath:additional-file.properties,file:src/test/resources/otherProps/application.properties - spring.jmx.enabled = false + spring.config.additional-location = classpath:additional-file.properties,file:src/test/resources/otherProps/application.properties ### FROM "spring.config.additional-location" from property source "commandLineArgs" ### + spring.jmx.enabled = false ### FROM "spring.jmx.enabled" from property source "Inlined Test Properties" ### ================================================================================%n""").formatted() ); } From 5035d9321f5fba8c8ac0b3382e9448fa655e690f Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Mon, 26 Jan 2026 13:53:47 +0100 Subject: [PATCH 09/11] refactor (PropertySource types) - check if enumerable - log propertysources processed --- pom.xml | 2 +- .../propertieslogger/PropertiesLogger.java | 11 ++- .../propertieslogger/PropertySourceType.java | 70 +++++++++---------- 3 files changed, 39 insertions(+), 44 deletions(-) diff --git a/pom.xml b/pom.xml index c877ee4..e89d0f1 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 3.4.0 3.12.0 3.2.8 - 0.9.0 + 0.10.0
    Spring Boot Properties Logger 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 ac51a5c..32d108c 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 @@ -102,8 +102,7 @@ private static Stream distinctPropertiesNames(Map prop private Map propertyNamesBySourceFromEnvironment() { Map propertyNamesBySource = new HashMap<>(); for (PropertySource propertySource : this.abstractEnvironment.getPropertySources()) { - PropertySourceType propertySourceType = PropertySourceType.of(propertySource); - asEnumerablePropertySourceIfHasToBeProcessed(propertySourceType, propertySource) + asEnumerablePropertySourceIfHasToBeProcessed(propertySource) .ifPresent(enumerablePropertySource -> propertyNamesBySource.put(enumerablePropertySource.getName(), enumerablePropertySource.getPropertyNames()) ); @@ -120,15 +119,15 @@ private String insertPropretySourceNamesOnePerLine(Set propertySourceNam return propertySourceNames.stream().sorted().map(name -> "- " + name).collect(Collectors.joining(System.lineSeparator())); } - private Optional> asEnumerablePropertySourceIfHasToBeProcessed(PropertySourceType propertySourceType, PropertySource propertySource) { - if (mustBeProcessed(propertySourceType, propertySource)) { + private Optional> asEnumerablePropertySourceIfHasToBeProcessed(PropertySource propertySource) { + if (mustBeProcessed(propertySource)) { return Optional.of((EnumerablePropertySource) propertySource); } return Optional.empty(); } - private boolean mustBeProcessed(PropertySourceType propertySourceType, PropertySource propertySource) { - return propertySourceType.isEnumerable() && isNotIgnored(propertySource); + private boolean mustBeProcessed(PropertySource propertySource) { + return PropertySourceType.isEnumerable(propertySource) && isNotIgnored(propertySource); } private boolean isNotIgnored(PropertySource propertySource) { 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 index 6b26604..9d8e001 100644 --- a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java +++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertySourceType.java @@ -11,53 +11,56 @@ import java.util.function.Supplier; public enum PropertySourceType { - ANSI_PROPERTY_SOURCE("AnsiPropertySource"), - STUB_PROPERTY_SOURCE("StubPropertySource"), RANDOM_VALUE_PROPRERTY_SOURCE("RandomValuePropertySource"), - JDNI_PROPERTY_SOURCE_TYPE("JndiPropertySource"), - ENUMERABLE_PROPERTY_SOURCE("EnumerablePropertySource"), - ORIGIN_LOOKUP_PROPERTY_SOURCE("OriginLookup"), UNKNOWN("unknown type"); - - private final String className; - - PropertySourceType(String className) { - this.className = className; - } + ANSI_PROPERTY_SOURCE, + JDNI_PROPERTY_SOURCE_TYPE, + ENUMERABLE_PROPERTY_SOURCE, + OTHERS, UNKNOWN; private static final LocalLogger log = new LocalLogger(PropertySourceType.class); - public static PropertySourceType of(PropertySource propertySource) { - PropertySourceType propertySourceType = isEnumerable(propertySource) ? ENUMERABLE_PROPERTY_SOURCE : ofNonEnumerablePropertySource(propertySource); + public static boolean isEnumerable(PropertySource propertySource) { + PropertySourceType propertySourceType = of(propertySource); propertySourceType.log(propertySource); - return propertySourceType; - } - - private static PropertySourceType ofNonEnumerablePropertySource(PropertySource propertySource) { - return propertySource instanceof OriginLookup ? ORIGIN_LOOKUP_PROPERTY_SOURCE : ofOtherPropertySource(propertySource); + return propertySourceType.isEnumerable(); } - private static PropertySourceType ofOtherPropertySource(PropertySource propertySource) { - return isAnsiPropertySource(propertySource) ? ANSI_PROPERTY_SOURCE : ofIgnoredPropertySource(propertySource); + private static PropertySourceType of(PropertySource propertySource) { + return isEnumerableInstance(propertySource) ? ENUMERABLE_PROPERTY_SOURCE : ofNonEnumerablePropertySource(propertySource); } - private static PropertySourceType ofIgnoredPropertySource(PropertySource propertySource) { + private static PropertySourceType ofNonEnumerablePropertySource(PropertySource propertySource) { PropertySourceType propertySourceType; - if (propertySource instanceof PropertySource.StubPropertySource) { - propertySourceType = STUB_PROPERTY_SOURCE; - } else if (propertySource instanceof RandomValuePropertySource) { - propertySourceType = RANDOM_VALUE_PROPRERTY_SOURCE; + 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 isEnumerable(PropertySource propertySource) { + private static boolean isEnumerableInstance(PropertySource propertySource) { return propertySource instanceof EnumerablePropertySource; } @@ -69,10 +72,7 @@ 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 STUB_PROPERTY_SOURCE -> - propertySource + " is a stub property source : it does not contain properties : will be ignored"; - case RANDOM_VALUE_PROPRERTY_SOURCE, ORIGIN_LOOKUP_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, - UNKNOWN -> propertySource + " is a " + this.className + " : it is not enumerable : 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"; }; @@ -81,16 +81,12 @@ private String logMessage(PropertySource propertySource) { private Consumer> withLogLevel() { return switch (this) { case ANSI_PROPERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, UNKNOWN -> log::warn; - case STUB_PROPERTY_SOURCE, RANDOM_VALUE_PROPRERTY_SOURCE, ORIGIN_LOOKUP_PROPERTY_SOURCE -> log::debug; + case OTHERS -> log::debug; case ENUMERABLE_PROPERTY_SOURCE -> log::trace; }; } - public boolean isEnumerable() { - return switch (this) { - case ANSI_PROPERTY_SOURCE, STUB_PROPERTY_SOURCE, RANDOM_VALUE_PROPRERTY_SOURCE, JDNI_PROPERTY_SOURCE_TYPE, - ORIGIN_LOOKUP_PROPERTY_SOURCE, UNKNOWN -> false; - case ENUMERABLE_PROPERTY_SOURCE -> true; - }; + private boolean isEnumerable() { + return this == ENUMERABLE_PROPERTY_SOURCE; } } From 1e53c0e1a8489197e78f1bf547792eafc867a4f2 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Tue, 27 Jan 2026 10:32:12 +0100 Subject: [PATCH 10/11] fix (bug due to rebase) --- ...nvironmentPreparedEventForPropertiesLogging.java | 2 +- .../boot/propertieslogger/PropertiesLogger.java | 13 +------------ .../boot/propertieslogger/PropertiesLoggerTest.java | 6 ++++-- 3 files changed, 6 insertions(+), 15 deletions(-) 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 99f1ef2..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 @@ -102,7 +102,7 @@ private static Method resolveMethod(Class targetType, String methodName, Clas return method; } - private CustomAbstractEnvironment(ConfigurableEnvironment delegate) { + CustomAbstractEnvironment(ConfigurableEnvironment delegate) { this.delegate = delegate; } 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 32d108c..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 @@ -5,7 +5,7 @@ import org.springframework.core.env.PropertySource; import java.util.*; -import java.util.function.UnaryOperator; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -172,9 +172,6 @@ private boolean mustBeMasked(String key) { */ Predicate isValueContainedIgnoringCaseIn(String container) { return value -> { - if (value==null){ - return false; - } if (value.isEmpty()){ return true; } @@ -188,14 +185,6 @@ Predicate isValueContainedIgnoringCaseIn(String container) { }; } - private Stream toPropertyNames(EnumerablePropertySource propertySource) { - log.trace(() -> "Flat properties for " + propertySource.getName()); - return Arrays.stream(propertySource.getPropertyNames()); - private boolean mustBeMasked(String key) { - return propertiesWithHiddenValues.stream().anyMatch(key::contains); - } - - private boolean keyWithAllowedPrefix(String key) { log.trace(() -> "Check if property " + key + " can be displayed"); boolean keyWithAllowedPrefix = isKeyWithAllowedPrefix(key); 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 From 69b656ad43eb1f104cbba8c6c383227a85946641 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Tue, 27 Jan 2026 10:35:03 +0100 Subject: [PATCH 11/11] pom (prepare version 2.0.0) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e89d0f1..060a0a1 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ io.github.fbibonne boot-properties-logger-starter - 1.6.0-SNAPSHOT + 2.0.0 jar