This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch fix/property-environment in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 6f4050761010e9c727e29272ff4ea3266bb51125 Author: Piotr P. Karwasz <[email protected]> AuthorDate: Wed Feb 21 13:07:11 2024 +0100 Add `PropertyEnvironment` to `log4j-sdk` This PR creates a simplified version of `PropertyEnvironment` in `log4j-sdk` that supports Spring-like property classes. It provides aggregation features to allow the conversion of multiple properties at once in a typesafe way. --- log4j-sdk/pom.xml | 6 + .../sdk/env/ConfigurablePropertyEnvironment.java | 35 +++ .../logging/log4j/sdk/env/Log4jProperty.java | 51 +++++ .../logging/log4j/sdk/env/PropertyEnvironment.java | 200 ++++++++++++++++ .../logging/log4j/sdk/env/PropertySource.java | 48 ++++ .../internal/ContextEnvironmentPropertySource.java | 67 ++++++ .../internal/ContextPropertiesPropertySource.java | 66 ++++++ .../PropertiesUtilPropertyEnvironment.java | 48 ++++ .../log4j/sdk/env/internal/package-info.java | 24 ++ .../apache/logging/log4j/sdk/env/package-info.java | 24 ++ .../sdk/env/support/BasicPropertyEnvironment.java | 254 +++++++++++++++++++++ .../support/ClassloaderPropertyEnvironment.java | 37 +++ .../support/PropertySourcePropertyEnvironment.java | 94 ++++++++ .../log4j/sdk/env/support/package-info.java | 24 ++ .../logging/log4j/sdk/logger/AbstractLogger.java | 2 + .../env/support/BasicPropertyEnvironmentTest.java | 157 +++++++++++++ .../log4j/sdk/logger/AbstractLoggerTest.java | 2 +- .../logging/log4j/sdk/logger/TestListLogger.java | 73 ++++++ 18 files changed, 1211 insertions(+), 1 deletion(-) diff --git a/log4j-sdk/pom.xml b/log4j-sdk/pom.xml index 595f381ff1..9451799cc7 100644 --- a/log4j-sdk/pom.xml +++ b/log4j-sdk/pom.xml @@ -60,6 +60,12 @@ <scope>test</scope> </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/ConfigurablePropertyEnvironment.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/ConfigurablePropertyEnvironment.java new file mode 100644 index 0000000000..0d5f0f5fb9 --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/ConfigurablePropertyEnvironment.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env; + +/** + * Provides methods to modify the set of {@link PropertySource}s used by a {@link PropertyEnvironment}. + */ +public interface ConfigurablePropertyEnvironment extends PropertyEnvironment { + + /** + * Adds a property source to the environment. + * @param source A property source. + */ + void addPropertySource(PropertySource source); + + /** + * Removes a property source from the environment. + * @param source A property source. + */ + void removePropertySource(PropertySource source); +} diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/Log4jProperty.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/Log4jProperty.java new file mode 100644 index 0000000000..081980ee7a --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/Log4jProperty.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotates a class or parameter that stores Log4j API configuration properties. + * <p> + * This annotation is required for root property classes. + * </p> + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD}) +public @interface Log4jProperty { + + /** + * Provides a name for the configuration property. + * <p> + * + * </p> + */ + String name() default ""; + + /** + * Provides the default value of the property. + * <p> + * This only applies to scalar values. + * </p> + */ + String defaultValue() default ""; +} diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/PropertyEnvironment.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/PropertyEnvironment.java new file mode 100644 index 0000000000..e153887c31 --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/PropertyEnvironment.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env; + +import java.nio.charset.Charset; +import java.time.Duration; +import org.apache.logging.log4j.sdk.env.internal.PropertiesUtilPropertyEnvironment; +import org.jspecify.annotations.Nullable; + +/** + * Represents the main access point to Log4j properties. + * <p> + * It provides as typesafe way to access properties stored in multiple {@link PropertySource}s, type conversion + * methods and property aggregation methods (cf. {@link #getProperty(Class)}). + * </p> + */ +public interface PropertyEnvironment { + + static PropertyEnvironment getGlobal() { + return PropertiesUtilPropertyEnvironment.INSTANCE; + } + + /** + * Gets the named property as a boolean value. If the property matches the string {@code "true"} (case-insensitive), + * then it is returned as the boolean value {@code true}. Any other non-{@code null} text in the property is + * considered {@code false}. + * + * @param name the name of the property to look up + * @return the boolean value of the property or {@code false} if undefined. + */ + default boolean getBooleanProperty(final String name) { + return getBooleanProperty(name, false); + } + + /** + * Gets the named property as a boolean value. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the boolean value of the property or {@code defaultValue} if undefined. + */ + @Nullable + Boolean getBooleanProperty(String name, @Nullable Boolean defaultValue); + + /** + * Gets the named property as a Charset value. + * + * @param name the name of the property to look up + * @return the Charset value of the property or {@link Charset#defaultCharset()} if undefined. + */ + default Charset getCharsetProperty(final String name) { + return getCharsetProperty(name, Charset.defaultCharset()); + } + + /** + * Gets the named property as a Charset value. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the Charset value of the property or {@code defaultValue} if undefined. + */ + @Nullable + Charset getCharsetProperty(String name, @Nullable Charset defaultValue); + + /** + * Gets the named property as a Class value. + * + * @param name the name of the property to look up + * @return the Class value of the property or {@code null} if it can not be loaded. + */ + default @Nullable Class<?> getClassProperty(final String name) { + return getClassProperty(name, null); + } + + /** + * Gets the named property as a Class value. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the Class value of the property or {@code defaultValue} if it can not be loaded. + */ + default @Nullable Class<?> getClassProperty(final String name, final @Nullable Class<?> defaultValue) { + return getClassProperty(name, defaultValue, Object.class); + } + + /** + * Gets the named property as a subclass of {@code upperBound}. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the Class value of the property or {@code defaultValue} if it can not be loaded. + */ + <T> @Nullable Class<? extends T> getClassProperty( + String name, @Nullable Class<? extends T> defaultValue, Class<T> upperBound); + + /** + * Gets the named property as {@link Duration}. + * + * @param name The property name. + * @return The value of the String as a Duration or {@link Duration#ZERO} if it was undefined or could not be parsed. + */ + default Duration getDurationProperty(final String name) { + return getDurationProperty(name, Duration.ZERO); + } + + /** + * Gets the named property as {@link Duration}. + * + * @param name The property name. + * @param defaultValue The default value. + * @return The value of the String as a Duration or {@code defaultValue} if it was undefined or could not be parsed. + */ + Duration getDurationProperty(String name, Duration defaultValue); + + /** + * Gets the named property as an integer. + * + * @param name the name of the property to look up + * @return the parsed integer value of the property or {@code 0} if it was undefined or could not be + * parsed. + */ + default int getIntegerProperty(final String name) { + return getIntegerProperty(name, 0); + } + + /** + * Gets the named property as an integer. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the parsed integer value of the property or {@code defaultValue} if it was undefined or could not be + * parsed. + */ + Integer getIntegerProperty(String name, Integer defaultValue); + + /** + * Gets the named property as a long. + * + * @param name the name of the property to look up + * @return the parsed long value of the property or {@code 0} if it was undefined or could not be + * parsed. + */ + default long getLongProperty(final String name) { + return getLongProperty(name, 0L); + } + + /** + * Gets the named property as a long. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the parsed long value of the property or {@code defaultValue} if it was undefined or could not be parsed. + */ + Long getLongProperty(String name, Long defaultValue); + + /** + * Gets the named property as a String. + * + * @param name the name of the property to look up + * @return the String value of the property or {@code null} if undefined. + */ + @Nullable + String getStringProperty(String name); + + /** + * Gets the named property as a String. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the String value of the property or {@code defaultValue} if undefined. + */ + default String getStringProperty(final String name, final String defaultValue) { + final String prop = getStringProperty(name); + return (prop == null) ? defaultValue : prop; + } + + /** + * Binds properties to class {@code T}. + * <p> + * The implementation should at least support binding Java records with a single public constructor and enums. + * </p> + * @param propertyClass a class annotated by {@link Log4jProperty}. + * @return an instance of T with all JavaBean properties bound. + */ + <T> T getProperty(final Class<T> propertyClass); +} diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/PropertySource.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/PropertySource.java new file mode 100644 index 0000000000..0318364db3 --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/PropertySource.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env; + +import org.jspecify.annotations.Nullable; + +/** + * Basic interface to retrieve property values. + * <p> + * We can not reuse the property sources from 2.x, since those required some sort of {@code log4j} prefix to be + * included. In 3.x we want to use keys without a prefix. + * </p> + */ +public interface PropertySource { + /** + * Provides the priority of the property source. + * <p> + * Property sources are ordered according to the natural ordering of their priority. Sources with lower + * numerical value take precedence over those with higher numerical value. + * </p> + * + * @return priority value + */ + int getPriority(); + + /** + * Gets the named property as a String. + * + * @param name the name of the property to look up + * @return the String value of the property or {@code null} if undefined. + */ + @Nullable + String getProperty(String name); +} diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/ContextEnvironmentPropertySource.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/ContextEnvironmentPropertySource.java new file mode 100644 index 0000000000..5761d48b85 --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/ContextEnvironmentPropertySource.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env.internal; + +import java.util.Locale; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.PropertySource; +import org.jspecify.annotations.Nullable; + +/** + * PropertySource backed by the current environment variables. + * <p> + * Should haves a slightly lower priority than global environment variables. + * </p> + */ +public class ContextEnvironmentPropertySource implements PropertySource { + + private static final int DEFAULT_PRIORITY = 100; + + private final String prefix; + private final int priority; + + public ContextEnvironmentPropertySource(final String contextName, final int priorityOffset) { + this.prefix = "log4j2." + contextName + "."; + this.priority = DEFAULT_PRIORITY + priorityOffset; + } + + @Override + public int getPriority() { + return priority; + } + + @Override + public @Nullable String getProperty(final String key) { + final String actualKey = key.replace('.', '_').toUpperCase(Locale.ROOT); + try { + return System.getenv(prefix + actualKey); + } catch (final SecurityException e) { + StatusLogger.getLogger() + .warn( + "{} lacks permissions to access system property {}.", + getClass().getName(), + actualKey, + e); + } + return null; + } + + @Override + public boolean containsProperty(final String key) { + return getProperty(key) != null; + } +} diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/ContextPropertiesPropertySource.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/ContextPropertiesPropertySource.java new file mode 100644 index 0000000000..21447c02f0 --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/ContextPropertiesPropertySource.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env.internal; + +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.PropertySource; +import org.jspecify.annotations.Nullable; + +/** + * PropertySource backed by the current system properties. + * <p> + * Should have a slightly lower priority than global system properties. + * </p> + */ +public class ContextPropertiesPropertySource implements PropertySource { + + private static final int DEFAULT_PRIORITY = 0; + + private final String prefix; + private final int priority; + + public ContextPropertiesPropertySource(final String contextName, final int priorityOffset) { + this.prefix = "log4j2." + contextName + "."; + this.priority = DEFAULT_PRIORITY + priorityOffset; + } + + @Override + public int getPriority() { + return priority; + } + + @Override + public @Nullable String getProperty(final String key) { + final String actualKey = prefix + key; + try { + return System.getProperty(actualKey); + } catch (final SecurityException e) { + StatusLogger.getLogger() + .warn( + "{} lacks permissions to access system property {}.", + getClass().getName(), + actualKey, + e); + } + return null; + } + + @Override + public boolean containsProperty(final String key) { + return getProperty(key) != null; + } +} diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/PropertiesUtilPropertyEnvironment.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/PropertiesUtilPropertyEnvironment.java new file mode 100644 index 0000000000..28f9836720 --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/PropertiesUtilPropertyEnvironment.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env.internal; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.sdk.env.PropertyEnvironment; +import org.apache.logging.log4j.sdk.env.support.BasicPropertyEnvironment; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.PropertiesUtil; + +/** + * An adapter of the {@link PropertiesUtil} from Log4j API 2.x. + * + * @implNote Since {@link PropertiesUtil} requires all properties to start with {@code log4j2.}, we must add the prefix + * before querying for the property. + */ +public class PropertiesUtilPropertyEnvironment extends BasicPropertyEnvironment { + + private static final String PREFIX = "log4j2."; + public static final PropertyEnvironment INSTANCE = + new PropertiesUtilPropertyEnvironment(PropertiesUtil.getProperties(), StatusLogger.getLogger()); + + private final PropertiesUtil propsUtil; + + public PropertiesUtilPropertyEnvironment(final PropertiesUtil propsUtil, final Logger statusLogger) { + super(statusLogger); + this.propsUtil = propsUtil; + } + + @Override + public String getStringProperty(final String name) { + return propsUtil.getStringProperty(PREFIX + name); + } +} diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/package-info.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/package-info.java new file mode 100644 index 0000000000..f4a5436ba2 --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/internal/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +@Export +@NullMarked +@Version("3.0.0") +package org.apache.logging.log4j.sdk.env.internal; + +import org.jspecify.annotations.NullMarked; +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/package-info.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/package-info.java new file mode 100644 index 0000000000..8c549819cc --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +@Export +@NullMarked +@Version("3.0.0") +package org.apache.logging.log4j.sdk.env; + +import org.jspecify.annotations.NullMarked; +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/BasicPropertyEnvironment.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/BasicPropertyEnvironment.java new file mode 100644 index 0000000000..0228a2d03b --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/BasicPropertyEnvironment.java @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env.support; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.time.Duration; +import java.time.format.DateTimeParseException; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.sdk.env.Log4jProperty; +import org.apache.logging.log4j.sdk.env.PropertyEnvironment; +import org.jspecify.annotations.Nullable; + +/** + * An implementation of {@link PropertyEnvironment} that only uses basic Java functions. + * <p> + * Conversion problems are logged using a status logger. + * </p> + */ +public abstract class BasicPropertyEnvironment implements PropertyEnvironment { + + private final Logger statusLogger; + + protected BasicPropertyEnvironment(final Logger statusLogger) { + this.statusLogger = statusLogger; + } + + @Override + public @Nullable Boolean getBooleanProperty(final String name, final @Nullable Boolean defaultValue) { + final String prop = getStringProperty(name); + return prop == null ? defaultValue : Boolean.parseBoolean(prop); + } + + @Override + public @Nullable Charset getCharsetProperty(final String name, final @Nullable Charset defaultValue) { + final String charsetName = getStringProperty(name); + if (charsetName == null) { + return defaultValue; + } + try { + return Charset.forName(charsetName); + } catch (final IllegalCharsetNameException | UnsupportedOperationException e) { + statusLogger.warn( + "Unable to get Charset '{}' for property '{}', using default '{}'.", + charsetName, + name, + defaultValue, + e); + } + return defaultValue; + } + + @Override + public <T> @Nullable Class<? extends T> getClassProperty( + final String name, final @Nullable Class<? extends T> defaultValue, final Class<T> upperBound) { + final String className = getStringProperty(name); + if (className == null) { + return defaultValue; + } + try { + final Class<?> clazz = getClassForName(className); + if (upperBound.isAssignableFrom(clazz)) { + return (Class<? extends T>) clazz; + } + statusLogger.warn( + "Unable to get Class '{}' for property '{}': class does not extend {}.", + className, + name, + upperBound.getName()); + } catch (final ReflectiveOperationException e) { + statusLogger.warn( + "Unable to get Class '{}' for property '{}', using default '{}'.", + className, + name, + defaultValue, + e); + } + return defaultValue; + } + + protected Class<?> getClassForName(final String className) throws ReflectiveOperationException { + return Class.forName(className); + } + + @Override + public @Nullable Duration getDurationProperty(final String name, final @Nullable Duration defaultValue) { + final String prop = getStringProperty(name); + if (prop != null) { + try { + return Duration.parse(prop); + } catch (final DateTimeParseException ignored) { + statusLogger.warn( + "Invalid Duration value '{}' for property '{}', using default '{}'.", prop, name, defaultValue); + } + } + return defaultValue; + } + + @Override + public @Nullable Integer getIntegerProperty(final String name, final @Nullable Integer defaultValue) { + final String prop = getStringProperty(name); + if (prop != null) { + try { + return Integer.parseInt(prop); + } catch (final Exception ignored) { + statusLogger.warn( + "Invalid integer value '{}' for property '{}', using default '{}'.", prop, name, defaultValue); + } + } + return defaultValue; + } + + @Override + public @Nullable Long getLongProperty(final String name, final @Nullable Long defaultValue) { + final String prop = getStringProperty(name); + if (prop != null) { + try { + return Long.parseLong(prop); + } catch (final Exception ignored) { + statusLogger.warn( + "Invalid long value '{}' for property '{}', using default '{}'.", prop, name, defaultValue); + } + } + return defaultValue; + } + + @Override + public abstract @Nullable String getStringProperty(String name); + + @Override + public <T> T getProperty(final Class<T> propertyClass) { + if (!propertyClass.isRecord()) { + throw new IllegalArgumentException("Unsupported configuration properties class '" + propertyClass.getName() + + "': class is not a record."); + } + if (propertyClass.getAnnotation(Log4jProperty.class) == null) { + throw new IllegalArgumentException("Unsupported configuration properties class '" + propertyClass.getName() + + "': missing '@Log4jProperty' annotation."); + } + return getProperty(null, propertyClass); + } + + private <T> T getProperty(final @Nullable String parentPrefix, final Class<T> propertyClass) { + final Log4jProperty annotation = propertyClass.getAnnotation(Log4jProperty.class); + final String prefix = parentPrefix != null + ? parentPrefix + : annotation != null && annotation.name().isEmpty() ? propertyClass.getSimpleName() : annotation.name(); + + @SuppressWarnings("unchecked") + final Constructor<T>[] constructors = (Constructor<T>[]) propertyClass.getDeclaredConstructors(); + if (constructors.length == 0) { + throw new IllegalArgumentException("Unsupported configuration properties class '" + propertyClass.getName() + + "': missing public constructor."); + } else if (constructors.length > 1) { + throw new IllegalArgumentException("Unsupported configuration properties class '" + propertyClass.getName() + + "': more than one constructor found."); + } + final Constructor<T> constructor = constructors[0]; + + final Parameter[] parameters = constructor.getParameters(); + final Object[] initArgs = new Object[parameters.length]; + for (int i = 0; i < initArgs.length; i++) { + initArgs[i] = getProperty(prefix, parameters[i]); + } + try { + return constructor.newInstance(initArgs); + } catch (final ReflectiveOperationException e) { + statusLogger.warn("Unable to parse configuration properties class {}.", propertyClass.getName(), e); + return null; + } + } + + private Object getProperty(final String parentPrefix, final Parameter parameter) { + if (!parameter.isNamePresent()) { + statusLogger.warn("Missing parameter name on configuration parameter {}.", parameter); + return null; + } + final String key = parentPrefix + "." + parameter.getName(); + final Class<?> type = parameter.getType(); + if (boolean.class.equals(type)) { + return getBooleanProperty(key); + } + if (Class.class.equals(type)) { + return getClassProperty(key, parameter.getAnnotatedType().getType()); + } + if (Charset.class.equals(type)) { + return getCharsetProperty(key); + } + if (Duration.class.equals(type)) { + return getDurationProperty(key); + } + if (Enum.class.isAssignableFrom(type)) { + final String prop = getStringProperty(key); + if (prop != null) { + try { + return Enum.valueOf((Class<? extends Enum>) type, prop); + } catch (final IllegalArgumentException e) { + statusLogger.warn("Invalid {} value '{}' for property '{}'.", type.getSimpleName(), prop, key); + } + } + return null; + } + if (int.class.equals(type)) { + return getIntegerProperty(key); + } + if (long.class.equals(type)) { + return getLongProperty(key); + } + return String.class.equals(type) ? getStringProperty(key) : getProperty(key, type); + } + + private Object getClassProperty(final String key, final Type type) { + Class<?> upperBound = Object.class; + if (type instanceof final ParameterizedType parameterizedType) { + final Type[] arguments = parameterizedType.getActualTypeArguments(); + if (arguments.length > 0) { + upperBound = findUpperBound(arguments[0]); + } + } + return getClassProperty(key, null, upperBound); + } + + private Class<?> findUpperBound(final Type type) { + final Type[] bounds; + if (type instanceof final TypeVariable<?> typeVariable) { + bounds = typeVariable.getBounds(); + } else if (type instanceof final WildcardType wildcardType) { + bounds = wildcardType.getUpperBounds(); + } else { + bounds = new Type[0]; + } + return bounds.length > 0 && bounds[0] instanceof final Class<?> clazz ? clazz : Object.class; + } +} diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/ClassloaderPropertyEnvironment.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/ClassloaderPropertyEnvironment.java new file mode 100644 index 0000000000..b5f5717fb6 --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/ClassloaderPropertyEnvironment.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env.support; + +import org.apache.logging.log4j.Logger; + +/** + * An environment implementation that uses a specific classloader to load classes. + */ +public abstract class ClassloaderPropertyEnvironment extends BasicPropertyEnvironment { + + private final ClassLoader loader; + + public ClassloaderPropertyEnvironment(final ClassLoader loader, final Logger statusLogger) { + super(statusLogger); + this.loader = loader; + } + + @Override + protected Class<?> getClassForName(final String className) throws ReflectiveOperationException { + return Class.forName(className, true, loader); + } +} diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/PropertySourcePropertyEnvironment.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/PropertySourcePropertyEnvironment.java new file mode 100644 index 0000000000..6d2b608f86 --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/PropertySourcePropertyEnvironment.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env.support; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.internal.CopyOnWriteNavigableSet; +import org.apache.logging.log4j.sdk.env.ConfigurablePropertyEnvironment; +import org.apache.logging.log4j.sdk.env.PropertyEnvironment; +import org.apache.logging.log4j.sdk.env.PropertySource; +import org.jspecify.annotations.Nullable; + +public class PropertySourcePropertyEnvironment extends ClassloaderPropertyEnvironment + implements ConfigurablePropertyEnvironment { + + private final Collection<PropertySource> sources = + new CopyOnWriteNavigableSet<>(Comparator.comparing(PropertySource::getPriority)); + private final Map<String, Optional<String>> stringCache = new ConcurrentHashMap<>(); + private final Map<Class<?>, Object> classCache = new ConcurrentHashMap<>(); + + public PropertySourcePropertyEnvironment( + final @Nullable PropertyEnvironment parentEnvironment, + final Collection<? extends PropertySource> sources, + final ClassLoader loader, + final Logger statusLogger) { + super(loader, statusLogger); + this.sources.addAll(sources); + if (parentEnvironment != null) { + this.sources.add(new ParentEnvironmentPropertySource(parentEnvironment)); + } + } + + @Override + public @Nullable String getStringProperty(final String name) { + return stringCache + .computeIfAbsent(name, key -> sources.stream() + .map(source -> source.getProperty(key)) + .filter(Objects::nonNull) + .findFirst()) + .orElse(null); + } + + @Override + @SuppressWarnings("unchecked") + public <T> T getProperty(final Class<T> propertyClass) { + return (T) classCache.computeIfAbsent(propertyClass, super::getProperty); + } + + @Override + public void addPropertySource(final PropertySource source) { + sources.add(Objects.requireNonNull(source)); + stringCache.clear(); + classCache.clear(); + } + + @Override + public void removePropertySource(final PropertySource source) { + sources.remove(Objects.requireNonNull(source)); + stringCache.clear(); + classCache.clear(); + } + + private record ParentEnvironmentPropertySource(PropertyEnvironment parentEnvironment) implements PropertySource { + + @Override + public int getPriority() { + return Integer.MAX_VALUE; + } + + @Override + public @Nullable String getProperty(final String name) { + return parentEnvironment.getStringProperty(name); + } + } +} diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/package-info.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/package-info.java new file mode 100644 index 0000000000..79c7e9d5a0 --- /dev/null +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/env/support/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +@Export +@NullMarked +@Version("3.0.0") +package org.apache.logging.log4j.sdk.env.support; + +import org.jspecify.annotations.NullMarked; +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/logger/AbstractLogger.java b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/logger/AbstractLogger.java index 03cb039e0b..4b1c690142 100644 --- a/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/logger/AbstractLogger.java +++ b/log4j-sdk/src/main/java/org/apache/logging/log4j/sdk/logger/AbstractLogger.java @@ -1114,6 +1114,7 @@ public abstract class AbstractLogger implements ExtendedLogger { final @Nullable Throwable throwable) { // This method does NOT check the level logMessageSafely(fqcn, null, level, marker, message, throwable); + logMessageSafely(FQCN, null, level, marker, message, throwable); } @Override @@ -1127,6 +1128,7 @@ public abstract class AbstractLogger implements ExtendedLogger { final @Nullable Throwable throwable) { // This method does NOT check the level logMessageSafely(fqcn, location, level, marker, message, throwable); + logMessageSafely(FQCN, location, level, marker, message, throwable); } // </editor-fold> diff --git a/log4j-sdk/src/test/java/org/apache/logging/log4j/sdk/env/support/BasicPropertyEnvironmentTest.java b/log4j-sdk/src/test/java/org/apache/logging/log4j/sdk/env/support/BasicPropertyEnvironmentTest.java new file mode 100644 index 0000000000..18ba1ab412 --- /dev/null +++ b/log4j-sdk/src/test/java/org/apache/logging/log4j/sdk/env/support/BasicPropertyEnvironmentTest.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.env.support; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.sdk.env.Log4jProperty; +import org.apache.logging.log4j.sdk.env.PropertyEnvironment; +import org.apache.logging.log4j.sdk.logger.TestListLogger; +import org.apache.logging.log4j.spi.StandardLevel; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class BasicPropertyEnvironmentTest { + + private static final Map<String, String> TEST_PROPS = Map.of( + "ComponentProperties.boolAttr", + "true", + "ComponentProperties.intAttr", + "123", + "ComponentProperties.longAttr", + "123456", + "ComponentProperties.charsetAttr", + "UTF-8", + "ComponentProperties.durationAttr", + "PT8H", + "ComponentProperties.stringAttr", + "Hello child!", + "ComponentProperties.classAttr", + "org.apache.logging.log4j.sdk.env.PropertyEnvironment", + "ComponentProperties.level", + "INFO", + "ComponentProperties.subComponent.subProperty", + "Hello parent!"); + + @Test + void get_property_should_support_records() { + final TestListLogger logger = new TestListLogger(BasicPropertyEnvironmentTest.class.getName()); + final PropertyEnvironment env = new TestPropertyEnvironment(TEST_PROPS, logger); + final ComponentProperties expected = new ComponentProperties( + true, + 123, + 123456L, + StandardCharsets.UTF_8, + Duration.ofHours(8), + "Hello child!", + PropertyEnvironment.class, + StandardLevel.INFO, + new SubComponentProperties("Hello parent!")); + assertThat(env.getProperty(ComponentProperties.class)).isEqualTo(expected); + assertThat(logger.getMessages()).isEmpty(); + } + + static Stream<Arguments> get_property_should_check_bounds() { + return Stream.of( + Arguments.of( + "BoundedClass.className", + "java.lang.String", + BoundedClass.class, + new BoundedClass(null), + List.of("Unable to get Class 'java.lang.String' for property 'BoundedClass.className': " + + "class does not extend java.lang.Number.")), + Arguments.of( + "BoundedClassParam.className", + "java.lang.String", + BoundedClassParam.class, + new BoundedClassParam(null), + List.of("Unable to get Class 'java.lang.String' for property 'BoundedClassParam.className': " + + "class does not extend java.lang.Number.")), + Arguments.of( + "BoundedClass.className", + "java.lang.Integer", + BoundedClass.class, + new BoundedClass(Integer.class), + Collections.emptyList()), + Arguments.of( + "BoundedClassParam.className", + "java.lang.Integer", + BoundedClassParam.class, + new BoundedClassParam(Integer.class), + Collections.emptyList())); + } + + @ParameterizedTest + @MethodSource + void get_property_should_check_bounds( + final String key, + final String value, + final Class<?> clazz, + final Object expected, + final Iterable<? extends String> expectedMessages) { + final TestListLogger logger = new TestListLogger(BasicPropertyEnvironmentTest.class.getName()); + final PropertyEnvironment env = new TestPropertyEnvironment(Map.of(key, value), logger); + assertThat(env.getProperty(clazz)).isEqualTo(expected); + Assertions.<String>assertThat(logger.getMessages()).containsExactlyElementsOf(expectedMessages); + } + + @Log4jProperty + record ComponentProperties( + boolean boolAttr, + int intAttr, + long longAttr, + Charset charsetAttr, + Duration durationAttr, + String stringAttr, + Class<?> classAttr, + StandardLevel level, + SubComponentProperties subComponent) {} + + record SubComponentProperties(String subProperty) {} + + @Log4jProperty + record BoundedClass(Class<? extends Number> className) {} + + @Log4jProperty + record BoundedClassParam<T extends Number>(Class<T> className) {} + + private static class TestPropertyEnvironment extends BasicPropertyEnvironment { + + private final Map<String, String> props; + + public TestPropertyEnvironment(final Map<String, String> props, final Logger logger) { + super(logger); + this.props = props; + } + + @Override + public String getStringProperty(final String name) { + return props.get(name); + } + } +} diff --git a/log4j-sdk/src/test/java/org/apache/logging/log4j/sdk/logger/AbstractLoggerTest.java b/log4j-sdk/src/test/java/org/apache/logging/log4j/sdk/logger/AbstractLoggerTest.java index 22e8078a67..9662c15ed0 100644 --- a/log4j-sdk/src/test/java/org/apache/logging/log4j/sdk/logger/AbstractLoggerTest.java +++ b/log4j-sdk/src/test/java/org/apache/logging/log4j/sdk/logger/AbstractLoggerTest.java @@ -26,7 +26,7 @@ import javassist.bytecode.CodeAttribute; import javassist.bytecode.MethodInfo; import org.junit.jupiter.api.Test; -public class AbstractLoggerTest { +class AbstractLoggerTest { private static final int MAX_INLINE_SIZE = 35; /** diff --git a/log4j-sdk/src/test/java/org/apache/logging/log4j/sdk/logger/TestListLogger.java b/log4j-sdk/src/test/java/org/apache/logging/log4j/sdk/logger/TestListLogger.java new file mode 100644 index 0000000000..c3c8e95522 --- /dev/null +++ b/log4j-sdk/src/test/java/org/apache/logging/log4j/sdk/logger/TestListLogger.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.logging.log4j.sdk.logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.internal.recycler.DummyRecyclerFactoryProvider; +import org.apache.logging.log4j.message.DefaultFlowMessageFactory; +import org.apache.logging.log4j.message.FlowMessageFactory; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory; +import org.apache.logging.log4j.spi.recycler.RecyclerFactory; +import org.apache.logging.log4j.status.StatusLogger; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public class TestListLogger extends AbstractLogger { + + private static final MessageFactory MESSAGE_FACTORY = ParameterizedNoReferenceMessageFactory.INSTANCE; + private static final FlowMessageFactory FLOW_MESSAGE_FACTORY = new DefaultFlowMessageFactory(); + private static final RecyclerFactory RECYCLER_FACTORY = + new DummyRecyclerFactoryProvider().createForEnvironment(null); + + private final List<String> messages = new ArrayList<>(); + + public TestListLogger(final String name) { + super(name, MESSAGE_FACTORY, FLOW_MESSAGE_FACTORY, RECYCLER_FACTORY, StatusLogger.getLogger()); + } + + @Override + public Level getLevel() { + return Level.DEBUG; + } + + @Override + public boolean isEnabled(final Level level, @Nullable final Marker marker) { + return Level.DEBUG.isLessSpecificThan(level); + } + + @Override + protected void doLog( + final String fqcn, + final @Nullable StackTraceElement location, + final Level level, + final @Nullable Marker marker, + final @Nullable Message message, + final @Nullable Throwable throwable) { + messages.add(message != null ? message.getFormattedMessage() : ""); + } + + public List<? extends String> getMessages() { + return Collections.unmodifiableList(messages); + } +}
