Skip to content

Commit 769a9b9

Browse files
committed
Introduce a property to enable or disable logging for each exporter.
This property provides more fine-grained control over log export: - management.otlp.logging.export.enabled By default, it is set to null, but if defined, it takes precedence over the global management.logging.export.enabled property
1 parent be09216 commit 769a9b9

File tree

6 files changed

+265
-0
lines changed

6 files changed

+265
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 exporter 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 Dmytro Nosan
36+
* @since 3.4.0
37+
*/
38+
@Retention(RetentionPolicy.RUNTIME)
39+
@Target({ ElementType.TYPE, ElementType.METHOD })
40+
@Documented
41+
@Conditional(OnEnabledLoggingCondition.class)
42+
public @interface ConditionalOnEnabledLogging {
43+
44+
/**
45+
* Name of the logging exporter.
46+
* @return the name of the logging exporter
47+
*/
48+
String value() default "";
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 Dmytro Nosan
32+
* @see ConditionalOnEnabledLogging
33+
*/
34+
class OnEnabledLoggingCondition extends SpringBootCondition {
35+
36+
private static final String GLOBAL_PROPERTY = "management.logging.export.enabled";
37+
38+
private static final String EXPORTER_PROPERTY = "management.%s.logging.export.enabled";
39+
40+
@Override
41+
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
42+
String loggingExporter = getExporterName(metadata);
43+
if (StringUtils.hasLength(loggingExporter)) {
44+
Boolean exporterLoggingEnabled = context.getEnvironment()
45+
.getProperty(EXPORTER_PROPERTY.formatted(loggingExporter), Boolean.class);
46+
if (exporterLoggingEnabled != null) {
47+
return new ConditionOutcome(exporterLoggingEnabled,
48+
ConditionMessage.forCondition(ConditionalOnEnabledLogging.class)
49+
.because(EXPORTER_PROPERTY.formatted(loggingExporter) + " is " + exporterLoggingEnabled));
50+
}
51+
}
52+
Boolean globalLoggingEnabled = context.getEnvironment().getProperty(GLOBAL_PROPERTY, Boolean.class);
53+
if (globalLoggingEnabled != null) {
54+
return new ConditionOutcome(globalLoggingEnabled,
55+
ConditionMessage.forCondition(ConditionalOnEnabledLogging.class)
56+
.because(GLOBAL_PROPERTY + " is " + globalLoggingEnabled));
57+
}
58+
return ConditionOutcome.match(ConditionMessage.forCondition(ConditionalOnEnabledLogging.class)
59+
.because("logging is enabled by default"));
60+
}
61+
62+
private static String getExporterName(AnnotatedTypeMetadata metadata) {
63+
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnEnabledLogging.class.getName());
64+
if (attributes == null) {
65+
return null;
66+
}
67+
return (String) attributes.get("value");
68+
}
69+
70+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
2121
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
2222

