Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
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.*;
import org.springframework.util.ReflectionUtils;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.StandardEnvironment;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

Expand Down Expand Up @@ -40,12 +39,12 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
log.debug(() -> "PropertiesLogger is disabled");
return;
}
abstractEnvironment(environment).ifPresent(this::doLogProperties);
configurableEnvironment(environment).ifPresent(this::doLogProperties);
}

private Optional<CustomAbstractEnvironment> abstractEnvironment(Environment environment) {
private Optional<ConfigurableEnvironment> configurableEnvironment(Environment environment) {
if (environment instanceof ConfigurableEnvironment configurableEnvironment) {
return Optional.of(new CustomAbstractEnvironment(configurableEnvironment));
return Optional.of(configurableEnvironment);
}
log.info(()->"Environment "+environment+" is not instance of ConfigurableEnvironment : PropertiesLogger WILL NOT LOG PROPERTIES");
return Optional.empty();
Expand All @@ -55,14 +54,16 @@ private boolean loggingDisabled(Environment environment) {
return getPropertyOrDefaultAndTrace(environment, KEY_FOR_DISABLED, boolean.class, DEFAULT_PROPERTIES_LOGGER_DISABLED);
}

private void doLogProperties(CustomAbstractEnvironment abstractEnvironment) {
private void doLogProperties(ConfigurableEnvironment configurableEnvironment) {
log.debug(() -> "Starting PropertiesLogger on ApplicationEnvironmentPreparedEvent");
log.trace(() -> "Collecting properties to configure PropertiesLogger");
final PropertiesWithHiddenValues propertiesWithHiddenValues = new PropertiesWithHiddenValues(getPropertyOrDefaultAndTrace(abstractEnvironment, KEY_FOR_PROPS_WITH_HIDDEN_VALUES, Set.class, DEFAULT_PROPS_WITH_HIDDEN_VALUES));
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, abstractEnvironment);
configurableEnvironment.setIgnoreUnresolvableNestedPlaceholders(true);
final PropertiesWithHiddenValues propertiesWithHiddenValues = new PropertiesWithHiddenValues(getPropertyOrDefaultAndTrace(configurableEnvironment, KEY_FOR_PROPS_WITH_HIDDEN_VALUES, Set.class, DEFAULT_PROPS_WITH_HIDDEN_VALUES));
final AllowedPrefixForProperties allowedPrefixForProperties = new AllowedPrefixForProperties(getPropertyOrDefaultAndTrace(configurableEnvironment, KEY_FOR_PREFIX_FOR_PROPERTIES, Set.class, DEFAULT_PREFIX_FOR_PROPERTIES));
final IgnoredPropertySources ignoredPropertySources = new IgnoredPropertySources(getPropertyOrDefaultAndTrace(configurableEnvironment, KEY_FOR_SOURCES_IGNORED, Set.class, DEFAULT_SOURCES_IGNORED));

PropertiesLogger propertiesLogger = new PropertiesLogger(propertiesWithHiddenValues, allowedPrefixForProperties, ignoredPropertySources, configurableEnvironment);
propertiesLogger.doLogProperties();
}

Expand All @@ -87,108 +88,4 @@ private static <T> T getPropertyOrDefaultAndTraceThrowingException(PropertyReso
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;


private static Method resolveMethod(Class<?> targetType, String methodName, Class<?>... parameterTypes) {
Method method = Objects.requireNonNull(ReflectionUtils.findMethod(targetType, methodName, parameterTypes));
ReflectionUtils.makeAccessible(method);
return method;
}

CustomAbstractEnvironment(ConfigurableEnvironment delegate) {
this.delegate = delegate;
}

@Nullable
private AbstractPropertyResolver invokeGetPropertyResolver(ConfigurableEnvironment delegate) {
if (delegate instanceof AbstractEnvironment abstractEnvironment) {
var abstractPropertyResolverCondidate = ReflectionUtils.invokeMethod(getPropertyResolver, abstractEnvironment);
if (abstractPropertyResolverCondidate instanceof AbstractPropertyResolver abstractPropertyResolver) {
return abstractPropertyResolver;
}
}
return null;
}

@Override
public boolean containsProperty(String key) {
return delegate.containsProperty(key);
}

@Override
public @Nullable String getProperty(String key) {
return delegate.getProperty(key);
}

@Override
public String getProperty(String key, String defaultValue) {
return delegate.getProperty(key, defaultValue);
}

@Override
public <T> @Nullable T getProperty(String key, Class<T> targetType) {
return delegate.getProperty(key, targetType);
}

@Override
public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
return delegate.getProperty(key, targetType, defaultValue);
}

@Override
public String getRequiredProperty(String key) throws IllegalStateException {
return delegate.getRequiredProperty(key);
}

@Override
public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException {
return delegate.getRequiredProperty(key, targetType);
}

@Override
public String resolvePlaceholders(String text) {
return delegate.resolvePlaceholders(text);
}

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return delegate.resolveRequiredPlaceholders(text);
}

public MutablePropertySources getPropertySources() {
return delegate.getPropertySources();
}

@Nullable
public String getPropertySafely(String key) {
try{
return delegate.getProperty(key);
}catch (IllegalArgumentException e) {
// IllegalArgumentException thrown when unresolved placeholder occurs
return getPropertyAsRawString(key);
} catch (Exception e) {
return "Error while getting property " + key + " : " + e.getMessage();
}
}

@Nullable
private String getPropertyAsRawString(String key) {
return invokeGetPropertyAsRawString(key);
}

@Nullable
private String invokeGetPropertyAsRawString(String key) {
if (this.propertyResolver == null) {
this.propertyResolver = invokeGetPropertyResolver(this.delegate);
}
return (String) ReflectionUtils.invokeMethod(getPropertyAsRawString, this.propertyResolver, key);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.fbibonne.springaddons.boot.propertieslogger;

import org.jspecify.annotations.Nullable;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;

Expand Down Expand Up @@ -29,16 +30,16 @@ public class PropertiesLogger {
final PropertiesWithHiddenValues propertiesWithHiddenValues;
final AllowedPrefixForProperties allowedPrefixForProperties;
final IgnoredPropertySources ignoredPropertySources;
final EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment;
final ConfigurableEnvironment configurableEnvironment;
final OriginFinder originFinder;


PropertiesLogger(PropertiesWithHiddenValues propertiesWithHiddenValues, AllowedPrefixForProperties allowedPrefixForProperties, IgnoredPropertySources ignoredPropertySources, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment) {
PropertiesLogger(PropertiesWithHiddenValues propertiesWithHiddenValues, AllowedPrefixForProperties allowedPrefixForProperties, IgnoredPropertySources ignoredPropertySources, ConfigurableEnvironment configurableEnvironment) {
this.propertiesWithHiddenValues = propertiesWithHiddenValues;
this.allowedPrefixForProperties = allowedPrefixForProperties;
this.ignoredPropertySources = ignoredPropertySources;
this.abstractEnvironment = abstractEnvironment;
this.originFinder = new OriginFinder(abstractEnvironment.getPropertySources());
this.configurableEnvironment = configurableEnvironment;
this.originFinder = new OriginFinder(this.configurableEnvironment.getPropertySources());
}

/**
Expand All @@ -58,7 +59,7 @@ public class PropertiesLogger {
* <li>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}</li>
* <li>order distinct keys with alphabetical order (natural order of {@link String}</li>
* <li>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}. </li>
* the environment ({@link PropertiesLogger#configurableEnvironment}) or masked if key is listed in property {@code properties.logger.with-hidden-values}. </li>
* <li>log the list of used propertySources to find keys, the ordered list of properties and their values and origin when available</li>
* </ol>
*
Expand Down Expand Up @@ -104,7 +105,7 @@ private static Stream<String> distinctPropertiesNames(Map<String, String[]> prop

private Map<String, String[]> propertyNamesBySourceFromEnvironment() {
Map<String, String[]> propertyNamesBySource = new HashMap<>();
for (PropertySource<?> propertySource : this.abstractEnvironment.getPropertySources()) {
for (PropertySource<?> propertySource : this.configurableEnvironment.getPropertySources()) {
asEnumerablePropertySourceIfHasToBeProcessed(propertySource)
.ifPresent(enumerablePropertySource ->
propertyNamesBySource.put(enumerablePropertySource.getName(), enumerablePropertySource.getPropertyNames())
Expand Down Expand Up @@ -147,19 +148,19 @@ private static void traceIgnored(PropertySource<?> propertySource) {

private String toDisplayedLine(String key) {
return ANSI_CYAN_BOLD_SEQUENCE + key + ANSI_NORMAL_SEQUENCE + " = "
+ ANSI_BROWN_UNDERLINE_SEQUENCE + resolveValueThenMaskItIfSecret(key, this.abstractEnvironment) + ANSI_NORMAL_SEQUENCE
+ ANSI_BROWN_UNDERLINE_SEQUENCE + resolveValueThenMaskItIfSecret(key) + ANSI_NORMAL_SEQUENCE
+ this.originFinder.findOriginFor(key).map(PropertiesLogger::originAsLine).orElse("");
}

private static String originAsLine(String origin) {
return " ### " + AINSI_PURPLE_ITALIC_SEQUENCE + origin + ANSI_NORMAL_SEQUENCE + " ###";
}

private @Nullable String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environment) {
private @Nullable String resolveValueThenMaskItIfSecret(String key) {
if (mustBeMasked(key)) {
return MASK;
}
return environment.getPropertySafely(key);
return this.configurableEnvironment.getProperty(key);
}

private boolean mustBeMasked(String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class PropertiesLoggerTest {
})
void isValueContainedIgnoringCaseInTest(String container, String value, boolean expected) {
PropertiesLogger propertiesLogger = new PropertiesLogger(null, null, null,
new EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment(new MockEnvironment())
new MockEnvironment()
);
assertThat(propertiesLogger.isValueContainedIgnoringCaseIn(container).test(value)).isEqualTo(expected);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.PlaceholderResolutionException;

import java.io.IOException;
import java.nio.file.Path;
Expand Down Expand Up @@ -41,7 +42,7 @@ void startingSpringBootApplicationWithExternalOptionalPropertiesFilesAndUnexisti
"--properties.logger.prefix-for-properties=info,logging,spring,server,management,properties,springdoc,io",
"--spring.main.web-application-type=none")).doesNotThrowAnyException();
Environment environment = contextRef.context.getEnvironment();
assertThatCode(()->environment.getProperty("spring.config.location")).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("Could not resolve placeholder 'unexisting'");
assertThatCode(()->environment.getProperty("spring.config.location")).isInstanceOf(PlaceholderResolutionException.class).hasMessageContaining("Could not resolve placeholder 'unexisting'");
assertThat(capturedOutput.toString()).contains("spring.config.location"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+"optional:file:/${unexisting}/application.properties");
//otherProps/application.properties
/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class WithEnvironmentVariableTest {
@Test
void shouldPickKeyFromApplicationPropertiesAndValueFromEnvironmentVariable(CapturedOutput output) {
String logOutput = output.toString();

assertThat(System.getenv("SPRING_DATASOURCE_USERNAME")).hasToString("user_prod");
assertThat(logOutput).contains("spring.datasource.username"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+"user_prod"+ANSI_NORMAL_SEQUENCE+" ### "+AINSI_PURPLE_ITALIC_SEQUENCE+"FROM System Environment Property \"SPRING_DATASOURCE_USERNAME\""+ANSI_NORMAL_SEQUENCE+" ###");
}

Expand Down