From 6f8fd5a4ebc5b3c9a3ca44637d6c16f99e2629c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:02:18 -0800 Subject: [PATCH] ensure jackson overrides are available to static initializers (#16719) (#16757) Moves the application of jackson defaults overrides into pure java, and applies them statically _before_ the `org.logstash.ObjectMappers` has a chance to start initializing object mappers that rely on the defaults. We replace the runner's invocation (which was too late to be fully applied) with a _verification_ that the configured defaults have been applied. (cherry picked from commit 202d07cbbf935b707bc70d21768d0dd76b965bbd) Co-authored-by: Ry Biesemeyer --- logstash-core/lib/logstash/runner.rb | 4 +- logstash-core/lib/logstash/util/jackson.rb | 71 +----- logstash-core/spec/logstash/runner_spec.rb | 4 +- .../spec/logstash/util/jackson_spec.rb | 113 ---------- .../main/java/org/logstash/ObjectMappers.java | 16 ++ .../jackson/StreamReadConstraintsUtil.java | 134 +++++++++++ .../StreamReadConstraintsUtilTest.java | 213 ++++++++++++++++++ .../log/LogstashConfigurationFactoryTest.java | 3 +- .../log/PluginDeprecationLoggerTest.java | 1 + .../log4j2-log-stream-read-constraints.xml | 14 ++ 10 files changed, 388 insertions(+), 185 deletions(-) delete mode 100644 logstash-core/spec/logstash/util/jackson_spec.rb create mode 100644 logstash-core/src/main/java/org/logstash/jackson/StreamReadConstraintsUtil.java create mode 100644 logstash-core/src/test/java/org/logstash/jackson/StreamReadConstraintsUtilTest.java create mode 100644 logstash-core/src/test/resources/log4j2-log-stream-read-constraints.xml diff --git a/logstash-core/lib/logstash/runner.rb b/logstash-core/lib/logstash/runner.rb index 0b2f97b696e..7db7ccb7e90 100644 --- a/logstash-core/lib/logstash/runner.rb +++ b/logstash-core/lib/logstash/runner.rb @@ -353,8 +353,8 @@ def execute # Add local modules to the registry before everything else LogStash::Modules::Util.register_local_modules(LogStash::Environment::LOGSTASH_HOME) - # Set up the Jackson defaults - LogStash::Util::Jackson.set_jackson_defaults(logger) + # Verify the Jackson defaults + LogStash::Util::Jackson.verify_jackson_overrides @dispatcher = LogStash::EventDispatcher.new(self) LogStash::PLUGIN_REGISTRY.hooks.register_emitter(self.class, @dispatcher) diff --git a/logstash-core/lib/logstash/util/jackson.rb b/logstash-core/lib/logstash/util/jackson.rb index 63f072a8173..2755d719607 100644 --- a/logstash-core/lib/logstash/util/jackson.rb +++ b/logstash-core/lib/logstash/util/jackson.rb @@ -18,76 +18,13 @@ module LogStash module Util module Jackson - def self.set_jackson_defaults(logger) - JacksonStreamReadConstraintsDefaults.new(logger).configure - end - - class JacksonStreamReadConstraintsDefaults - - java_import com.fasterxml.jackson.core.StreamReadConstraints - - PROPERTY_MAX_STRING_LENGTH = 'logstash.jackson.stream-read-constraints.max-string-length'.freeze - PROPERTY_MAX_NUMBER_LENGTH = 'logstash.jackson.stream-read-constraints.max-number-length'.freeze - PROPERTY_MAX_NESTING_DEPTH = 'logstash.jackson.stream-read-constraints.max-nesting-depth'.freeze - - def initialize(logger) - @logger = logger - end - - public - - def configure - max_string_len = get_default_value_override!(PROPERTY_MAX_STRING_LENGTH) - max_num_len = get_default_value_override!(PROPERTY_MAX_NUMBER_LENGTH) - max_nesting_depth = get_default_value_override!(PROPERTY_MAX_NESTING_DEPTH) - - if max_string_len || max_num_len || max_nesting_depth - begin - override_default_stream_read_constraints(max_string_len, max_num_len, max_nesting_depth) - rescue java.lang.IllegalArgumentException => e - raise LogStash::ConfigurationError, "Invalid `logstash.jackson.*` system properties configuration: #{e.message}" - end - end - end - - private - def get_default_value_override!(property) - value = get_property_value(property) - return if value.nil? + def self.verify_jackson_overrides + java_import org.logstash.ObjectMappers - begin - int_value = java.lang.Integer.parseInt(value) - - if int_value < 1 - raise LogStash::ConfigurationError, "System property '#{property}' must be bigger than zero. Received: #{int_value}" - end - - @logger.info("Jackson default value override `#{property}` configured to `#{int_value}`") - - int_value - rescue java.lang.NumberFormatException => _e - raise LogStash::ConfigurationError, "System property '#{property}' must be a positive integer value. Received: #{value}" - end - end - - def get_property_value(name) - java.lang.System.getProperty(name) - end - - def override_default_stream_read_constraints(max_string_len, max_num_len, max_nesting_depth) - builder = new_stream_read_constraints_builder - builder.maxStringLength(max_string_len) if max_string_len - builder.maxNumberLength(max_num_len) if max_num_len - builder.maxNestingDepth(max_nesting_depth) if max_nesting_depth - - StreamReadConstraints.overrideDefaultStreamReadConstraints(builder.build) - end - - def new_stream_read_constraints_builder - StreamReadConstraints::builder - end + ObjectMappers::getConfiguredStreamReadConstraints().validateIsGlobalDefault() end + end end end \ No newline at end of file diff --git a/logstash-core/spec/logstash/runner_spec.rb b/logstash-core/spec/logstash/runner_spec.rb index d1b5a4e5f0b..892f2079cb2 100644 --- a/logstash-core/spec/logstash/runner_spec.rb +++ b/logstash-core/spec/logstash/runner_spec.rb @@ -571,8 +571,8 @@ subject { LogStash::Runner.new("") } let(:args) { ["-e", "input {} output {}"] } - it 'should be set' do - expect(LogStash::Util::Jackson).to receive(:set_jackson_defaults) + it 'should be verified' do + expect(LogStash::Util::Jackson).to receive(:verify_jackson_overrides) subject.run(args) end end diff --git a/logstash-core/spec/logstash/util/jackson_spec.rb b/logstash-core/spec/logstash/util/jackson_spec.rb deleted file mode 100644 index afdfb3b34b6..00000000000 --- a/logstash-core/spec/logstash/util/jackson_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -# Licensed to Elasticsearch B.V. under one or more contributor -# license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright -# ownership. Elasticsearch B.V. licenses this file to you 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. - -require 'spec_helper' - -describe LogStash::Util::Jackson do - it 'configures the read constraints defaults' do - read_constraints_defaults = double('read_constraints_defaults') - expect(read_constraints_defaults).to receive(:configure) - - expect(LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults).to receive(:new).and_return(read_constraints_defaults) - - LogStash::Util::Jackson.set_jackson_defaults(double('logger').as_null_object) - end -end - -describe LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults do - let(:logger) { double('logger') } - - subject { described_class.new(logger) } - - shared_examples 'stream read constraint property' do |property| - let(:property) { property } - let(:value) { nil } - let(:builder) { double('builder') } - let(:builder_set_value_method) { expected_builder_set_value_method(property) } - - before(:each) do - allow(logger).to receive(:info) - - allow(builder).to receive(:build).and_return(com.fasterxml.jackson.core.StreamReadConstraints::builder.build) - allow(builder).to receive(builder_set_value_method).with(value.to_i) - - allow(subject).to receive(:new_stream_read_constraints_builder).and_return(builder) - allow(subject).to receive(:get_property_value) do |name| - if name == property - value.to_s - else - nil - end - end - end - - context 'with valid number' do - let(:value) { '10' } - it 'does not raises an error and set value' do - expect { subject.configure }.to_not raise_error - expect(builder).to have_received(builder_set_value_method).with(value.to_i) - end - end - - context 'with non-number value' do - let(:value) { 'foo' } - it 'raises an error and does not set value' do - expect { subject.configure }.to raise_error(LogStash::ConfigurationError, /System property '#{property}' must be a positive integer value. Received: #{value}/) - expect(builder).to_not have_received(builder_set_value_method) - end - end - - context 'with zeroed value' do - let(:value) { '0' } - it 'raises an error and does not set value' do - expect { subject.configure }.to raise_error(LogStash::ConfigurationError, /System property '#{property}' must be bigger than zero. Received: #{value}/) - expect(builder).to_not have_received(builder_set_value_method) - end - end - - context 'with zeroed value' do - let(:value) { '-1' } - it 'raises an error and does not set value' do - expect { subject.configure }.to raise_error(LogStash::ConfigurationError, /System property '#{property}' must be bigger than zero. Received: #{value}/) - expect(builder).to_not have_received(builder_set_value_method) - end - end - - def expected_builder_set_value_method(property) - case property - when LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_STRING_LENGTH - return :maxStringLength - when LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_NUMBER_LENGTH - return :maxNumberLength - when LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_NESTING_DEPTH - return :maxNestingDepth - else - raise 'Invalid system property value' - end - end - end - - [ - LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_STRING_LENGTH, - LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_NUMBER_LENGTH, - LogStash::Util::Jackson::JacksonStreamReadConstraintsDefaults::PROPERTY_MAX_NESTING_DEPTH, - ].each { |system_property| - context "#{system_property}" do - it_behaves_like "stream read constraint property", system_property - end - } -end \ No newline at end of file diff --git a/logstash-core/src/main/java/org/logstash/ObjectMappers.java b/logstash-core/src/main/java/org/logstash/ObjectMappers.java index cdc8943a058..fb5ffa3d3cd 100644 --- a/logstash-core/src/main/java/org/logstash/ObjectMappers.java +++ b/logstash-core/src/main/java/org/logstash/ObjectMappers.java @@ -42,6 +42,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashMap; + import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper; import org.jruby.RubyBignum; import org.jruby.RubyBoolean; @@ -52,12 +53,27 @@ import org.jruby.RubySymbol; import org.jruby.ext.bigdecimal.RubyBigDecimal; import org.logstash.ext.JrubyTimestampExtLibrary; +import org.logstash.jackson.StreamReadConstraintsUtil; import org.logstash.log.RubyBasicObjectSerializer; public final class ObjectMappers { static final String RUBY_SERIALIZERS_MODULE_ID = "RubySerializers"; + static final StreamReadConstraintsUtil CONFIGURED_STREAM_READ_CONSTRAINTS; + + static { + // The StreamReadConstraintsUtil needs to load the configured constraints from system + // properties and apply them _statically_, before any object mappers are initialized. + CONFIGURED_STREAM_READ_CONSTRAINTS = StreamReadConstraintsUtil.fromSystemProperties(); + CONFIGURED_STREAM_READ_CONSTRAINTS.applyAsGlobalDefault(); + } + + public static StreamReadConstraintsUtil getConfiguredStreamReadConstraints() { + return CONFIGURED_STREAM_READ_CONSTRAINTS; + } + + private static final SimpleModule RUBY_SERIALIZERS = new SimpleModule(RUBY_SERIALIZERS_MODULE_ID) .addSerializer(RubyString.class, new RubyStringSerializer()) diff --git a/logstash-core/src/main/java/org/logstash/jackson/StreamReadConstraintsUtil.java b/logstash-core/src/main/java/org/logstash/jackson/StreamReadConstraintsUtil.java new file mode 100644 index 00000000000..d8e5a96de10 --- /dev/null +++ b/logstash-core/src/main/java/org/logstash/jackson/StreamReadConstraintsUtil.java @@ -0,0 +1,134 @@ +package org.logstash.jackson; + +import com.fasterxml.jackson.core.StreamReadConstraints; +import com.google.common.collect.Sets; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class StreamReadConstraintsUtil { + + private final Map propertyOverrides; + private final Logger logger; + + private StreamReadConstraints configuredStreamReadConstraints; + + enum Override { + MAX_STRING_LENGTH(StreamReadConstraints.Builder::maxStringLength, StreamReadConstraints::getMaxStringLength), + MAX_NUMBER_LENGTH(StreamReadConstraints.Builder::maxNumberLength, StreamReadConstraints::getMaxNumberLength), + MAX_NESTING_DEPTH(StreamReadConstraints.Builder::maxNestingDepth, StreamReadConstraints::getMaxNestingDepth), + ; + + static final String PROP_PREFIX = "logstash.jackson.stream-read-constraints."; + + final String propertyName; + private final IntValueApplicator applicator; + private final IntValueObserver observer; + + Override(final IntValueApplicator applicator, + final IntValueObserver observer) { + this.propertyName = PROP_PREFIX + this.name().toLowerCase().replace('_', '-'); + this.applicator = applicator; + this.observer = observer; + } + + @FunctionalInterface + interface IntValueObserver extends Function {} + + @FunctionalInterface + interface IntValueApplicator extends BiFunction {} + } + + /** + * @return an instance configured by {@code System.getProperties()} + */ + public static StreamReadConstraintsUtil fromSystemProperties() { + return new StreamReadConstraintsUtil(System.getProperties()); + } + + StreamReadConstraintsUtil(final Properties properties) { + this(properties, null); + } + + StreamReadConstraintsUtil(final Properties properties, + final Logger logger) { + this(extractProperties(properties), logger); + } + + static private Map extractProperties(final Properties properties) { + return properties.stringPropertyNames().stream() + .filter(propName -> propName.startsWith(Override.PROP_PREFIX)) + .map(propName -> Map.entry(propName, properties.getProperty(propName))) + .filter(entry -> entry.getValue() != null) + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + private StreamReadConstraintsUtil(final Map propertyOverrides, + final Logger logger) { + this.propertyOverrides = Map.copyOf(propertyOverrides); + this.logger = Objects.requireNonNullElseGet(logger, () -> LogManager.getLogger(StreamReadConstraintsUtil.class)); + } + + StreamReadConstraints get() { + if (configuredStreamReadConstraints == null) { + final StreamReadConstraints.Builder builder = StreamReadConstraints.defaults().rebuild(); + + eachOverride((override, value) -> override.applicator.apply(builder, value)); + + this.configuredStreamReadConstraints = builder.build(); + } + return configuredStreamReadConstraints; + } + + public void applyAsGlobalDefault() { + StreamReadConstraints.overrideDefaultStreamReadConstraints(get()); + } + + public void validateIsGlobalDefault() { + validate(StreamReadConstraints.defaults()); + } + + private void validate(final StreamReadConstraints streamReadConstraints) { + final List fatalIssues = new ArrayList<>(); + eachOverride((override, specifiedValue) -> { + final Integer effectiveValue = override.observer.apply(streamReadConstraints); + if (Objects.equals(specifiedValue, effectiveValue)) { + logger.info("Jackson default value override `{}` configured to `{}`", override.propertyName, specifiedValue); + } else { + fatalIssues.add(String.format("`%s` (expected: `%s`, actual: `%s`)", override.propertyName, specifiedValue, effectiveValue)); + } + }); + for (String unsupportedProperty : getUnsupportedProperties()) { + logger.warn("Jackson default value override `{}` is unknown and has been ignored", unsupportedProperty); + } + if (!fatalIssues.isEmpty()) { + throw new IllegalStateException(String.format("Jackson default values not applied: %s", String.join(",", fatalIssues))); + } + } + + void eachOverride(BiConsumer overrideIntegerBiConsumer) { + for (Override override : Override.values()) { + final String propValue = this.propertyOverrides.get(override.propertyName); + if (propValue != null) { + try { + int intValue = Integer.parseInt(propValue); + overrideIntegerBiConsumer.accept(override, intValue); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(String.format("System property `%s` must be positive integer value. Received: `%s`", override.propertyName, propValue), e); + } + } + } + } + + Set getUnsupportedProperties() { + Set supportedProps = Arrays.stream(Override.values()).map(p -> p.propertyName).collect(Collectors.toSet()); + Set providedProps = this.propertyOverrides.keySet(); + + return Sets.difference(providedProps, supportedProps); + } +} diff --git a/logstash-core/src/test/java/org/logstash/jackson/StreamReadConstraintsUtilTest.java b/logstash-core/src/test/java/org/logstash/jackson/StreamReadConstraintsUtilTest.java new file mode 100644 index 00000000000..ac4f1d0f4e4 --- /dev/null +++ b/logstash-core/src/test/java/org/logstash/jackson/StreamReadConstraintsUtilTest.java @@ -0,0 +1,213 @@ +package org.logstash.jackson; + +import com.fasterxml.jackson.core.StreamReadConstraints; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.test.appender.ListAppender; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Properties; +import java.util.function.BiConsumer; +import org.apache.logging.log4j.Level; + +import static org.assertj.core.api.Assertions.*; +import static org.logstash.jackson.StreamReadConstraintsUtil.Override.*; + +public class StreamReadConstraintsUtilTest { + + @ClassRule + public static final LoggerContextRule LOGGER_CONTEXT_RULE = new LoggerContextRule("log4j2-log-stream-read-constraints.xml"); + + private ListAppender listAppender; + private Logger observedLogger; + + @Before + public void setUpLoggingListAppender() { + int i = 1+16; + this.observedLogger = LOGGER_CONTEXT_RULE.getLogger(StreamReadConstraintsUtil.class); + this.listAppender = LOGGER_CONTEXT_RULE.getListAppender("EventListAppender").clear(); + } + + @Test + public void configuresDefaultsByDefault() { + StreamReadConstraintsUtil.fromSystemProperties().validateIsGlobalDefault(); + } + + @Test + public void configuresMaxStringLength() { + final Properties properties = new Properties(); + properties.setProperty(MAX_STRING_LENGTH.propertyName, "10101"); + + fromProperties(properties, (configuredUtil, defaults) -> { + final StreamReadConstraints configuredConstraints = configuredUtil.get(); + + assertThat(configuredConstraints).returns(10101, from(StreamReadConstraints::getMaxStringLength)); + + assertThat(configuredConstraints).as("inherited defaults") + .returns(defaults.getMaxDocumentLength(), from(StreamReadConstraints::getMaxDocumentLength)) + .returns(defaults.getMaxNameLength(), from(StreamReadConstraints::getMaxNameLength)) + .returns(defaults.getMaxNestingDepth(), from(StreamReadConstraints::getMaxNestingDepth)) + .returns(defaults.getMaxNumberLength(), from(StreamReadConstraints::getMaxNumberLength)); + + assertThatThrownBy(configuredUtil::validateIsGlobalDefault).isInstanceOf(IllegalStateException.class).hasMessageContaining(MAX_STRING_LENGTH.propertyName); + + configuredUtil.applyAsGlobalDefault(); + assertThatCode(configuredUtil::validateIsGlobalDefault).doesNotThrowAnyException(); + }); + } + + @Test + public void configuresMaxStringLengthInvalid() { + final Properties properties = new Properties(); + properties.setProperty(MAX_STRING_LENGTH.propertyName, "NaN"); + + fromProperties(properties, (configuredUtil, defaults) -> assertThatThrownBy(configuredUtil::get) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(MAX_STRING_LENGTH.propertyName)); + } + + @Test + public void configuresMaxStringLengthNegative() { + final Properties properties = new Properties(); + properties.setProperty(MAX_STRING_LENGTH.propertyName, "-1000"); + + fromProperties(properties, (configuredUtil, defaults) -> assertThatThrownBy(configuredUtil::get) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(MAX_STRING_LENGTH.propertyName)); + } + + @Test + public void configuresMaxNumberLength() { + final Properties properties = new Properties(); + properties.setProperty(MAX_NUMBER_LENGTH.propertyName, "101"); + + fromProperties(properties, (configuredUtil, defaults) -> { + final StreamReadConstraints configuredConstraints = configuredUtil.get(); + + assertThat(configuredConstraints).returns(101, from(StreamReadConstraints::getMaxNumberLength)); + + assertThat(configuredConstraints).as("inherited defaults") + .returns(defaults.getMaxDocumentLength(), from(StreamReadConstraints::getMaxDocumentLength)) + .returns(defaults.getMaxNameLength(), from(StreamReadConstraints::getMaxNameLength)) + .returns(defaults.getMaxNestingDepth(), from(StreamReadConstraints::getMaxNestingDepth)) + .returns(defaults.getMaxStringLength(), from(StreamReadConstraints::getMaxStringLength)); + + assertThatThrownBy(configuredUtil::validateIsGlobalDefault).isInstanceOf(IllegalStateException.class).hasMessageContaining(MAX_NUMBER_LENGTH.propertyName); + + configuredUtil.applyAsGlobalDefault(); + assertThatCode(configuredUtil::validateIsGlobalDefault).doesNotThrowAnyException(); + }); + } + + @Test + public void configuresMaxNumberLengthInvalid() { + final Properties properties = new Properties(); + properties.setProperty(MAX_NUMBER_LENGTH.propertyName, "NaN"); + + fromProperties(properties, (configuredUtil, defaults) -> assertThatThrownBy(configuredUtil::get) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(MAX_NUMBER_LENGTH.propertyName)); + } + + @Test + public void configuresMaxNumberLengthNegative() { + final Properties properties = new Properties(); + properties.setProperty(MAX_NUMBER_LENGTH.propertyName, "-1000"); + + fromProperties(properties, (configuredUtil, defaults) -> assertThatThrownBy(configuredUtil::get) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(MAX_NUMBER_LENGTH.propertyName)); + } + + @Test + public void configuresMaxNestingDepth() { + final Properties properties = new Properties(); + properties.setProperty(MAX_NESTING_DEPTH.propertyName, "101"); + + fromProperties(properties, (configuredUtil, defaults) -> { + final StreamReadConstraints configuredConstraints = configuredUtil.get(); + + assertThat(configuredConstraints).returns(101, from(StreamReadConstraints::getMaxNestingDepth)); + + assertThat(configuredConstraints).as("inherited defaults") + .returns(defaults.getMaxDocumentLength(), from(StreamReadConstraints::getMaxDocumentLength)) + .returns(defaults.getMaxNameLength(), from(StreamReadConstraints::getMaxNameLength)) + .returns(defaults.getMaxStringLength(), from(StreamReadConstraints::getMaxStringLength)) + .returns(defaults.getMaxNumberLength(), from(StreamReadConstraints::getMaxNumberLength)); + + assertThatThrownBy(configuredUtil::validateIsGlobalDefault).isInstanceOf(IllegalStateException.class).hasMessageContaining(MAX_NESTING_DEPTH.propertyName); + + configuredUtil.applyAsGlobalDefault(); + assertThatCode(configuredUtil::validateIsGlobalDefault).doesNotThrowAnyException(); + }); + } + + @Test + public void configuresMaxNestingDepthInvalid() { + final Properties properties = new Properties(); + properties.setProperty(MAX_NESTING_DEPTH.propertyName, "NaN"); + + fromProperties(properties, (configuredUtil, defaults) -> assertThatThrownBy(configuredUtil::get) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(MAX_NESTING_DEPTH.propertyName)); + } + + @Test + public void configuresMaxNestingDepthNegative() { + final Properties properties = new Properties(); + properties.setProperty(MAX_NESTING_DEPTH.propertyName, "-1000"); + + fromProperties(properties, (configuredUtil, defaults) -> assertThatThrownBy(configuredUtil::get) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(MAX_NESTING_DEPTH.propertyName)); + } + + @Test + public void validatesApplication() { + final Properties properties = new Properties(); + properties.setProperty(PROP_PREFIX + "unsupported-option1", "100"); + properties.setProperty(PROP_PREFIX + "unsupported-option2", "100"); + properties.setProperty(MAX_NESTING_DEPTH.propertyName, "1010"); + properties.setProperty(MAX_STRING_LENGTH.propertyName, "1011"); + properties.setProperty(MAX_NUMBER_LENGTH.propertyName, "1110"); + + System.out.format("START%n"); + + fromProperties(properties, (configuredUtil, defaults) -> { + configuredUtil.applyAsGlobalDefault(); + configuredUtil.validateIsGlobalDefault(); + }); + + System.out.format("OK%n"); + + assertLogObserved(Level.INFO, "override `" + MAX_NESTING_DEPTH.propertyName + "` configured to `1010`"); + assertLogObserved(Level.INFO, "override `" + MAX_STRING_LENGTH.propertyName + "` configured to `1011`"); + assertLogObserved(Level.INFO, "override `" + MAX_NUMBER_LENGTH.propertyName + "` configured to `1110`"); + + assertLogObserved(Level.WARN, "override `" + PROP_PREFIX + "unsupported-option1` is unknown and has been ignored"); + assertLogObserved(Level.WARN, "override `" + PROP_PREFIX + "unsupported-option1` is unknown and has been ignored"); + } + + private void assertLogObserved(final Level level, final String... messageFragments) { + List logEvents = listAppender.getEvents(); + assertThat(logEvents) + .withFailMessage("Expected %s to contain a %s-level log event containing %s", logEvents, level, Arrays.toString(messageFragments)) + .filteredOn(logEvent -> logEvent.getLevel().equals(level)) + .anySatisfy(logEvent -> assertThat(logEvent.getMessage().getFormattedMessage()).contains(messageFragments)); + } + + private synchronized void fromProperties(final Properties properties, BiConsumer defaultsConsumer) { + final StreamReadConstraints defaults = StreamReadConstraints.defaults(); + try { + final StreamReadConstraintsUtil util = new StreamReadConstraintsUtil(properties, this.observedLogger); + defaultsConsumer.accept(util, defaults); + } finally { + StreamReadConstraints.overrideDefaultStreamReadConstraints(defaults); + } + } +} \ No newline at end of file diff --git a/logstash-core/src/test/java/org/logstash/log/LogstashConfigurationFactoryTest.java b/logstash-core/src/test/java/org/logstash/log/LogstashConfigurationFactoryTest.java index 6bb5dedb9ac..3399e314f1c 100644 --- a/logstash-core/src/test/java/org/logstash/log/LogstashConfigurationFactoryTest.java +++ b/logstash-core/src/test/java/org/logstash/log/LogstashConfigurationFactoryTest.java @@ -59,6 +59,7 @@ public static void afterClass() { ThreadContext.putAll(dumpedLog4jThreadContext); snapshotHelper.restoreSnapshot("log4j.configurationFile", "ls.log.format", "ls.logs", LogstashConfigurationFactory.PIPELINE_SEPARATE_LOGS); + forceLog4JContextRefresh(); } @Before @@ -123,7 +124,7 @@ public void testDisableAppenderPerPipelineIsCreatedAfterLogLine() { assertNull("No routing appender should be present", routingApp); } - private void forceLog4JContextRefresh() { + private static void forceLog4JContextRefresh() { LoggerContext context = LoggerContext.getContext(false); context.reconfigure(); } diff --git a/logstash-core/src/test/java/org/logstash/log/PluginDeprecationLoggerTest.java b/logstash-core/src/test/java/org/logstash/log/PluginDeprecationLoggerTest.java index 496e55d4ae0..2a5e3682e4e 100644 --- a/logstash-core/src/test/java/org/logstash/log/PluginDeprecationLoggerTest.java +++ b/logstash-core/src/test/java/org/logstash/log/PluginDeprecationLoggerTest.java @@ -48,6 +48,7 @@ public static void beforeClass() { public static void afterClass() { snapshotHelper.restoreSnapshot("log4j.configurationFile", "ls.log.format", "ls.logs", LogstashConfigurationFactory.PIPELINE_SEPARATE_LOGS); + LogTestUtils.reloadLogConfiguration(); } @Before diff --git a/logstash-core/src/test/resources/log4j2-log-stream-read-constraints.xml b/logstash-core/src/test/resources/log4j2-log-stream-read-constraints.xml new file mode 100644 index 00000000000..581e1327426 --- /dev/null +++ b/logstash-core/src/test/resources/log4j2-log-stream-read-constraints.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file