diff --git a/src/main/java/net/logstash/logback/composite/AbstractPatternJsonProvider.java b/src/main/java/net/logstash/logback/composite/AbstractPatternJsonProvider.java index d2d89b43..7630b305 100644 --- a/src/main/java/net/logstash/logback/composite/AbstractPatternJsonProvider.java +++ b/src/main/java/net/logstash/logback/composite/AbstractPatternJsonProvider.java @@ -67,28 +67,33 @@ public String getPattern() { public void setPattern(final String pattern) { this.pattern = pattern; - parse(); } @Override public void setJsonFactory(JsonFactory jsonFactory) { this.jsonFactory = Objects.requireNonNull(jsonFactory); - parse(); } + @Override + public void start() { + if (jsonFactory == null) { + throw new IllegalStateException("JsonFactory has not been set"); + } + initializeNodeWriter(); + + super.start(); + } + + /** * Parses the pattern into a {@link NodeWriter}. - * We do this when the properties are set instead of on {@link #start()}, - * because {@link #start()} is called by logstash's xml parser - * before the Formatter has had an opportunity to set the jsonFactory. */ - private void parse() { - if (pattern != null && jsonFactory != null) { - AbstractJsonPatternParser parser = createParser(this.jsonFactory); - parser.setOmitEmptyFields(omitEmptyFields); - nodeWriter = parser.parse(pattern); - } + private void initializeNodeWriter() { + AbstractJsonPatternParser parser = createParser(this.jsonFactory); + parser.setOmitEmptyFields(omitEmptyFields); + this.nodeWriter = parser.parse(pattern); } + /** * When {@code true}, fields whose values are considered empty ({@link AbstractJsonPatternParser#isEmptyValue(Object)}}) diff --git a/src/main/java/net/logstash/logback/composite/GlobalCustomFieldsJsonProvider.java b/src/main/java/net/logstash/logback/composite/GlobalCustomFieldsJsonProvider.java index 4cb9b42a..beb48b36 100644 --- a/src/main/java/net/logstash/logback/composite/GlobalCustomFieldsJsonProvider.java +++ b/src/main/java/net/logstash/logback/composite/GlobalCustomFieldsJsonProvider.java @@ -18,12 +18,13 @@ import java.io.IOException; import java.util.Iterator; import java.util.Map.Entry; +import java.util.Objects; import ch.qos.logback.core.spi.DeferredProcessingAware; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; public class GlobalCustomFieldsJsonProvider extends AbstractJsonProvider implements JsonFactoryAware { @@ -36,8 +37,12 @@ public class GlobalCustomFieldsJsonProviderThe provider is started even when it fails to parse the {@link #customFields} JSON string. + * An ERROR status is emitted instead and no exception is thrown. + */ @Override public void start() { initializeCustomFields(); @@ -65,39 +76,77 @@ public void start() { } private void initializeCustomFields() { - if (this.customFields != null && jsonFactory != null) { - try (JsonParser parser = this.jsonFactory.createParser(customFields)) { - this.customFieldsNode = parser.readValueAsTree(); - } catch (IOException e) { - addError("Failed to parse custom fields [" + customFields + "]", e); - } + if (customFieldsNode != null || customFields == null) { + return; + } + if (jsonFactory == null) { + throw new IllegalStateException("JsonFactory has not been set"); + } + + try { + this.customFieldsNode = JsonReadingUtils.readFullyAsObjectNode(this.jsonFactory, this.customFields); + } catch (IOException e) { + addError("[customFields] is not a valid JSON object", e); } } + /** + * Set the custom fields as a JSON string. + * The string will be parsed when the provider is {@link #start()}. + * + * @param customFields the custom fields as JSON string. + */ public void setCustomFields(String customFields) { - this.customFields = customFields; if (isStarted()) { - initializeCustomFields(); + throw new IllegalStateException("Configuration cannot be changed while the provider is started"); } + + this.customFields = customFields; + this.customFieldsNode = null; } public String getCustomFields() { return customFields; } - public JsonNode getCustomFieldsNode() { + public ObjectNode getCustomFieldsNode() { return this.customFieldsNode; } - + + /** + * Set the custom JSON fields. + * Must be a valid JsonNode that maps to a JSON object structure, i.e. an {@link ObjectNode}. + * + * @param customFields a {@link JsonNode} whose properties must be added as custom fields. + * @deprecated use {@link #setCustomFieldsNode(ObjectNode)} instead. + * @throws IllegalArgumentException if the argument is not a {@link ObjectNode}. + */ + @Deprecated public void setCustomFieldsNode(JsonNode customFields) { - this.customFieldsNode = customFields; - if (this.customFieldsNode != null && customFields == null) { - this.customFields = this.customFieldsNode.toString(); + if (customFields != null && !(customFields instanceof ObjectNode)) { + throw new IllegalArgumentException("Must be an ObjectNode"); + } + setCustomFieldsNode((ObjectNode) customFields); + } + + + /** + * Use the fields of the given {@link ObjectNode} (may be empty). + * + * @param customFields the JSON object whose fields as added as custom fields + */ + public void setCustomFieldsNode(ObjectNode customFields) { + if (isStarted()) { + throw new IllegalStateException("Configuration cannot be changed while the provider is started"); } + + this.customFieldsNode = customFields; + this.customFields = null; } + @Override public void setJsonFactory(JsonFactory jsonFactory) { - this.jsonFactory = jsonFactory; + this.jsonFactory = Objects.requireNonNull(jsonFactory); } } diff --git a/src/main/java/net/logstash/logback/composite/JsonProvider.java b/src/main/java/net/logstash/logback/composite/JsonProvider.java index b0b507ff..4c04d8d7 100644 --- a/src/main/java/net/logstash/logback/composite/JsonProvider.java +++ b/src/main/java/net/logstash/logback/composite/JsonProvider.java @@ -21,7 +21,6 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.spi.ContextAware; import ch.qos.logback.core.spi.DeferredProcessingAware; -import ch.qos.logback.core.spi.LifeCycle; import com.fasterxml.jackson.core.JsonGenerator; /** @@ -29,7 +28,7 @@ * * @param type of event ({@link ILoggingEvent} or {@link IAccessEvent}). */ -public interface JsonProvider extends LifeCycle, ContextAware { +public interface JsonProvider extends ContextAware { /** * Writes information about the event, to the given generator. @@ -53,4 +52,19 @@ public interface JsonProvider extends Lif */ void prepareForDeferredProcessing(Event event); + /** + * Start the provider after all configuration properties are set. + */ + void start(); + + /** + * Stop the provider + */ + void stop(); + + /** + * Report whether the provider is started or not. + * @return {@code true} if the provider is started, {@code false} otherwise. + */ + boolean isStarted(); } diff --git a/src/main/java/net/logstash/logback/composite/JsonReadingUtils.java b/src/main/java/net/logstash/logback/composite/JsonReadingUtils.java new file mode 100644 index 00000000..556155bf --- /dev/null +++ b/src/main/java/net/logstash/logback/composite/JsonReadingUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright 2013-2021 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 + * + * http://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 net.logstash.logback.composite; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Utilities for reading/parsing JSON string. + * + *

Note: This class is for internal use only and subject to backward incompatible change + * at any time. + * + * @author brenuart + */ +public class JsonReadingUtils { + + private JsonReadingUtils() { + // utility class - prevent instantiation + } + + + /** + * Fully read the supplied JSON string into the equivalent {@link JsonNode}. + * + * Throws a {@link JsonParseException} if the string is not fully read after a first valid JsonNode is found. + * This may happen for input like 10 foobar that would otherwise return a NumericNode with value + * {@code 10} leaving foobar unread. + * + * @param jsonFactory the {@link JsonFactory} from which to obtain a {@link JsonParser} to read the JSON string. + * @param json the JSON string to read + * @return the {@link JsonNode} corresponding to the input string or {@code null} if the string is null or empty. + * @throws IOException if there is either an underlying I/O problem or decoding issue + */ + public static JsonNode readFully(JsonFactory jsonFactory, String json) throws IOException { + if (json == null) { + return null; + } + + final String trimmedJson = json.trim(); + try (JsonParser parser = jsonFactory.createParser(trimmedJson)) { + final JsonNode tree = parser.readValueAsTree(); + + if (parser.getCurrentLocation().getCharOffset() < trimmedJson.length()) { + /* + * If the full trimmed string was not read, then the full trimmed string contains a json value plus other text. + * For example, trimmedValue = '10 foobar', or 'true foobar', or '{"foo","bar"} baz'. + * In these cases readTree will only read the first part, and will not read the remaining text. + */ + throw new JsonParseException(parser, "unexpected character"); + } + + return tree; + } + } + + + /** + * Fully read a JSON string into an {@link ObjectNode}, throwing a {@link JsonParseException} if the supplied string + * is not a valid JSON object representation. + * + * @param jsonFactory the {@link JsonFactory} from which to obtain a {@link JsonParser} to read the JSON string. + * @param json the JSON string to read + * @return the {@link JsonNode} corresponding to the input string or {@code null} if the string is null or empty. + * @throws IOException if there is either an underlying I/O problem or decoding issue + * + * @see JsonReadingUtils#readFully(JsonFactory, String) + */ + public static ObjectNode readFullyAsObjectNode(JsonFactory jsonFactory, String json) throws IOException { + final JsonNode node = readFully(jsonFactory, json); + + if (node != null && !(node instanceof ObjectNode)) { + throw new JsonParseException(null, "expected a JSON object representation"); + } + + return (ObjectNode) node; } +} diff --git a/src/main/java/net/logstash/logback/composite/JsonWritingUtils.java b/src/main/java/net/logstash/logback/composite/JsonWritingUtils.java index 7a0f9c86..3e261587 100644 --- a/src/main/java/net/logstash/logback/composite/JsonWritingUtils.java +++ b/src/main/java/net/logstash/logback/composite/JsonWritingUtils.java @@ -23,6 +23,12 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonMappingException; +/** + * Utilities for writing JSON + * + *

Note: This class is for internal use only and subject to backward incompatible change + * at any time. + */ public class JsonWritingUtils { /** diff --git a/src/main/java/net/logstash/logback/pattern/AbstractJsonPatternParser.java b/src/main/java/net/logstash/logback/pattern/AbstractJsonPatternParser.java index 6e796ba6..6047313e 100644 --- a/src/main/java/net/logstash/logback/pattern/AbstractJsonPatternParser.java +++ b/src/main/java/net/logstash/logback/pattern/AbstractJsonPatternParser.java @@ -25,6 +25,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import net.logstash.logback.composite.JsonReadingUtils; +import net.logstash.logback.util.StringUtils; + import ch.qos.logback.core.pattern.PatternLayoutBase; import ch.qos.logback.core.spi.ContextAware; import com.fasterxml.jackson.core.JsonFactory; @@ -34,6 +37,7 @@ import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeType; +import com.fasterxml.jackson.databind.node.ObjectNode; /** * Parser that takes a JSON pattern, resolves all the conversion specifiers and returns an instance @@ -165,7 +169,7 @@ protected abstract static class AbstractAsObjectTransformer implements @Override public T getValue(final Event event) { final String value = generator.getValue(event); - if (value == null || value.isEmpty()) { + if (StringUtils.isEmpty(value)) { return null; } try { @@ -189,7 +193,7 @@ protected abstract static class AbstractAsNumberTransformer generator) { } protected JsonNode transform(final String value) throws IOException { - try (JsonParser jsonParser = jsonFactory.createParser(value)) { - return jsonParser.readValueAsTree(); - } + return JsonReadingUtils.readFully(jsonFactory, value); } } @@ -242,7 +244,7 @@ public TryJsonValueTransformer(final ValueGetter generator) { } protected Object transform(final String value) throws IOException { - final String trimmedValue = value.trim(); + final String trimmedValue = StringUtils.trimToEmpty(value); try (JsonParser parser = jsonFactory.createParser(trimmedValue)) { final TreeNode tree = parser.readValueAsTree(); @@ -520,27 +522,15 @@ private ChildrenWriter parseChildren(JsonNode node) { public NodeWriter parse(String pattern) { - - if (pattern == null) { - contextAware.addError("No pattern specified"); + if (StringUtils.isEmpty(pattern)) { return null; } - final JsonNode node; + final ObjectNode node; try (JsonParser jsonParser = jsonFactory.createParser(pattern)) { - node = jsonParser.readValueAsTree(); + node = JsonReadingUtils.readFullyAsObjectNode(jsonFactory, pattern); } catch (IOException e) { - contextAware.addError("Failed to parse pattern '" + pattern + "'", e); - return null; - } - - if (node == null) { - contextAware.addError("Empty JSON pattern"); - return null; - } - - if (!node.isObject()) { - contextAware.addError("Invalid pattern JSON - must be an object"); + contextAware.addError("[pattern] is not a valid JSON object", e); return null; } diff --git a/src/main/java/net/logstash/logback/util/StringUtils.java b/src/main/java/net/logstash/logback/util/StringUtils.java new file mode 100644 index 00000000..5d399549 --- /dev/null +++ b/src/main/java/net/logstash/logback/util/StringUtils.java @@ -0,0 +1,166 @@ +/* + * Copyright 2013-2021 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 + * + * http://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 net.logstash.logback.util; + +import static ch.qos.logback.core.CoreConstants.EMPTY_STRING; + +/** + * Operations on {@link java.lang.String} that are + * {@code null} safe. + * + *

Largely inspired by Apache Commons Lang3. + * + *

Note: This class is for internal use only and subject to backward incompatible change + * at any time. + * + * @author brenuart + */ +public class StringUtils { + + private StringUtils() { + // utility class - no instantiation + } + + /** + *

Removes control characters (char <= 32) from both + * ends of this String, handling {@code null} by returning + * {@code null}.

+ * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32.

+ * + *
+     * StringUtils.trim(null)          = null
+     * StringUtils.trim("")            = ""
+     * StringUtils.trim("     ")       = ""
+     * StringUtils.trim("abc")         = "abc"
+     * StringUtils.trim("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed string, {@code null} if null String input + */ + public static String trim(final String str) { + return str == null ? null : str.trim(); + } + + /** + *

Removes control characters (char <= 32) from both + * ends of this String returning an empty String ("") if the String + * is empty ("") after the trim or if it is {@code null}. + * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32.

+ * + *
+     * StringUtils.trimToEmpty(null)          = ""
+     * StringUtils.trimToEmpty("")            = ""
+     * StringUtils.trimToEmpty("     ")       = ""
+     * StringUtils.trimToEmpty("abc")         = "abc"
+     * StringUtils.trimToEmpty("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed String, or an empty String if {@code null} input + */ + public static String trimToEmpty(final String str) { + return str == null ? EMPTY_STRING : str.trim(); + } + + /** + *

Removes control characters (char <= 32) from both + * ends of this String returning {@code null} if the String is + * empty ("") after the trim or if it is {@code null}. + * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32.

+ * + *
+     * StringUtils.trimToNull(null)          = null
+     * StringUtils.trimToNull("")            = null
+     * StringUtils.trimToNull("     ")       = null
+     * StringUtils.trimToNull("abc")         = "abc"
+     * StringUtils.trimToNull("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed String, {@code null} if only chars <= 32, empty + * or null String input + */ + public static String trimToNull(final String str) { + final String ts = trim(str); + return isEmpty(ts) ? null : ts; + } + + /** + *

Checks if a CharSequence is empty ("") or null.

+ * + *
+     * StringUtils.isEmpty(null)      = true
+     * StringUtils.isEmpty("")        = true
+     * StringUtils.isEmpty(" ")       = false
+     * StringUtils.isEmpty("bob")     = false
+     * StringUtils.isEmpty("  bob  ") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is empty or null + */ + public static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } + + /** + *

Checks if a CharSequence is empty (""), null or whitespace only.

+ * + *

Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.isBlank(null)      = true
+     * StringUtils.isBlank("")        = true
+     * StringUtils.isBlank(" ")       = true
+     * StringUtils.isBlank("bob")     = false
+     * StringUtils.isBlank("  bob  ") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is null, empty or whitespace only + */ + public static boolean isBlank(final CharSequence cs) { + final int strLen = length(cs); + if (strLen == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(cs.charAt(i))) { + return false; + } + } + return true; + } + + /** + * Gets a CharSequence length or {@code 0} if the CharSequence is + * {@code null}. + * + * @param cs a CharSequence or {@code null} + * @return CharSequence length or {@code 0} if the CharSequence is + * {@code null}. + */ + public static int length(final CharSequence cs) { + return cs == null ? 0 : cs.length(); + } +} diff --git a/src/test/java/net/logstash/logback/composite/GlobalCustomFieldsJsonProviderTest.java b/src/test/java/net/logstash/logback/composite/GlobalCustomFieldsJsonProviderTest.java index 636ae24b..a5061a5a 100644 --- a/src/test/java/net/logstash/logback/composite/GlobalCustomFieldsJsonProviderTest.java +++ b/src/test/java/net/logstash/logback/composite/GlobalCustomFieldsJsonProviderTest.java @@ -16,56 +16,95 @@ package net.logstash.logback.composite; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import java.io.IOException; import java.io.StringWriter; +import net.logstash.logback.test.AbstractLogbackTest; + import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.status.Status; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.MappingJsonFactory; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.NumericNode; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -@ExtendWith(MockitoExtension.class) -public class GlobalCustomFieldsJsonProviderTest { +public class GlobalCustomFieldsJsonProviderTest extends AbstractLogbackTest { + @InjectMocks private GlobalCustomFieldsJsonProvider provider = new GlobalCustomFieldsJsonProvider(); @Mock private ILoggingEvent event; - private JsonFactory factory = new MappingJsonFactory(); - private StringWriter writer = new StringWriter(); - private JsonGenerator generator; + @BeforeEach - public void setup() throws IOException { - provider.setCustomFields("{\"name\":\"value\"}"); + public void setup() throws Exception { + super.setup(); + + JsonFactory factory = new MappingJsonFactory(); provider.setJsonFactory(factory); - provider.start(); - generator = factory.createGenerator(writer); } + @AfterEach + public void tearDown() { + provider.stop(); + super.tearDown(); + } + @Test public void test() throws IOException { + provider.setCustomFields("{\"name\":\"value\"}"); + provider.start(); generator.writeStartObject(); - provider.writeTo(generator, event); - generator.writeEndObject(); - generator.flush(); - assertThat(writer.toString()).isEqualTo("{\"name\":\"value\"}"); + assertThat(writer).hasToString("{\"name\":\"value\"}"); } + @Test + public void invalidCustomFields_notAnObject() { + provider.setCustomFields("\"aNonObjectValue\""); + provider.start(); + + assertThat(statusManager.getCopyOfStatusList()) + .hasSize(1) + .allMatch(s -> s.getLevel() == Status.ERROR && s.getOrigin() == provider); + assertThat(provider.isStarted()) + .isTrue(); + } + + @Test + public void invalidCustomFields_trailingChars() { + provider.setCustomFields("{} trailingChars"); + provider.start(); + + assertThat(statusManager.getCopyOfStatusList()) + .hasSize(1) + .allMatch(s -> s.getLevel() == Status.ERROR && s.getOrigin() == provider); + assertThat(provider.isStarted()) + .isTrue(); + } + + @Test + @SuppressWarnings("deprecation") + public void customFieldsNodeIsNotAnObject() { + NumericNode node = JsonNodeFactory.instance.numberNode(0); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> provider.setCustomFieldsNode(node)); + } } diff --git a/src/test/java/net/logstash/logback/pattern/AbstractJsonPatternParserTest.java b/src/test/java/net/logstash/logback/pattern/AbstractJsonPatternParserTest.java index d5e53e45..e2665055 100644 --- a/src/test/java/net/logstash/logback/pattern/AbstractJsonPatternParserTest.java +++ b/src/test/java/net/logstash/logback/pattern/AbstractJsonPatternParserTest.java @@ -218,7 +218,8 @@ public void asJsonShouldTransformTextValueToJson() throws IOException { + " \"key6\": \"#asJson{[1, \\\"2\\\"]}\",\n" + " \"key7\": \"#asJson{{\\\"field\\\":\\\"value\\\"}}\",\n" + " \"key8\": \"#asJson{{\\\"field\\\":\\\"value\\\",\\\"num\\\":123}}\",\n" - + " \"key9\": \"#asJson{one two three}\"\n" + + " \"key9\": \"#asJson{one two three}\",\n" + + " \"key10\": \"#asJson{1 suffix}\"\n" + "}"; String expected = "" @@ -231,7 +232,8 @@ public void asJsonShouldTransformTextValueToJson() throws IOException { + " \"key6\": [1, \"2\"],\n" + " \"key7\": {\"field\":\"value\"},\n" + " \"key8\": {\"field\":\"value\", \"num\":123},\n" - + " \"key9\": null\n" + + " \"key9\": null,\n" + + " \"key10\": null\n" + "}"; verifyFields(pattern, expected); diff --git a/src/test/java/net/logstash/logback/test/AbstractLogbackTest.java b/src/test/java/net/logstash/logback/test/AbstractLogbackTest.java new file mode 100644 index 00000000..e6761c83 --- /dev/null +++ b/src/test/java/net/logstash/logback/test/AbstractLogbackTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013-2021 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 + * + * http://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 net.logstash.logback.test; + +import static org.mockito.Mockito.spy; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.core.BasicStatusManager; +import ch.qos.logback.core.status.OnConsoleStatusListener; +import ch.qos.logback.core.status.StatusManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * @author brenuart + * + */ +@ExtendWith(MockitoExtension.class) +public abstract class AbstractLogbackTest { + + protected LoggerContext context = spy(new LoggerContext()); + + protected StatusManager statusManager = new BasicStatusManager(); + + + @BeforeEach + public void setup() throws Exception { + // Output statuses on the console for easy debugging. Must be initialized early to capture + // warnings emitted by setter/getter methods before the appender is started. + OnConsoleStatusListener consoleListener = new OnConsoleStatusListener(); + consoleListener.start(); + statusManager.add(consoleListener); + context.setStatusManager(statusManager); + + context.start(); + } + + @AfterEach + public void tearDown() { + context.stop(); + } + +}