diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml index 386a330..0c77785 100644 --- a/.github/workflows/deploy-release.yml +++ b/.github/workflows/deploy-release.yml @@ -29,7 +29,7 @@ jobs: MAVEN_GPG_KEY: ${{ secrets.MAVEN_GPG_KEY }} MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} - run: mvn clean deploy --no-transfer-progress + run: mvn clean deploy -Pci-publish --no-transfer-progress - name: Get current version id: version run: echo "::set-output name=prop::$(mvn -f pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)" diff --git a/docs/console.png b/docs/console.png index 4816b27..fdc46e9 100644 Binary files a/docs/console.png and b/docs/console.png differ diff --git a/docs/console_banner.png b/docs/console_banner.png new file mode 100644 index 0000000..67e68ce Binary files /dev/null and b/docs/console_banner.png differ diff --git a/docs/illustration.svg b/docs/illustration.svg new file mode 100644 index 0000000..f4123c0 --- /dev/null +++ b/docs/illustration.svg @@ -0,0 +1,176 @@ + + + + diff --git a/docs/illustrationColoree.png b/docs/illustrationColoree.png new file mode 100644 index 0000000..75e18ff Binary files /dev/null and b/docs/illustrationColoree.png differ diff --git a/pom.xml b/pom.xml index e8f3f66..a521c1f 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ io.github.fbibonne boot-properties-logger-starter - 2.1.0 + 2.2.0 jar @@ -107,6 +107,59 @@ + + + ci-publish + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + bc + + + + sign-artifacts + install + + sign + + + + + + dev.sigstore + sigstore-maven-plugin + ${sigstore-maven-plugin.version} + + + sign + install + + sign + + + + + + org.sonatype.central + central-publishing-maven-plugin + ${central-publishing-maven-plugin.version} + true + + central + Properties-Logger + true + + + + + + + + @@ -152,47 +205,6 @@ - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin.version} - - bc - - - - sign-artifacts - install - - sign - - - - - - dev.sigstore - sigstore-maven-plugin - ${sigstore-maven-plugin.version} - - - sign - install - - sign - - - - - - org.sonatype.central - central-publishing-maven-plugin - ${central-publishing-maven-plugin.version} - true - - central - 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 c44f754..34b5386 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 @@ -11,15 +11,20 @@ /** * 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()} method. + * inside the 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 */ -class PropertiesLogger { +public class PropertiesLogger { public static final String SEPARATION_LINE = "================================================================================"; public static final String MASK = "******"; private static final LocalLogger log = new LocalLogger(PropertiesLogger.class); + public static final String ANSI_CYAN_BOLD_SEQUENCE = "\u001B[1;36m"; + public static final String ANSI_NORMAL_SEQUENCE = "\u001B[0m"; + public static final String ANSI_BROWN_UNDERLINE_SEQUENCE = "\u001B[4;33m"; + public static final String AINSI_PURPLE_ITALIC_SEQUENCE = "\u001B[1;3;35m"; + public static final String AINSI_GREEN_BOLD_SEQUENCE = "\u001B[1;32m"; final PropertiesWithHiddenValues propertiesWithHiddenValues; final AllowedPrefixForProperties allowedPrefixForProperties; @@ -46,7 +51,7 @@ 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
  • - + * *
  • If propertySource has an unknown type, a warning is logged
  • * * @@ -58,14 +63,14 @@ class PropertiesLogger { * * */ - public void doLogProperties() { + void doLogProperties() { debugStarting(); final StringBuilder stringWithPropertiesToDisplay = new StringBuilder(); Map propertyNamesBySource = propertyNamesBySourceFromEnvironment(); final Set propertySourceNames = propertyNamesBySource.keySet(); - stringWithPropertiesToDisplay.append( headerBlock(propertySourceNames)) + stringWithPropertiesToDisplay.append(headerBlock(propertySourceNames)) .append(toKeyValuesBlock(propertyNamesBySource)) .append(System.lineSeparator()) .append(SEPARATION_LINE); @@ -77,10 +82,10 @@ private String headerBlock(Set propertySourceNames) { return """ %1$s - Values of properties from sources : - %2$s + %2$sValues of properties from sources :%3$s + %4$s ==== - """.formatted(SEPARATION_LINE, insertPropretySourceNamesOnePerLine(propertySourceNames)); + """.formatted(SEPARATION_LINE, AINSI_GREEN_BOLD_SEQUENCE, ANSI_NORMAL_SEQUENCE, propretySourceNamesOnePerLine(propertySourceNames)); } private String toKeyValuesBlock(Map propertyNamesBySource) { @@ -113,7 +118,7 @@ private void debugStarting() { + ignoredPropertySources + ". Values masked for properties whose keys contain " + propertiesWithHiddenValues); } - private String insertPropretySourceNamesOnePerLine(Set propertySourceNames) { + private String propretySourceNamesOnePerLine(Set propertySourceNames) { return propertySourceNames.stream().sorted().map(name -> "- " + name).collect(Collectors.joining(System.lineSeparator())); } @@ -141,12 +146,13 @@ private static void traceIgnored(PropertySource propertySource) { } private String toDisplayedLine(String key) { - return key + " = " + resolveValueThenMaskItIfSecret(key, this.abstractEnvironment) + return ANSI_CYAN_BOLD_SEQUENCE + key + ANSI_NORMAL_SEQUENCE + " = " + + ANSI_BROWN_UNDERLINE_SEQUENCE + resolveValueThenMaskItIfSecret(key, this.abstractEnvironment) + ANSI_NORMAL_SEQUENCE + this.originFinder.findOriginFor(key).map(PropertiesLogger::originAsLine).orElse(""); } private static String originAsLine(String origin) { - return " ### " + origin + " ###"; + return " ### " + AINSI_PURPLE_ITALIC_SEQUENCE + origin + ANSI_NORMAL_SEQUENCE + " ###"; } private @Nullable String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environment) { @@ -163,6 +169,7 @@ private boolean mustBeMasked(String key) { /** * Implementation of a String#containsIgnoreCase based the algorithm of String#contains * but using String#equalsIgnoreCase to test equality + * * @param container the string which is supposed to contain the argument passed to the predicate. * Must be not null * @return a predicate such as container::containsIgnoreCase @@ -170,12 +177,12 @@ private boolean mustBeMasked(String key) { */ Predicate isValueContainedIgnoringCaseIn(String container) { return value -> { - if (value.isEmpty()){ + if (value.isEmpty()) { return true; } int valueLength = value.length(); - for(int i = 0; i <= container.length()- valueLength; i++) { - if (container.substring(i, i+valueLength).equalsIgnoreCase(value)){ + for (int i = 0; i <= container.length() - valueLength; i++) { + if (container.substring(i, i + valueLength).equalsIgnoreCase(value)) { return true; } } diff --git a/src/test/java/io/github/fbibonne/test/ExcludedPropertySourcesTest.java b/src/test/java/io/github/fbibonne/test/ExcludedPropertySourcesTest.java index c34adfb..feea2dd 100644 --- a/src/test/java/io/github/fbibonne/test/ExcludedPropertySourcesTest.java +++ b/src/test/java/io/github/fbibonne/test/ExcludedPropertySourcesTest.java @@ -17,6 +17,7 @@ import java.nio.file.Files; import java.nio.file.Path; +import static io.github.fbibonne.springaddons.boot.propertieslogger.PropertiesLogger.*; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(classes = ExcludedPropertySourcesTest.class, properties = { @@ -63,11 +64,11 @@ void contextLoad_shouldNotPrintExcludedPropertySources(@Autowired Environment en assertThat(environment.getProperty("properties.logger.sources-ignored")).hasToString("systemProperties, systemEnvironment,[application.properties],[otherProps/application.properties],commandLineArgs,Inlined Test Properties,["+ externalPropertiesPath.toAbsolutePath()+"]"); assertThat(environment.getProperty("io.github.fbibonne.shared")).hasToString("dynamicPropertySource"); assertThat(environment.getProperty("io.github.fbibonne.specific.nonExcludedFile")).hasToString("nonExcludedFile"); - assertThat(logOutput).contains("io.github.fbibonne.shared = inlineTestProperties") + assertThat(logOutput).contains("io.github.fbibonne.shared"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+"inlineTestProperties") .doesNotContain("io.github.fbibonne.specific.dynamicPropertySource") - .contains("io.github.fbibonne.specific.nonExcludedFile = nonExcludedFile") + .contains("io.github.fbibonne.specific.nonExcludedFile"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+"nonExcludedFile") .doesNotContain("io.github.fbibonne.sharedWithExternal = nonExcludedFile") - .contains("io.github.fbibonne.sharedWithExternal = inFileSystem") + .contains("io.github.fbibonne.sharedWithExternal"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+"inFileSystem") .doesNotContain("io.github.fbibonne.specific.inFileSystem = inFileSystem") .doesNotContain("io.github.fbibonne.specific.applicationproperties") .doesNotContain("io.github.fbibonne.specific.additionalPropsInClasspath") diff --git a/src/test/java/io/github/fbibonne/test/ExternalPropertiesFilesIntegrationTest.java b/src/test/java/io/github/fbibonne/test/ExternalPropertiesFilesIntegrationTest.java index 1560de8..81e6c3e 100644 --- a/src/test/java/io/github/fbibonne/test/ExternalPropertiesFilesIntegrationTest.java +++ b/src/test/java/io/github/fbibonne/test/ExternalPropertiesFilesIntegrationTest.java @@ -13,6 +13,8 @@ import java.io.IOException; import java.nio.file.Path; +import static io.github.fbibonne.springaddons.boot.propertieslogger.PropertiesLogger.ANSI_BROWN_UNDERLINE_SEQUENCE; +import static io.github.fbibonne.springaddons.boot.propertieslogger.PropertiesLogger.ANSI_NORMAL_SEQUENCE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -40,14 +42,14 @@ void startingSpringBootApplicationWithExternalOptionalPropertiesFilesAndUnexisti "--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'"); - assertThat(capturedOutput.toString()).contains("spring.config.location = optional:file:/${unexisting}/application.properties"); + assertThat(capturedOutput.toString()).contains("spring.config.location"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+"optional:file:/${unexisting}/application.properties"); //otherProps/application.properties /* * io.github.fbibonne.shared = additionalPropsInClasspath * io.github.fbibonne.specific.additionalPropsInClasspath = additionalPropsInClasspath */ assertThat(environment.getProperty("io.github.fbibonne.specific.additionalPropsInClasspath")).hasToString("additionalPropsInClasspath"); - assertThat(capturedOutput.toString()).contains("io.github.fbibonne.specific.additionalPropsInClasspath = additionalPropsInClasspath"); + assertThat(capturedOutput.toString()).contains("io.github.fbibonne.specific.additionalPropsInClasspath"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+"additionalPropsInClasspath"); // /${existing}/application.properties => nonExcluded/application.properties /* * io.github.fbibonne.shared = nonExcludedFile @@ -55,7 +57,7 @@ void startingSpringBootApplicationWithExternalOptionalPropertiesFilesAndUnexisti * io.github.fbibonne.sharedWithExternal = nonExcludedFile */ assertThat(environment.getProperty("io.github.fbibonne.specific.nonExcludedFile")).hasToString("nonExcludedFile"); - assertThat(capturedOutput.toString()).contains("io.github.fbibonne.specific.nonExcludedFile = nonExcludedFile"); + assertThat(capturedOutput.toString()).contains("io.github.fbibonne.specific.nonExcludedFile"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+"nonExcludedFile"); // /${existing:"+ ignoredPath +"}/application.properties => not /ignored/application.properties assertThat(environment.getProperty("io.github.fbibonne.specific.ignoredFile")).isNull(); assertThat(capturedOutput.toString()).doesNotContain("io.github.fbibonne.specific.ignoredFile"); diff --git a/src/test/java/io/github/fbibonne/test/MaskedValuesTest.java b/src/test/java/io/github/fbibonne/test/MaskedValuesTest.java index eeb8b3f..144b2a7 100644 --- a/src/test/java/io/github/fbibonne/test/MaskedValuesTest.java +++ b/src/test/java/io/github/fbibonne/test/MaskedValuesTest.java @@ -8,6 +8,7 @@ import org.springframework.context.annotation.Configuration; import static io.github.fbibonne.springaddons.boot.propertieslogger.ConstantsForTests.MASK; +import static io.github.fbibonne.springaddons.boot.propertieslogger.PropertiesLogger.*; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(classes = ExcludedPropertySourcesTest.class, properties = { @@ -21,7 +22,7 @@ class MaskedValuesTest { @Test void checkMaskValuesIgnoreCase(CapturedOutput output) { assertThat(output.toString()).doesNotContain("verysecretpassword") - .contains("com.mycompany.serviceContactPassword = "+MASK); + .contains(ANSI_CYAN_BOLD_SEQUENCE+"com.mycompany.serviceContactPassword"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+MASK); } } diff --git a/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java b/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java index 3387bb9..557c075 100644 --- a/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java +++ b/src/test/java/io/github/fbibonne/test/WebAppIntegrationTest.java @@ -9,6 +9,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; +import static io.github.fbibonne.springaddons.boot.propertieslogger.PropertiesLogger.*; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(classes = WebAppIntegrationTest.class, properties = { @@ -28,11 +29,10 @@ 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" ### assertThat(output.toString()).contains((""" main] i.g.f.s.b.p.PropertiesLogger : %n\ ================================================================================ - Values of properties from sources : + %1$sValues of properties from sources :%2$s - 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\ @@ -41,22 +41,22 @@ 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 ### - 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()) + %3$sfrom.system.properties%2$s = %4$strue%2$s ### %5$sFROM "from.system.properties" from property source "systemProperties"%2$s ### + %3$sio.github.fbibonne.secret%2$s = %4$s******%2$s ### %5$sFROM class path resource [application.properties] - 2:29%2$s ### + %3$sio.github.fbibonne.shared%2$s = %4$sadditionalPropsInClasspath%2$s ### %5$sFROM URL [file:src/test/resources/otherProps/application.properties] - 1:29%2$s ### + %3$sio.github.fbibonne.specific.additional-file%2$s = %4$sadditional-file.properties%2$s ### %5$sFROM class path resource [additional-file.properties] - 2:47%2$s ### + %3$sio.github.fbibonne.specific.additionalPropsInClasspath%2$s = %4$sadditionalPropsInClasspath%2$s ### %5$sWARNING ! Configuration property name 'io.github.fbibonne.specific.additionalPropsInClasspath' is not valid : see org.springframework.boot.context.properties.source.ConfigurationPropertyName%2$s ### + %3$sio.github.fbibonne.specific.applicationproperties%2$s = %4$sapplication.properties%2$s ### %5$sFROM class path resource [application.properties] - 4:53%2$s ### + %3$sio.github.fbibonne.test%2$s = %4$sok%2$s ### %5$sFROM class path resource [application.properties] - 1:27%2$s ### + %3$slogging.level.io.github.fbibonne.springaddons.boot%2$s = %4$strace%2$s ### %5$sFROM "logging.level.io.github.fbibonne.springaddons.boot" from property source "Inlined Test Properties"%2$s ### + %3$sproperties.logger.prefix-for-properties%2$s = %4$sinfo, logging, spring, server, management, properties, springdoc, io, from%2$s ### %5$sFROM "properties.logger.prefix-for-properties" from property source "Inlined Test Properties"%2$s ### + %3$sproperties.logger.sources-ignored%2$s = %4$ssystemEnvironment%2$s ### %5$sFROM "properties.logger.sources-ignored" from property source "Inlined Test Properties"%2$s ### + %3$sspring.application.pid%2$s =\s""").formatted(AINSI_GREEN_BOLD_SEQUENCE, ANSI_NORMAL_SEQUENCE, ANSI_CYAN_BOLD_SEQUENCE, ANSI_BROWN_UNDERLINE_SEQUENCE, AINSI_PURPLE_ITALIC_SEQUENCE)) .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.datasource.username = user_prod ### FROM System Environment Property "SPRING_DATASOURCE_USERNAME" ### - spring.jmx.enabled = false ### FROM "spring.jmx.enabled" from property source "Inlined Test Properties" ### - ================================================================================%n""").formatted() + %3$sspring.config.additional-location%2$s = %4$sclasspath:additional-file.properties,file:src/test/resources/otherProps/application.properties%2$s ### %5$sFROM "spring.config.additional-location" from property source "commandLineArgs"%2$s ### + %3$sspring.datasource.username%2$s = %4$suser_prod%2$s ### %5$sFROM System Environment Property "SPRING_DATASOURCE_USERNAME"%2$s ### + %3$sspring.jmx.enabled%2$s = %4$sfalse%2$s ### %5$sFROM "spring.jmx.enabled" from property source "Inlined Test Properties"%2$s ### + ================================================================================%n""").formatted(AINSI_GREEN_BOLD_SEQUENCE, ANSI_NORMAL_SEQUENCE, ANSI_CYAN_BOLD_SEQUENCE, ANSI_BROWN_UNDERLINE_SEQUENCE, AINSI_PURPLE_ITALIC_SEQUENCE) ); } diff --git a/src/test/java/io/github/fbibonne/test/examples/WithEnvironmentVariableTest.java b/src/test/java/io/github/fbibonne/test/examples/WithEnvironmentVariableTest.java index 17ca0fa..f76e449 100644 --- a/src/test/java/io/github/fbibonne/test/examples/WithEnvironmentVariableTest.java +++ b/src/test/java/io/github/fbibonne/test/examples/WithEnvironmentVariableTest.java @@ -1,6 +1,5 @@ package io.github.fbibonne.test.examples; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; @@ -8,6 +7,7 @@ import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.annotation.Configuration; +import static io.github.fbibonne.springaddons.boot.propertieslogger.PropertiesLogger.*; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(classes = WithEnvironmentVariableTest.class) @@ -19,7 +19,7 @@ class WithEnvironmentVariableTest { void shouldPickKeyFromApplicationPropertiesAndValueFromEnvironmentVariable(CapturedOutput output) { String logOutput = output.toString(); - assertThat(logOutput).contains("spring.datasource.username = user_prod ### FROM System Environment Property \"SPRING_DATASOURCE_USERNAME\" ###"); + 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+" ###"); } }