23+
import org.springframework.boot.actuate.autoconfigure.logging.ConditionalOnEnabledLogging;
2324
import org.springframework.boot.autoconfigure.AutoConfiguration;
2425
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2526
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -36,6 +37,7 @@
3637
@ConditionalOnClass({ SdkLoggerProvider.class, OpenTelemetry.class, OtlpHttpLogRecordExporter.class })
3738
@EnableConfigurationProperties(OtlpLoggingProperties.class)
3839
@Import({ OtlpLoggingConfigurations.ConnectionDetails.class, OtlpLoggingConfigurations.Exporters.class })
40+
@ConditionalOnEnabledLogging("otlp")
3941
public class OtlpLoggingAutoConfiguration {
4042

4143
}

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
@@ -2067,6 +2067,11 @@
20672067
"description": "Whether auto-configuration of Micrometer annotations is enabled.",
20682068
"defaultValue": false
20692069
},
2070+
{
2071+
"name": "management.otlp.logging.export.enabled",
2072+
"type": "java.lang.Boolean",
2073+
"description": "Whether auto-configuration for exporting OTLP log records is enabled."
2074+
},
20702075
{
20712076
"name": "management.otlp.tracing.export.enabled",
20722077
"type": "java.lang.Boolean",
@@ -2193,6 +2198,12 @@
21932198
"level": "error"
21942199
}
21952200
},
2201+
{
2202+
"name": "management.logging.export.enabled",
2203+
"type": "java.lang.Boolean",
2204+
"description": "Whether the auto-configuration for exporting log record data is enabled",
2205+
"defaultValue": true
2206+
},
21962207
{
21972208
"name": "management.tracing.enabled",
21982209
"type": "java.lang.Boolean",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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+
class OnEnabledLoggingConditionTests {
34+
35+
@Test
36+
void shouldMatchIfNoPropertyIsSet() {
37+
OnEnabledLoggingCondition condition = new OnEnabledLoggingCondition();
38+
ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext(), mockMetadata(""));
39+
assertThat(outcome.isMatch()).isTrue();
40+
assertThat(outcome.getMessage()).isEqualTo("@ConditionalOnEnabledLogging logging is enabled by default");
41+
}
42+
43+
@Test
44+
void shouldNotMatchIfGlobalPropertyIsFalse() {
45+
OnEnabledLoggingCondition condition = new OnEnabledLoggingCondition();
46+
ConditionOutcome outcome = condition.getMatchOutcome(
47+
mockConditionContext(Map.of("management.logging.export.enabled", "false")), mockMetadata(""));
48+
assertThat(outcome.isMatch()).isFalse();
49+
assertThat(outcome.getMessage())
50+
.isEqualTo("@ConditionalOnEnabledLogging management.logging.export.enabled is false");
51+
}
52+
53+
@Test
54+
void shouldMatchIfGlobalPropertyIsTrue() {
55+
OnEnabledLoggingCondition condition = new OnEnabledLoggingCondition();
56+
ConditionOutcome outcome = condition.getMatchOutcome(
57+
mockConditionContext(Map.of("management.logging.export.enabled", "true")), mockMetadata(""));
58+
assertThat(outcome.isMatch()).isTrue();
59+
assertThat(outcome.getMessage())
60+
.isEqualTo("@ConditionalOnEnabledLogging management.logging.export.enabled is true");
61+
}
62+
63+
@Test
64+
void shouldNotMatchIfExporterPropertyIsFalse() {
65+
OnEnabledLoggingCondition condition = new OnEnabledLoggingCondition();
66+
ConditionOutcome outcome = condition.getMatchOutcome(
67+
mockConditionContext(Map.of("management.otlp.logging.export.enabled", "false")), mockMetadata("otlp"));
68+
assertThat(outcome.isMatch()).isFalse();
69+
assertThat(outcome.getMessage())
70+
.isEqualTo("@ConditionalOnEnabledLogging management.otlp.logging.export.enabled is false");
71+
}
72+
73+
@Test
74+
void shouldMatchIfExporterPropertyIsTrue() {
75+
OnEnabledLoggingCondition condition = new OnEnabledLoggingCondition();
76+
ConditionOutcome outcome = condition.getMatchOutcome(
77+
mockConditionContext(Map.of("management.otlp.logging.export.enabled", "true")), mockMetadata("otlp"));
78+
assertThat(outcome.isMatch()).isTrue();
79+
assertThat(outcome.getMessage())
80+
.isEqualTo("@ConditionalOnEnabledLogging management.otlp.logging.export.enabled is true");
81+
}
82+
83+
@Test
84+
void exporterPropertyShouldOverrideGlobalPropertyIfTrue() {
85+
OnEnabledLoggingCondition condition = new OnEnabledLoggingCondition();
86+
ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext(
87+
Map.of("management.logging.enabled", "false", "management.otlp.logging.export.enabled", "true")),
88+
mockMetadata("otlp"));
89+
assertThat(outcome.isMatch()).isTrue();
90+
assertThat(outcome.getMessage())
91+
.isEqualTo("@ConditionalOnEnabledLogging management.otlp.logging.export.enabled is true");
92+
}
93+
94+
@Test
95+
void exporterPropertyShouldOverrideGlobalPropertyIfFalse() {
96+
OnEnabledLoggingCondition condition = new OnEnabledLoggingCondition();
97+
ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext(
98+
Map.of("management.logging.enabled", "true", "management.otlp.logging.export.enabled", "false")),
99+
mockMetadata("otlp"));
100+
assertThat(outcome.isMatch()).isFalse();
101+
assertThat(outcome.getMessage())
102+
.isEqualTo("@ConditionalOnEnabledLogging management.otlp.logging.export.enabled is false");
103+
}
104+
105+
private ConditionContext mockConditionContext() {
106+
return mockConditionContext(Collections.emptyMap());
107+
}
108+
109+
private ConditionContext mockConditionContext(Map<String, String> properties) {
110+
ConditionContext context = mock(ConditionContext.class);
111+
MockEnvironment environment = new MockEnvironment();
112+
properties.forEach(environment::setProperty);
113+
given(context.getEnvironment()).willReturn(environment);
114+
return context;
115+
}
116+
117+
private AnnotatedTypeMetadata mockMetadata(String exporter) {
118+
AnnotatedTypeMetadata metadata = mock(AnnotatedTypeMetadata.class);
119+
given(metadata.getAnnotationAttributes(ConditionalOnEnabledLogging.class.getName()))
120+
.willReturn(Map.of("value", exporter));
121+
return metadata;
122+
}
123+
124+
}

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

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

76+
@Test
77+
void shouldBackOffWhenOtlpLoggingExportPropertyIsNotEnabled() {
78+
this.contextRunner.withPropertyValues("management.otlp.logging.export.enabled=false").run((context) -> {
79+
assertThat(context).doesNotHaveBean(OtlpLoggingConnectionDetails.class);
80+
assertThat(context).doesNotHaveBean(LogRecordExporter.class);
81+
});
82+
}
83+
7684
@Test
7785
void shouldBackOffWhenCustomHttpExporterIsDefined() {
7886
this.contextRunner.withUserConfiguration(CustomHttpExporterConfiguration.class)

0 commit comments

Comments
 (0)