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 <jakobwanger@gmail.com>
This commit is contained in:
Jakob Wanger 2024-03-16 21:29:17 -04:00
parent 781d7b0394
commit 456d7b7d0c
20 changed files with 362 additions and 14 deletions

View File

@ -43,6 +43,11 @@ class OtlpPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter<Ot
*/
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 final OpenTelemetryProperties openTelemetryProperties;
private final OtlpMetricsConnectionDetails connectionDetails;
@ -79,6 +84,7 @@ class OtlpPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter<Ot
Map<String, String> 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<Ot
return this.environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
}
private String getApplicationGroup() {
return this.environment.getProperty("spring.application.group", DEFAULT_APPLICATION_GROUP);
}
@Override
public Map<String, String> headers() {
return get(OtlpProperties::getHeaders, OtlpConfig.super::headers);

View File

@ -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<String> ATTRIBUTE_KEY_SERVICE_NAME = AttributeKey.stringKey("service.name");
private static final AttributeKey<String> ATTRIBUTE_KEY_SERVICE_GROUP = AttributeKey.stringKey("service.group");
@Bean
@ConditionalOnMissingBean(OpenTelemetry.class)
OpenTelemetrySdk openTelemetry(ObjectProvider<SdkTracerProvider> 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));
}

View File

@ -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);

View File

@ -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

View File

@ -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.

View File

@ -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]
----
<Properties>
<Property name="applicationName">${spring:spring.application.name}</Property>
<Property name="applicationProperty">${spring:spring.application.property}</Property>
</Properties>
----

View File

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

View File

@ -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.
*/

View File

@ -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;
}
}

View File

@ -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()

View File

@ -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) {

View File

@ -4,8 +4,8 @@
<Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
<Property name="LOG_LEVEL_PATTERN">%5p</Property>
<Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd'T'HH:mm:ss.SSSXXX</Property>
<Property name="CONSOLE_LOG_PATTERN">%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}</Property>
<Property name="FILE_LOG_PATTERN">%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}</Property>
<Property name="CONSOLE_LOG_PATTERN">%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}</Property>
<Property name="FILE_LOG_PATTERN">%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}</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">

View File

@ -4,8 +4,8 @@
<Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
<Property name="LOG_LEVEL_PATTERN">%5p</Property>
<Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd'T'HH:mm:ss.SSSXXX</Property>
<Property name="CONSOLE_LOG_PATTERN">%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}</Property>
<Property name="FILE_LOG_PATTERN">%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}</Property>
<Property name="CONSOLE_LOG_PATTERN">%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}</Property>
<Property name="FILE_LOG_PATTERN">%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}</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">

View File

@ -6,15 +6,16 @@ Default logback configuration provided for import
<included>
<conversionRule conversionWord="applicationName" converterClass="org.springframework.boot.logging.logback.ApplicationNameConverter" />
<conversionRule conversionWord="applicationGroup" converterClass="org.springframework.boot.logging.logback.ApplicationGroupConverter" />
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="correlationId" converterClass="org.springframework.boot.logging.logback.CorrelationIdConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<property name="CONSOLE_LOG_PATTERN" value="${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(${LOG_CORRELATION_PATTERN:-}){faint}%clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<property name="CONSOLE_LOG_PATTERN" value="${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(---){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}}"/>
<property name="CONSOLE_LOG_CHARSET" value="${CONSOLE_LOG_CHARSET:-${file.encoding:-UTF-8}}"/>
<property name="CONSOLE_LOG_THRESHOLD" value="${CONSOLE_LOG_THRESHOLD:-TRACE}"/>
<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- %applicationName[%t] ${LOG_CORRELATION_PATTERN:-}%-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%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}}"/>
<property name="FILE_LOG_CHARSET" value="${FILE_LOG_CHARSET:-${file.encoding:-UTF-8}}"/>
<property name="FILE_LOG_THRESHOLD" value="${FILE_LOG_THRESHOLD:-TRACE}"/>

View File

@ -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"))

View File

@ -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();

View File

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

View File

@ -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();

View File

@ -2,7 +2,7 @@
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="PID">????</Property>
<Property name="LOG_PATTERN">%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</Property>
<Property name="LOG_PATTERN">%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</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">

View File

@ -1,4 +1,5 @@
spring.application.name=sample
spring.application.group=sample-group
service.name=Phil
spring.security.user.name=user