From a249b164d1cff25756ac70ddb4c82e24781697d9 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 2 Jul 2024 14:06:54 +0200 Subject: [PATCH] Add tests for StructuredLoggingEncoder --- .../log4j2/StructuredLoggingLayout.java | 3 +- .../log4j2/StructuredLoggingLayoutTests.java | 2 +- .../StructuredLoggingEncoderTests.java | 180 ++++++++++++++++++ 3 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/StructuredLoggingEncoderTests.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayout.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayout.java index 7de203360e96..f2fe6a9947e7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayout.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayout.java @@ -138,7 +138,8 @@ else if (ClassUtils.isPresent(format, null)) { } else { throw new IllegalArgumentException( - "Unknown format '%s'. Common formats are: ecs, logstash".formatted(format)); + "Unknown format '%s'. Supported common formats are: ecs, logstash: ecs, logstash" + .formatted(format)); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayoutTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayoutTests.java index b4db356a2d9f..90bc49477c26 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayoutTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayoutTests.java @@ -91,7 +91,7 @@ void shouldCheckTypeArgumentWithRawType() { void shouldFailIfNoCommonOrCustomFormatIsSet() { assertThatIllegalArgumentException() .isThrownBy(() -> StructuredLoggingLayout.newBuilder().setFormat("does-not-exist").build()) - .withMessageContaining("Unknown format 'does-not-exist'. Common formats are: ecs, logstash"); + .withMessageContaining("Unknown format 'does-not-exist'. Supported common formats are: ecs, logstash"); } static final class CustomLog4j2StructuredLoggingFormatter implements StructuredLoggingFormatter { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/StructuredLoggingEncoderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/StructuredLoggingEncoderTests.java new file mode 100644 index 000000000000..3a02a5fb29ae --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/StructuredLoggingEncoderTests.java @@ -0,0 +1,180 @@ +/* + * 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.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; + +import ch.qos.logback.classic.pattern.ThrowableProxyConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.LoggingEvent; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.logging.structured.ApplicationMetadata; +import org.springframework.boot.logging.structured.StructuredLoggingFormatter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link StructuredLoggingEncoder}. + * + * @author Moritz Halbritter + */ +class StructuredLoggingEncoderTests extends AbstractStructuredLoggingTests { + + private StructuredLoggingEncoder encoder; + + @Override + @BeforeEach + void setUp() { + super.setUp(); + this.encoder = new StructuredLoggingEncoder(); + } + + @Override + @AfterEach + void tearDown() { + super.tearDown(); + this.encoder.stop(); + } + + @Test + void shouldSupportEcsCommonFormat() { + this.encoder.setFormat("ecs"); + this.encoder.start(); + LoggingEvent event = createEvent(); + event.setMDCPropertyMap(Collections.emptyMap()); + String json = encode(event); + Map deserialized = deserialize(json); + assertThat(deserialized).containsKey("ecs.version"); + } + + @Test + void shouldSupportLogstashCommonFormat() { + this.encoder.setFormat("logstash"); + this.encoder.start(); + LoggingEvent event = createEvent(); + event.setMDCPropertyMap(Collections.emptyMap()); + String json = encode(event); + Map deserialized = deserialize(json); + assertThat(deserialized).containsKey("@version"); + } + + @Test + void shouldSupportCustomFormat() { + this.encoder.setFormat(CustomLogbackStructuredLoggingFormatter.class.getName()); + this.encoder.start(); + LoggingEvent event = createEvent(); + event.setMDCPropertyMap(Collections.emptyMap()); + String format = encode(event); + assertThat(format).isEqualTo("custom-format"); + } + + @Test + void shouldInjectCustomFormatConstructorParameters() { + this.encoder.setFormat(CustomLogbackStructuredLoggingFormatterWithInjection.class.getName()); + this.encoder.setPid(1L); + this.encoder.start(); + LoggingEvent event = createEvent(); + event.setMDCPropertyMap(Collections.emptyMap()); + String format = encode(event); + assertThat(format).isEqualTo("custom-format-with-injection pid=1 hasThrowableProxyConverter=true"); + } + + @Test + void shouldCheckTypeArgument() { + assertThatIllegalArgumentException().isThrownBy(() -> { + this.encoder.setFormat(CustomLogbackStructuredLoggingFormatterWrongType.class.getName()); + this.encoder.start(); + }).withMessageContaining("must be ch.qos.logback.classic.spi.ILoggingEvent, but was java.lang.String"); + } + + @Test + void shouldCheckTypeArgumentWithRawType() { + assertThatIllegalArgumentException().isThrownBy(() -> { + this.encoder.setFormat(CustomLogbackStructuredLoggingFormatterRawType.class.getName()); + this.encoder.start(); + }).withMessageContaining("must be ch.qos.logback.classic.spi.ILoggingEvent, but was null"); + } + + @Test + void shouldFailIfNoCommonOrCustomFormatIsSet() { + assertThatIllegalArgumentException().isThrownBy(() -> { + this.encoder.setFormat("does-not-exist"); + this.encoder.start(); + }).withMessageContaining("Unknown format 'does-not-exist'. Supported common formats are: ecs, logstash"); + } + + private String encode(LoggingEvent event) { + return new String(this.encoder.encode(event), StandardCharsets.UTF_8); + } + + static final class CustomLogbackStructuredLoggingFormatter implements StructuredLoggingFormatter { + + @Override + public String format(ILoggingEvent event) { + return "custom-format"; + } + + } + + static final class CustomLogbackStructuredLoggingFormatterWithInjection + implements StructuredLoggingFormatter { + + private final ApplicationMetadata metadata; + + private final ThrowableProxyConverter throwableProxyConverter; + + CustomLogbackStructuredLoggingFormatterWithInjection(ApplicationMetadata metadata, + ThrowableProxyConverter throwableProxyConverter) { + this.metadata = metadata; + this.throwableProxyConverter = throwableProxyConverter; + } + + @Override + public String format(ILoggingEvent event) { + boolean hasThrowableProxyConverter = this.throwableProxyConverter != null; + return "custom-format-with-injection pid=" + this.metadata.pid() + " hasThrowableProxyConverter=" + + hasThrowableProxyConverter; + } + + } + + static final class CustomLogbackStructuredLoggingFormatterWrongType implements StructuredLoggingFormatter { + + @Override + public String format(String event) { + return event; + } + + } + + @SuppressWarnings("rawtypes") + static final class CustomLogbackStructuredLoggingFormatterRawType implements StructuredLoggingFormatter { + + @Override + public String format(Object event) { + return ""; + } + + } + +}