Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ build/
.vscode/

### Mac OS ###
.DS_Store
.DS_Store
/mise.toml
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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 ###
================================================================================

. ____ _ __ _ _
Expand All @@ -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 `====` )
Expand Down
28 changes: 22 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,28 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.5.5</version>
<version>4.0.2</version>
</parent>

<groupId>io.github.fbibonne</groupId>
<artifactId>boot-properties-logger-starter</artifactId>
<version>1.5.0</version>
<version>2.0.0</version>
<packaging>jar</packaging>

<!-- If you can't get dependabot, find updates with :
```
mvn -ntp org.codehaus.mojo:versions-maven-plugin:2.21.0:display-property-updates
mvn -ntp org.codehaus.mojo:versions-maven-plugin:2.21.0:display-parent-updates
``` -->
<properties>
<jspecify.version>1.0.0</jspecify.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven-source-plugin.version>3.4.0</maven-source-plugin.version>
<maven-javadoc-plugin.version>3.12.0</maven-javadoc-plugin.version>
<maven-gpg-plugin.version>3.2.8</maven-gpg-plugin.version>
<central-publishing-maven-plugin.version>0.10.0</central-publishing-maven-plugin.version>
</properties>

<name>Spring Boot Properties Logger</name>
Expand Down Expand Up @@ -69,6 +79,12 @@


<dependencies>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>${jspecify.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
Expand Down Expand Up @@ -107,7 +123,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
<version>${maven-source-plugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
Expand All @@ -120,7 +136,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.10.1</version>
<version>${maven-javadoc-plugin.version}</version>
<executions>
<execution>
<id>attach-javadocs</id>
Expand All @@ -133,7 +149,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.8</version>
<version>${maven-gpg-plugin.version}</version>
<configuration>
<signer>bc</signer>
</configuration>
Expand All @@ -150,7 +166,7 @@
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.8.0</version>
<version>${central-publishing-maven-plugin.version}</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.*;
Expand All @@ -24,12 +25,12 @@ public record EnvironmentPreparedEventForPropertiesLogging() implements Applicat

private static final Set<String> DEFAULT_PROPS_WITH_HIDDEN_VALUES = Set.of("password", "pwd", "token", "secret", "credential", "pw");
private static final Set<String> DEFAULT_PREFIX_FOR_PROPERTIES = Set.of("debug", "trace", "info", "logging", "spring", "server", "management", "springdoc", "properties");
private static final Set<String> DEFAULT_SOURCES_IGNORED = Set.of(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
public static final Set<String> 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
Expand Down Expand Up @@ -61,26 +62,37 @@ 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> T getPropertyOrDefaultAndTrace(PropertyResolver environment, String key, Class<T> 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> T getPropertyOrDefaultAndTraceThrowingException(PropertyResolver environment, String key, Class<T> clazz, T defaultValue) throws RuntimeException{
T result = environment.getProperty(key, clazz, defaultValue);
log.trace(() -> key + " -> " + result);
return result;
}

static final class CustomAbstractEnvironment implements PropertyResolver {
private static final Method getPropertyResolver = resolveMethod(AbstractEnvironment.class, "getPropertyResolver");
private static final Method getPropertyAsRawString = resolveMethod(AbstractPropertyResolver.class, "getPropertyAsRawString", String.class);

private final ConfigurableEnvironment delegate;
@Nullable
private AbstractPropertyResolver propertyResolver;


Expand All @@ -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);
Expand All @@ -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);
}

Expand Down Expand Up @@ -153,6 +166,7 @@ public MutablePropertySources getPropertySources() {
return delegate.getPropertySources();
}

@Nullable
public String getPropertySafely(String key) {
try{
return delegate.getProperty(key);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> sources) {
public Stream<String> stream() {
private Stream<String> stream() {
return sources.stream();
}

/**
* Return true if the library is configured to ignore the propertySource passed as a parameter.
* <br/>
* 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<ConfigurationPropertySource> configurationPropertySources) {

public OriginFinder(MutablePropertySources propertySources) {
this(propertySources.stream()
.map(ConfigurationPropertySource::from)
.filter(Objects::nonNull)
.toList());
}

public Optional<String> 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");
}
}
}
Loading