From 456d7b7d0cc142c9ec87a62e70e3002b0584c126 Mon Sep 17 00:00:00 2001 From: Jakob Wanger Date: Sat, 16 Mar 2024 21:29:17 -0400 Subject: [PATCH] FEAT - Add standardized property to distinguish a group of applications - Add a standardized property to provide some indicator that a set of applications are part of a larger "business application" so that they can be viewed in metrics, portals, traces and more https://github.com/spring-projects/spring-boot/issues/39913 Signed-off-by: Jakob Wanger --- .../otlp/OtlpPropertiesConfigAdapter.java | 10 +++ .../OpenTelemetryAutoConfiguration.java | 9 +++ .../OtlpPropertiesConfigAdapterTests.java | 26 ++++++ .../pages/properties-and-configuration.adoc | 1 + .../reference/pages/actuator/tracing.adoc | 3 +- .../reference/pages/features/logging.adoc | 5 +- .../boot/logging/LoggingSystemProperties.java | 11 +++ .../boot/logging/LoggingSystemProperty.java | 5 ++ .../logback/ApplicationGroupConverter.java | 50 ++++++++++++ .../logback/DefaultLogbackConfiguration.java | 5 +- .../logging/logback/LogbackRuntimeHints.java | 6 +- .../boot/logging/log4j2/log4j2-file.xml | 4 +- .../boot/logging/log4j2/log4j2.xml | 4 +- .../boot/logging/logback/defaults.xml | 5 +- .../logging/LoggingSystemPropertiesTests.java | 19 +++++ .../log4j2/Log4J2LoggingSystemTests.java | 73 +++++++++++++++++ .../ApplicationGroupConverterTests.java | 81 +++++++++++++++++++ .../logback/LogbackLoggingSystemTests.java | 56 +++++++++++++ .../src/main/resources/log4j2.xml | 2 +- .../src/main/resources/application.properties | 1 + 20 files changed, 362 insertions(+), 14 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ApplicationGroupConverter.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ApplicationGroupConverterTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java index 79640714a1e..88cd5eacbca 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapter.java @@ -43,6 +43,11 @@ class OtlpPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter result = new HashMap<>((!CollectionUtils.isEmpty(resourceAttributes)) ? resourceAttributes : get(OtlpProperties::getResourceAttributes, OtlpConfig.super::resourceAttributes)); result.computeIfAbsent("service.name", (key) -> getApplicationName()); + result.computeIfAbsent("service.group", (key) -> getApplicationGroup()); return Collections.unmodifiableMap(result); } @@ -86,6 +92,10 @@ class OtlpPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter headers() { return get(OtlpProperties::getHeaders, OtlpConfig.super::headers); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java index 622ad4371b0..3ccdf9eba87 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/opentelemetry/OpenTelemetryAutoConfiguration.java @@ -53,8 +53,15 @@ public class OpenTelemetryAutoConfiguration { */ private static final String DEFAULT_APPLICATION_NAME = "unknown_service"; + /** + * Default value for application group if {@code spring.application.group} is not set. + */ + private static final String DEFAULT_APPLICATION_GROUP = "unknown_group"; + private static final AttributeKey ATTRIBUTE_KEY_SERVICE_NAME = AttributeKey.stringKey("service.name"); + private static final AttributeKey ATTRIBUTE_KEY_SERVICE_GROUP = AttributeKey.stringKey("service.group"); + @Bean @ConditionalOnMissingBean(OpenTelemetry.class) OpenTelemetrySdk openTelemetry(ObjectProvider tracerProvider, @@ -72,8 +79,10 @@ public class OpenTelemetryAutoConfiguration { @ConditionalOnMissingBean Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) { String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); + String applicationGroup = environment.getProperty("spring.application.group", DEFAULT_APPLICATION_GROUP); return Resource.getDefault() .merge(Resource.create(Attributes.of(ATTRIBUTE_KEY_SERVICE_NAME, applicationName))) + .merge(Resource.create(Attributes.of(ATTRIBUTE_KEY_SERVICE_GROUP, applicationGroup))) .merge(toResource(properties)); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java index dd6c3f19920..38d67a5f824 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesConfigAdapterTests.java @@ -139,6 +139,32 @@ class OtlpPropertiesConfigAdapterTests { assertThat(createAdapter().resourceAttributes()).containsEntry("service.name", "unknown_service"); } + @Test + @SuppressWarnings("removal") + void serviceGroupOverridesApplicationGroup() { + this.environment.setProperty("spring.application.group", "alpha"); + this.properties.setResourceAttributes(Map.of("service.group", "beta")); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.group", "beta"); + } + + @Test + void serviceGroupOverridesApplicationGroupWhenUsingOtelProperties() { + this.environment.setProperty("spring.application.group", "alpha"); + this.openTelemetryProperties.setResourceAttributes(Map.of("service.group", "beta")); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.group", "beta"); + } + + @Test + void shouldUseApplicationGroupIfServiceGroupIsNotSet() { + this.environment.setProperty("spring.application.group", "alpha"); + assertThat(createAdapter().resourceAttributes()).containsEntry("service.group", "alpha"); + } + + @Test + void shouldUseDefaultApplicationGroupIfApplicationGroupIsNotSet() { + assertThat(createAdapter().resourceAttributes()).containsEntry("service.group", "unknown_group"); + } + private OtlpPropertiesConfigAdapter createAdapter() { return new OtlpPropertiesConfigAdapter(this.properties, this.openTelemetryProperties, this.connectionDetails, this.environment); diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/properties-and-configuration.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/properties-and-configuration.adoc index 40184bd008d..0e091e073fe 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/properties-and-configuration.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/properties-and-configuration.adoc @@ -204,6 +204,7 @@ The preceding example YAML corresponds to the following `application.properties` [source,properties,subs="verbatim",configprops] ---- spring.application.name=cruncher +spring.application.group=crunchGroup spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost/test server.port=9000 diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc index 8761abffa59..c56f1ad57a8 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/tracing.adoc @@ -88,9 +88,10 @@ logging: pattern: correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}] " include-application-name: false + include-application-group: false ---- -NOTE: In the example above, configprop:logging.include-application-name[] is set to `false` to avoid the application name being duplicated in the log messages (configprop:logging.pattern.correlation[] already contains it). +NOTE: In the example above, configprop:logging.include-application-name[] and configprop:logging.include-application-group[] is set to `false` to avoid the application name being duplicated in the log messages (configprop:logging.pattern.correlation[] already contains it). It's also worth mentioning that configprop:logging.pattern.correlation[] contains a trailing space so that it is separated from the logger name that comes right after it by default. diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/logging.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/logging.adoc index 746f25c6fee..38cd6a0219c 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/logging.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/logging.adoc @@ -34,6 +34,7 @@ The following items are output: * Process ID. * A `---` separator to distinguish the start of actual log messages. * Application name: Enclosed in square brackets (logged by default only if configprop:spring.application.name[] is set) +* Application group: Enclosed in square brackets (logged by default only if configprop:spring.application.group[] is set) * Thread name: Enclosed in square brackets (may be truncated for console output). * Correlation ID: If tracing is enabled (not shown in the sample above) * Logger name: This is usually the source class name (often abbreviated). @@ -43,6 +44,7 @@ NOTE: Logback does not have a `FATAL` level. It is mapped to `ERROR`. TIP: If you have a configprop:spring.application.name[] property but don't want it logged you can set configprop:logging.include-application-name[] to `false`. +TIP: If you have a configprop:spring.application.group[] property but don't want it logged you can set configprop:logging.include-application-group[] to `false`. @@ -544,12 +546,13 @@ The following listing shows three sample profiles: If you want to refer to properties from your Spring `Environment` within your Log4j2 configuration you can use `spring:` prefixed https://logging.apache.org/log4j/2.x/manual/lookups.html[lookups]. Doing so can be useful if you want to access values from your `application.properties` file in your Log4j2 configuration. -The following example shows how to set a Log4j2 property named `applicationName` that reads `spring.application.name` from the Spring `Environment`: +The following example shows how to set a Log4j2 property named `applicationName` and `applicationGroup` that reads `spring.application.name` and `spring.application.group` from the Spring `Environment`: [source,xml] ---- ${spring:spring.application.name} + ${spring:spring.application.property} ---- diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java index a39c8afbdb0..59c7c9e0cc7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java @@ -229,6 +229,7 @@ public class LoggingSystemProperties { protected void apply(LogFile logFile, PropertyResolver resolver) { String defaultCharsetName = getDefaultCharset().name(); setApplicationNameSystemProperty(resolver); + setApplicationGroupSystemProperty(resolver); setSystemProperty(LoggingSystemProperty.PID, new ApplicationPid().toString()); setSystemProperty(LoggingSystemProperty.CONSOLE_CHARSET, resolver, defaultCharsetName); setSystemProperty(LoggingSystemProperty.FILE_CHARSET, resolver, defaultCharsetName); @@ -255,6 +256,16 @@ public class LoggingSystemProperties { } } + private void setApplicationGroupSystemProperty(PropertyResolver resolver) { + if (resolver.getProperty("logging.include-application-group", Boolean.class, Boolean.TRUE)) { + String applicationGroup = resolver.getProperty("spring.application.group"); + if (StringUtils.hasText(applicationGroup)) { + setSystemProperty(LoggingSystemProperty.APPLICATION_GROUP.getEnvironmentVariableName(), + "[%s] ".formatted(applicationGroup)); + } + } + } + private void setSystemProperty(LoggingSystemProperty property, PropertyResolver resolver) { setSystemProperty(property, resolver, Function.identity()); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java index 489ebec89fe..6ef25a84e4f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java @@ -30,6 +30,11 @@ public enum LoggingSystemProperty { */ APPLICATION_NAME("LOGGED_APPLICATION_NAME"), + /** + * Logging system property for the application group that should be logged. + */ + APPLICATION_GROUP("LOGGED_APPLICATION_GROUP"), + /** * Logging system property for the process ID. */ diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ApplicationGroupConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ApplicationGroupConverter.java new file mode 100644 index 00000000000..8042a62df0e --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ApplicationGroupConverter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.logging.logback; + +import ch.qos.logback.classic.pattern.ClassicConverter; +import ch.qos.logback.classic.pattern.PropertyConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; + +import org.springframework.boot.logging.LoggingSystemProperty; + +/** + * Logback {@link ClassicConverter} to convert the + * {@link LoggingSystemProperty#APPLICATION_GROUP APPLICATION_GROUP} into a value suitable + * for logging. Similar to Logback's {@link PropertyConverter} but a non-existent property + * is logged as an empty string rather than {@code null}. + * + * @author Jakob Wanger + * @since 3.4.0 + */ +public class ApplicationGroupConverter extends ClassicConverter { + + @Override + public String convert(ILoggingEvent event) { + String applicationGroup = event.getLoggerContextVO() + .getPropertyMap() + .get(LoggingSystemProperty.APPLICATION_GROUP.getEnvironmentVariableName()); + if (applicationGroup == null) { + applicationGroup = System.getProperty(LoggingSystemProperty.APPLICATION_GROUP.getEnvironmentVariableName()); + if (applicationGroup == null) { + applicationGroup = ""; + } + } + return applicationGroup; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java index abba6c24c41..26d5375c023 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java @@ -69,6 +69,7 @@ class DefaultLogbackConfiguration { private void defaults(LogbackConfigurator config) { config.conversionRule("applicationName", ApplicationNameConverter.class); + config.conversionRule("applicationGroup", ApplicationGroupConverter.class); config.conversionRule("clr", ColorConverter.class); config.conversionRule("correlationId", CorrelationIdConverter.class); config.conversionRule("wex", WhitespaceThrowableProxyConverter.class); @@ -76,7 +77,7 @@ class DefaultLogbackConfiguration { config.getContext() .putProperty("CONSOLE_LOG_PATTERN", resolve(config, "${CONSOLE_LOG_PATTERN:-" + "%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) " - + "%clr(${PID:- }){magenta} %clr(---){faint} %clr(%applicationName[%15.15t]){faint} " + + "%clr(${PID:- }){magenta} %clr(---){faint} %clr(%applicationName[%15.15t]){faint} %clr(---){faint} %clr(%applicationGroup[%15.15t]){faint} " + "%clr(${LOG_CORRELATION_PATTERN:-}){faint}%clr(%-40.40logger{39}){cyan} " + "%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}")); String defaultCharset = Charset.defaultCharset().name(); @@ -85,7 +86,7 @@ class DefaultLogbackConfiguration { config.getContext().putProperty("CONSOLE_LOG_THRESHOLD", resolve(config, "${CONSOLE_LOG_THRESHOLD:-TRACE}")); config.getContext() .putProperty("FILE_LOG_PATTERN", resolve(config, "${FILE_LOG_PATTERN:-" - + "%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- %applicationName[%t] " + + "%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- %applicationName[%t] --- %applicationGroup[%t] " + "${LOG_CORRELATION_PATTERN:-}" + "%-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}")); config.getContext() diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackRuntimeHints.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackRuntimeHints.java index f73eac890b6..8285ef57b5f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackRuntimeHints.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackRuntimeHints.java @@ -58,9 +58,9 @@ class LogbackRuntimeHints implements RuntimeHintsRegistrar { } private void registerHintsForSpringBootConverters(ReflectionHints reflection) { - registerForPublicConstructorInvocation(reflection, ApplicationNameConverter.class, ColorConverter.class, - ExtendedWhitespaceThrowableProxyConverter.class, WhitespaceThrowableProxyConverter.class, - CorrelationIdConverter.class); + registerForPublicConstructorInvocation(reflection, ApplicationNameConverter.class, + ApplicationGroupConverter.class, ColorConverter.class, ExtendedWhitespaceThrowableProxyConverter.class, + WhitespaceThrowableProxyConverter.class, CorrelationIdConverter.class); } private void registerForPublicConstructorInvocation(ReflectionHints reflection, Class... classes) { diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml index 916f1126db6..a078a2bfd7e 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml @@ -4,8 +4,8 @@ %xwEx %5p yyyy-MM-dd'T'HH:mm:ss.SSSXXX - %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_NAME:-}[%15.15t]}{faint} %clr{${sys:LOG_CORRELATION_PATTERN:-}}{faint}%clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} - %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- ${sys:LOGGED_APPLICATION_NAME:-}[%t] ${sys:LOG_CORRELATION_PATTERN:-}%-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_NAME:-}[%15.15t]}{faint} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_GROUP:-}[%15.15t]}{faint} %clr{${sys:LOG_CORRELATION_PATTERN:-}}{faint}%clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- ${sys:LOGGED_APPLICATION_NAME:-} --- ${sys:LOGGED_APPLICATION_GROUP:-}[%t] ${sys:LOG_CORRELATION_PATTERN:-}%-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml index 239f2e35a34..5bd996b6bc8 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml @@ -4,8 +4,8 @@ %xwEx %5p yyyy-MM-dd'T'HH:mm:ss.SSSXXX - %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_NAME:-}[%15.15t]}{faint} %clr{${sys:LOG_CORRELATION_PATTERN:-}}{faint}%clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} - %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- ${sys:LOGGED_APPLICATION_NAME:-}[%t] ${sys:LOG_CORRELATION_PATTERN:-}%-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %clr{%d{${sys:LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${sys:LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_NAME:-}[%15.15t]} {faint}%clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_GROUP:-}[%15.15t]}{faint} %clr{${sys:LOG_CORRELATION_PATTERN:-}}{faint}%clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %d{${sys:LOG_DATEFORMAT_PATTERN}} ${sys:LOG_LEVEL_PATTERN} %pid --- ${sys:LOGGED_APPLICATION_NAME:-} --- ${sys:LOGGED_APPLICATION_GROUP:-}[%t] ${sys:LOG_CORRELATION_PATTERN:-}%-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml index a469b4cba9c..a641a362166 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml @@ -6,15 +6,16 @@ Default logback configuration provided for import + - + - + diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java index b070493fe62..3f81feaeeca 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java @@ -156,6 +156,25 @@ class LoggingSystemPropertiesTests { assertThat(getSystemProperty(LoggingSystemProperty.APPLICATION_NAME)).isNull(); } + @Test + void loggedApplicationGroupWhenHasApplicationGroup() { + new LoggingSystemProperties(new MockEnvironment().withProperty("spring.application.group", "test")).apply(null); + assertThat(getSystemProperty(LoggingSystemProperty.APPLICATION_GROUP)).isEqualTo("[test] "); + } + + @Test + void loggedApplicationGroupWhenHasNoApplicationGroup() { + new LoggingSystemProperties(new MockEnvironment()).apply(null); + assertThat(getSystemProperty(LoggingSystemProperty.APPLICATION_GROUP)).isNull(); + } + + @Test + void loggedApplicationGroupWhenApplicationGroupLoggingDisabled() { + new LoggingSystemProperties(new MockEnvironment().withProperty("spring.application.group", "test") + .withProperty("logging.include-application-group", "false")).apply(null); + assertThat(getSystemProperty(LoggingSystemProperty.APPLICATION_GROUP)).isNull(); + } + @Test void shouldSupportFalseConsoleThreshold() { new LoggingSystemProperties(new MockEnvironment().withProperty("logging.threshold.console", "false")) diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java index 7d089c17508..4fcc4ad6ead 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java @@ -651,6 +651,79 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests { .doesNotContain("myapp"); } + @Test + void applicationGroupLoggingToConsoleWhenHasApplicationGroup(CapturedOutput output) { + this.environment.setProperty("spring.application.group", "mygroup"); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).contains("[mygroup] "); + } + + @Test + void applicationGroupLoggingToConsoleWhenHasApplicationGroupWithParenthesis(CapturedOutput output) { + this.environment.setProperty("spring.application.group", "mygroup (dev)"); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).contains("[mygroup (dev)] "); + } + + @Test + void applicationGroupLoggingToConsoleWhenDisabled(CapturedOutput output) { + this.environment.setProperty("spring.application.group", "application-group"); + this.environment.setProperty("logging.include-application-group", "false"); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).doesNotContain("${sys:LOGGED_APPLICATION_GROUP}") + .doesNotContain("myapp"); + } + + @Test + void applicationGroupLoggingToFileWhenHasApplicationGroup() { + this.environment.setProperty("spring.application.group", "mygroup"); + new LoggingSystemProperties(this.environment).apply(); + File file = new File(tmpDir(), "log4j2-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, logFile); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")).contains("[mygroup] "); + } + + @Test + void applicationGroupLoggingToFileWhenHasApplicationGroupWithParenthesis() { + this.environment.setProperty("spring.application.group", "mygroup (dev)"); + new LoggingSystemProperties(this.environment).apply(); + File file = new File(tmpDir(), "log4j2-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, logFile); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")).contains("[mygroup (dev)] "); + } + + @Test + void applicationGroupLoggingToFileWhenDisabled() { + this.environment.setProperty("spring.application.group", "application-group"); + this.environment.setProperty("logging.include-application-group", "false"); + new LoggingSystemProperties(this.environment).apply(); + File file = new File(tmpDir(), "log4j2-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + this.loggingSystem.setStandardConfigLocations(false); + this.loggingSystem.beforeInitialize(); + this.loggingSystem.initialize(this.initializationContext, null, logFile); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")).doesNotContain("${sys:LOGGED_APPLICATION_GROUP}") + .doesNotContain("myapp"); + } + @Test void shouldNotContainAnsiEscapeCodes(CapturedOutput output) { this.loggingSystem.beforeInitialize(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ApplicationGroupConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ApplicationGroupConverterTests.java new file mode 100644 index 00000000000..5375814301f --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ApplicationGroupConverterTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.logging.logback; + +import java.util.Collections; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.LoggerContextVO; +import ch.qos.logback.classic.spi.LoggingEvent; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.logging.LoggingSystemProperty; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ApplicationGroupConverter}. + * + * @author Jakob Wanger + */ +class ApplicationGroupConverterTests { + + private final ApplicationGroupConverter converter; + + private final LoggingEvent event = new LoggingEvent(); + + ApplicationGroupConverterTests() { + this.converter = new ApplicationGroupConverter(); + this.converter.setContext(new LoggerContext()); + this.event.setLoggerContextRemoteView( + new LoggerContextVO("test", Collections.emptyMap(), System.currentTimeMillis())); + } + + @Test + void whenNoLoggedApplicationGroupConvertReturnsEmptyString() { + withLoggedApplicationGroup(null, () -> { + this.converter.start(); + String converted = this.converter.convert(this.event); + assertThat(converted).isEqualTo(""); + }); + } + + @Test + void whenLoggedApplicationGroupConvertReturnsIt() { + withLoggedApplicationGroup("my-application", () -> { + this.converter.start(); + String converted = this.converter.convert(this.event); + assertThat(converted).isEqualTo("my-application"); + }); + } + + private void withLoggedApplicationGroup(String group, Runnable action) { + if (group == null) { + System.clearProperty(LoggingSystemProperty.APPLICATION_GROUP.getEnvironmentVariableName()); + } + else { + System.setProperty(LoggingSystemProperty.APPLICATION_GROUP.getEnvironmentVariableName(), group); + } + try { + action.run(); + } + finally { + System.clearProperty(LoggingSystemProperty.APPLICATION_GROUP.getEnvironmentVariableName()); + } + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index 995c25f8804..fbd182b3524 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -876,6 +876,62 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { assertThat(output).doesNotContain("WARN"); } + @Test + void applicationGroupLoggingToConsoleWhenHasApplicationGroup(CapturedOutput output) { + this.environment.setProperty("spring.application.group", "mygroup"); + initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).contains("[mygroup] "); + } + + @Test + void applicationGroupLoggingToConsoleWhenHasApplicationGroupWithParenthesis(CapturedOutput output) { + this.environment.setProperty("spring.application.group", "mygroup (dev)"); + initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).contains("[mygroup (dev)] "); + } + + @Test + void applicationGroupLoggingToConsoleWhenDisabled(CapturedOutput output) { + this.environment.setProperty("spring.application.group", "mygroup"); + this.environment.setProperty("logging.include-application-group", "false"); + initialize(this.initializationContext, null, null); + this.logger.info("Hello world"); + assertThat(getLineWithText(output, "Hello world")).doesNotContain("mygroup").doesNotContain("null"); + } + + @Test + void applicationGroupLoggingToFileWhenHasApplicationGroup() { + this.environment.setProperty("spring.application.group", "mygroup"); + File file = new File(tmpDir(), "logback-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + initialize(this.initializationContext, null, logFile); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")).contains("[mygroup] "); + } + + @Test + void applicationGroupLoggingToFileWhenHasApplicationGroupWithParenthesis() { + this.environment.setProperty("spring.application.group", "mygroup (dev)"); + File file = new File(tmpDir(), "logback-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + initialize(this.initializationContext, null, logFile); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")).contains("[mygroup (dev)] "); + } + + @Test + void applicationGroupLoggingToFileWhenDisabled(CapturedOutput output) { + this.environment.setProperty("spring.application.group", "myGroup"); + this.environment.setProperty("logging.include-application-group", "false"); + File file = new File(tmpDir(), "logback-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + initialize(this.initializationContext, null, logFile); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")).doesNotContain("myGroup").doesNotContain("null"); + } + @Test void shouldNotContainAnsiEscapeCodes(CapturedOutput output) { this.loggingSystem.beforeInitialize(); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/log4j2.xml b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/log4j2.xml index 1c84d286b09..bcda4f2453c 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/log4j2.xml +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-log4j2/src/main/resources/log4j2.xml @@ -2,7 +2,7 @@ ???? - %clr{%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_NAME:-}[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx + %clr{%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_NAME:-}[%15.15t]}{faint} %clr{---}{faint} %clr{${sys:LOGGED_APPLICATION_GROUP:-}[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/main/resources/application.properties index 2c35d22ff03..381f50751e6 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/main/resources/application.properties +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/main/resources/application.properties @@ -1,4 +1,5 @@ spring.application.name=sample +spring.application.group=sample-group service.name=Phil spring.security.user.name=user