Skip to content

Commit a391613

Browse files
committed
Merge pull request #42813 from nosan
* pr/42813: Polish "Add property to control log exporting" Add property to control log exporting Closes gh-42813
2 parents baac4cc + 0ce4dbd commit a391613

File tree

6 files changed

+288
-0
lines changed

6 files changed

+288
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.logging;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.context.annotation.Conditional;
26+
27+
/**
28+
* {@link Conditional @Conditional} that checks whether logging export is enabled. It
29+
* matches if the value of the {@code management.logging.export.enabled} property is
30+
* {@code true} or if it is not configured. If the {@link #value() logging exporter name}
31+
* is set, the {@code management.<name>.logging.export.enabled} property can be used to
32+
* control the behavior for the specific logging exporter. In that case, the
33+
* exporter-specific property takes precedence over the global property.
34+
*
35+
* @author Moritz Halbritter
36+
* @author Dmytro Nosan
37+
* @since 3.4.0
38+
*/
39+
@Retention(RetentionPolicy.RUNTIME)
40+
@Target({ ElementType.TYPE, ElementType.METHOD })
41+
@Documented
42+
@Conditional(OnEnabledLoggingExportCondition.class)
43+
public @interface ConditionalOnEnabledLoggingExport {
44+
45+
/**
46+
* Name of the logging exporter.
47+
* @return the name of the logging exporter
48+
*/
49+
String value() default "";
50+
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.logging;
18+
19+
import java.util.Map;
20+
21+
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
22+
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
23+
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
24+
import org.springframework.context.annotation.ConditionContext;
25+
import org.springframework.core.type.AnnotatedTypeMetadata;
26+
import org.springframework.util.StringUtils;
27+
28+
/**
29+
* {@link SpringBootCondition} to check whether logging exporter is enabled.
30+
*
31+
* @author Moritz Halbritter
32+
* @author Dmytro Nosan
33+
* @see ConditionalOnEnabledLoggingExport
34+
*/
35+
class OnEnabledLoggingExportCondition extends SpringBootCondition {
36+
37+
private static final String GLOBAL_PROPERTY = "management.logging.export.enabled";
38+
39+
private static final String EXPORTER_PROPERTY = "management.%s.logging.export.enabled";
40+
41+
@Override
42+
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
43+
String loggingExporter = getExporterName(metadata);
44+
if (StringUtils.hasLength(loggingExporter)) {
45+
Boolean exporterLoggingEnabled = context.getEnvironment()
46+
.getProperty(EXPORTER_PROPERTY.formatted(loggingExporter), Boolean.class);
47+
if (exporterLoggingEnabled != null) {
48+
return new ConditionOutcome(exporterLoggingEnabled,
49+
ConditionMessage.forCondition(ConditionalOnEnabledLoggingExport.class)
50+
.because(EXPORTER_PROPERTY.formatted(loggingExporter) + " is " + exporterLoggingEnabled));
51+
}
52+
}
53+
Boolean globalLoggingEnabled = context.getEnvironment().getProperty(GLOBAL_PROPERTY, Boolean.class);
54+
if (globalLoggingEnabled != null) {
55+
return new ConditionOutcome(globalLoggingEnabled,
56+
ConditionMessage.forCondition(ConditionalOnEnabledLoggingExport.class)
57+
.because(GLOBAL_PROPERTY + " is " + globalLoggingEnabled));
58+
}
59+
return ConditionOutcome.match(ConditionMessage.forCondition(ConditionalOnEnabledLoggingExport.class)
60+
.because("is enabled by default"));
61+
}
62+
63+
private static String getExporterName(AnnotatedTypeMetadata metadata) {
64+
Map<String, Object> attributes = metadata
65+
.getAnnotationAttributes(ConditionalOnEnabledLoggingExport.class.getName());
66+
if (attributes == null) {
67+
return null;
68+
}
69+
return (String) attributes.get("value");
70+
}
71+
72+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingConfigurations.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
2424
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder;
2525

26+
import org.springframework.boot.actuate.autoconfigure.logging.ConditionalOnEnabledLoggingExport;
2627
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2728
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2829
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -76,6 +77,7 @@ public String getUrl(Transport transport) {
7677
@Configuration(proxyBeanMethods = false)
7778
@ConditionalOnMissingBean({ OtlpGrpcLogRecordExporter.class, OtlpHttpLogRecordExporter.class })
7879
@ConditionalOnBean(OtlpLoggingConnectionDetails.class)
80+
@ConditionalOnEnabledLoggingExport("otlp")
7981
static class Exporters {
8082

8183
@Bean

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,12 @@
309309
"description": "Whether to enable SSL certificate info.",
310310
"defaultValue": false
311311
},
312+
{
313+
"name": "management.logging.export.enabled",
314+
"type": "java.lang.Boolean",
315+
"description": "Whether auto-configuration of logging is enabled to export logs.",
316+
"defaultValue": true
317+
},
312318
{
313319
"name": "management.metrics.binders.files.enabled",
314320
"type": "java.lang.Boolean",
@@ -2067,6 +2073,11 @@
20672073
"description": "Whether auto-configuration of Micrometer annotations is enabled.",
20682074
"defaultValue": false
20692075
},
2076+
{
2077+
"name": "management.otlp.logging.export.enabled",
2078+
"type": "java.lang.Boolean",
2079+
"description": "Whether auto-configuration of logging is enabled to export OTLP logs."
2080+
},
20702081
{
20712082
"name": "management.otlp.tracing.export.enabled",
20722083
"type": "java.lang.Boolean",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.logging;
18+
19+
import java.util.Collections;
20+
import java.util.Map;
21+
22+
import org.junit.jupiter.api.Test;
23+
24+
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
25+
import org.springframework.context.annotation.ConditionContext;
26+
import org.springframework.core.type.AnnotatedTypeMetadata;
27+
import org.springframework.mock.env.MockEnvironment;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.mockito.BDDMockito.given;
31+
import static org.mockito.Mockito.mock;
32+
33+
/**
34+
* Tests for {@link OnEnabledLoggingExportCondition}.
35+
*
36+
* @author Moritz Halbritter
37+
* @author Dmytro Nosan
38+
*/
39+
class OnEnabledLoggingExportConditionTests {
40+
41+
@Test
42+
void shouldMatchIfNoPropertyIsSet() {
43+
OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition();
44+
ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext(), mockMetadata(""));
45+
assertThat(outcome.isMatch()).isTrue();
46+
assertThat(outcome.getMessage()).isEqualTo("@ConditionalOnEnabledLoggingExport is enabled by default");
47+
}
48+
49+
@Test
50+
void shouldNotMatchIfGlobalPropertyIsFalse() {
51+
OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition();
52+
ConditionOutcome outcome = condition.getMatchOutcome(
53+
mockConditionContext(Map.of("management.logging.export.enabled", "false")), mockMetadata(""));
54+
assertThat(outcome.isMatch()).isFalse();
55+
assertThat(outcome.getMessage())
56+
.isEqualTo("@ConditionalOnEnabledLoggingExport management.logging.export.enabled is false");
57+
}
58+
59+
@Test
60+
void shouldMatchIfGlobalPropertyIsTrue() {
61+
OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition();
62+
ConditionOutcome outcome = condition.getMatchOutcome(
63+
mockConditionContext(Map.of("management.logging.export.enabled", "true")), mockMetadata(""));
64+
assertThat(outcome.isMatch()).isTrue();
65+
assertThat(outcome.getMessage())
66+
.isEqualTo("@ConditionalOnEnabledLoggingExport management.logging.export.enabled is true");
67+
}
68+
69+
@Test
70+
void shouldNotMatchIfExporterPropertyIsFalse() {
71+
OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition();
72+
ConditionOutcome outcome = condition.getMatchOutcome(
73+
mockConditionContext(Map.of("management.otlp.logging.export.enabled", "false")), mockMetadata("otlp"));
74+
assertThat(outcome.isMatch()).isFalse();
75+
assertThat(outcome.getMessage())
76+
.isEqualTo("@ConditionalOnEnabledLoggingExport management.otlp.logging.export.enabled is false");
77+
}
78+
79+
@Test
80+
void shouldMatchIfExporterPropertyIsTrue() {
81+
OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition();
82+
ConditionOutcome outcome = condition.getMatchOutcome(
83+
mockConditionContext(Map.of("management.otlp.logging.export.enabled", "true")), mockMetadata("otlp"));
84+
assertThat(outcome.isMatch()).isTrue();
85+
assertThat(outcome.getMessage())
86+
.isEqualTo("@ConditionalOnEnabledLoggingExport management.otlp.logging.export.enabled is true");
87+
}
88+
89+
@Test
90+
void exporterPropertyShouldOverrideGlobalPropertyIfTrue() {
91+
OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition();
92+
ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext(
93+
Map.of("management.logging.enabled", "false", "management.otlp.logging.export.enabled", "true")),
94+
mockMetadata("otlp"));
95+
assertThat(outcome.isMatch()).isTrue();
96+
assertThat(outcome.getMessage())
97+
.isEqualTo("@ConditionalOnEnabledLoggingExport management.otlp.logging.export.enabled is true");
98+
}
99+
100+
@Test
101+
void exporterPropertyShouldOverrideGlobalPropertyIfFalse() {
102+
OnEnabledLoggingExportCondition condition = new OnEnabledLoggingExportCondition();
103+
ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext(
104+
Map.of("management.logging.enabled", "true", "management.otlp.logging.export.enabled", "false")),
105+
mockMetadata("otlp"));
106+
assertThat(outcome.isMatch()).isFalse();
107+
assertThat(outcome.getMessage())
108+
.isEqualTo("@ConditionalOnEnabledLoggingExport management.otlp.logging.export.enabled is false");
109+
}
110+
111+
private ConditionContext mockConditionContext() {
112+
return mockConditionContext(Collections.emptyMap());
113+
}
114+
115+
private ConditionContext mockConditionContext(Map<String, String> properties) {
116+
ConditionContext context = mock(ConditionContext.class);
117+
MockEnvironment environment = new MockEnvironment();
118+
properties.forEach(environment::setProperty);
119+
given(context.getEnvironment()).willReturn(environment);
120+
return context;
121+
}
122+
123+
private AnnotatedTypeMetadata mockMetadata(String exporter) {
124+
AnnotatedTypeMetadata metadata = mock(AnnotatedTypeMetadata.class);
125+
given(metadata.getAnnotationAttributes(ConditionalOnEnabledLoggingExport.class.getName()))
126+
.willReturn(Map.of("value", exporter));
127+
return metadata;
128+
}
129+
130+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/logging/otlp/OtlpLoggingAutoConfigurationTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ void shouldNotSupplyBeansIfDependencyIsMissing(String packageName) {
7373
});
7474
}
7575

76+
@Test
77+
void shouldBackOffWhenLoggingExportPropertyIsNotEnabled() {
78+
this.contextRunner
79+
.withPropertyValues("management.logging.export.enabled=false",
80+
"management.otlp.logging.endpoint=http://localhost:4318/v1/logs")
81+
.run((context) -> {
82+
assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class);
83+
assertThat(context).doesNotHaveBean(LogRecordExporter.class);
84+
});
85+
}
86+
87+
@Test
88+
void shouldBackOffWhenOtlpLoggingExportPropertyIsNotEnabled() {
89+
this.contextRunner
90+
.withPropertyValues("management.otlp.logging.export.enabled=false",
91+
"management.otlp.logging.endpoint=http://localhost:4318/v1/logs")
92+
.run((context) -> {
93+
assertThat(context).hasSingleBean(OtlpLoggingConnectionDetails.class);
94+
assertThat(context).doesNotHaveBean(LogRecordExporter.class);
95+
});
96+
}
97+
7698
@Test
7799
void shouldBackOffWhenCustomHttpExporterIsDefined() {
78100
this.contextRunner.withUserConfiguration(CustomHttpExporterConfiguration.class)

0 commit comments

Comments
 (0)