diff --git a/log4j-api-test/pom.xml b/log4j-api-test/pom.xml
index c243fad2326..0ef2578dd8f 100644
--- a/log4j-api-test/pom.xml
+++ b/log4j-api-test/pom.xml
@@ -138,7 +138,6 @@
org.mockito
mockito-core
- test
org.mockito
diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusListenerExtension.java
similarity index 80%
rename from log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerExtension.java
rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusListenerExtension.java
index 7f4c9c0702c..7ca5776c583 100644
--- a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerExtension.java
+++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusListenerExtension.java
@@ -16,21 +16,16 @@
*/
package org.apache.logging.log4j.test.junit;
-import java.io.IOException;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory;
-import org.apache.logging.log4j.simple.SimpleLogger;
+import org.apache.logging.log4j.message.ParameterizedMessage;
+import org.apache.logging.log4j.status.StatusConsoleListener;
import org.apache.logging.log4j.status.StatusData;
+import org.apache.logging.log4j.status.StatusListener;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.test.ListStatusListener;
-import org.apache.logging.log4j.util.PropertiesUtil;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -43,12 +38,12 @@
import org.junit.platform.commons.support.ModifierSupport;
import org.junit.platform.commons.support.ReflectionSupport;
-class StatusLoggerExtension extends TypeBasedParameterResolver
+class StatusListenerExtension extends TypeBasedParameterResolver
implements BeforeAllCallback, BeforeEachCallback, TestExecutionExceptionHandler {
private static final Object KEY = ListStatusListener.class;
- public StatusLoggerExtension() {
+ public StatusListenerExtension() {
super(ListStatusListener.class);
}
@@ -130,33 +125,19 @@ public ListStatusListener getStatusListener() {
}
@Override
- public void close() throws Throwable {
+ public void close() {
statusLogger.removeListener(statusListener);
}
public void handleException(final ExtensionContext context, final Throwable throwable) {
- final Logger logger = new SimpleLogger(
- "StatusLoggerExtension",
- Level.ALL,
- false,
- false,
- false,
- false,
+ final StatusListener listener = new StatusConsoleListener(Level.ALL, System.err);
+ listener.log(new StatusData(
null,
- ParameterizedNoReferenceMessageFactory.INSTANCE,
- PropertiesUtil.getProperties(),
- System.err);
- logger.error("Test {} failed.\nDumping status data:", context.getDisplayName());
- final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_TIME.withZone(ZoneId.systemDefault());
- statusListener.getStatusData().forEach(data -> {
- logger.atLevel(data.getLevel())
- .withThrowable(data.getThrowable())
- .withLocation(data.getStackTraceElement())
- .log(
- "{} {}",
- formatter.format(Instant.ofEpochMilli(data.getTimestamp())),
- data.getMessage().getFormattedMessage());
- });
+ Level.ERROR,
+ new ParameterizedMessage("Test `{}` has failed, dumping status data...", context.getDisplayName()),
+ throwable,
+ null));
+ statusListener.getStatusData().forEach(listener::log);
}
}
@@ -186,14 +167,14 @@ public Level getStatusLevel() {
}
@Override
- public void close() throws IOException {
+ public void close() {
// NOP
}
@Override
public Stream getStatusData() {
synchronized (statusData) {
- final List clone = (List) statusData.clone();
+ final List clone = new ArrayList<>(statusData);
return parent != null ? Stream.concat(parent.getStatusData(), clone.stream()) : clone.stream();
}
}
diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerMockExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerMockExtension.java
new file mode 100644
index 00000000000..2725dc7314a
--- /dev/null
+++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerMockExtension.java
@@ -0,0 +1,75 @@
+/*
+ * 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.test.junit;
+
+import static org.apache.logging.log4j.test.junit.ExtensionContextAnchor.getAttribute;
+import static org.apache.logging.log4j.test.junit.ExtensionContextAnchor.setAttribute;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+import org.apache.logging.log4j.status.StatusConsoleListener;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+/**
+ * Replaces {@link StatusLogger} static instance with a mocked one.
+ *
+ * Warning!
+ * Many classes store the result of {@link StatusLogger#getLogger()} in {@code static} field.
+ * Hence, the mock replacement must be performed before anybody tries to access it.
+ * Similarly, we cannot replace the mock in between tests, since it is already stored in {@code static} fields.
+ * That is why we only reset the mocked instance before each test.
+ *
+ *
+ * @see UsingStatusLoggerMock
+ */
+class StatusLoggerMockExtension implements BeforeAllCallback, BeforeEachCallback, AfterAllCallback {
+
+ private static final String KEY_PREFIX = StatusLoggerMockExtension.class.getSimpleName() + '.';
+
+ private static final String INITIAL_STATUS_LOGGER_KEY = KEY_PREFIX + "initialStatusLogger";
+
+ @Override
+ public void beforeAll(final ExtensionContext context) throws Exception {
+ setAttribute(INITIAL_STATUS_LOGGER_KEY, StatusLogger.getLogger(), context);
+ final StatusLogger statusLogger = mock(StatusLogger.class);
+ stubFallbackListener(statusLogger);
+ StatusLogger.setLogger(statusLogger);
+ }
+
+ @Override
+ public void beforeEach(final ExtensionContext context) throws Exception {
+ final StatusLogger statusLogger = StatusLogger.getLogger();
+ reset(statusLogger); // Stubs get reset too!
+ stubFallbackListener(statusLogger);
+ }
+
+ private static void stubFallbackListener(final StatusLogger statusLogger) {
+ final StatusConsoleListener fallbackListener = mock(StatusConsoleListener.class);
+ when(statusLogger.getFallbackListener()).thenReturn(fallbackListener);
+ }
+
+ @Override
+ public void afterAll(final ExtensionContext context) {
+ final StatusLogger statusLogger = getAttribute(INITIAL_STATUS_LOGGER_KEY, StatusLogger.class, context);
+ StatusLogger.setLogger(statusLogger);
+ }
+}
diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusListener.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusListener.java
index e54deb6b429..2ca8124b03a 100644
--- a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusListener.java
+++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusListener.java
@@ -37,5 +37,5 @@
@Documented
@ExtendWith(ExtensionContextAnchor.class)
@ExtendWith(TestPropertyResolver.class)
-@ExtendWith(StatusLoggerExtension.class)
+@ExtendWith(StatusListenerExtension.class)
public @interface UsingStatusListener {}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest2.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusLoggerMock.java
similarity index 52%
rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest2.java
rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusLoggerMock.java
index 54e5a78a97f..105eef9e2d2 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest2.java
+++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusLoggerMock.java
@@ -14,24 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.logging.log4j.core.async;
+package org.apache.logging.log4j.test.junit;
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.test.junit.Tags;
-import org.apache.logging.log4j.test.junit.SetTestProperty;
-import org.junit.jupiter.api.Tag;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import org.junit.jupiter.api.extension.ExtendWith;
/**
- * Tests queue full scenarios with AsyncLoggers in configuration.
+ * Shortcut to {@link StatusLoggerMockExtension}.
*/
-@SetTestProperty(key = "log4j2.formatMsgAsync", value = "true")
-@Tag(Tags.ASYNC_LOGGERS)
-public class QueueFullAsyncLoggerConfigLoggingFromToStringTest2
- extends QueueFullAsyncLoggerConfigLoggingFromToStringTest {
-
- @Override
- protected void checkConfig(final LoggerContext ctx) throws ReflectiveOperationException {
- super.checkConfig(ctx);
- assertFormatMessagesInBackground();
- }
-}
+@Retention(RUNTIME)
+@Target({TYPE, METHOD})
+@Documented
+@ExtendWith({ExtensionContextAnchor.class, StatusLoggerMockExtension.class})
+public @interface UsingStatusLoggerMock {}
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java
index 194fb330130..e2ec325ce74 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java
@@ -42,9 +42,12 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.ResourceAccessMode;
import org.junit.jupiter.api.parallel.ResourceLock;
+import org.junitpioneer.jupiter.SetSystemProperty;
@StatusLoggerLevel("WARN")
@ResourceLock(value = Resources.MARKER_MANAGER, mode = ResourceAccessMode.READ)
+@SetSystemProperty(key = "log4j2.status.entries", value = "200")
+@SetSystemProperty(key = "log4j2.StatusLogger.level", value = "WARN")
public class AbstractLoggerTest {
private static final StringBuilder CHAR_SEQ = new StringBuilder("CharSeq");
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.java
index 48d9a7346fc..0b93ae1fbcd 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.java
@@ -19,11 +19,9 @@
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.LogBuilder;
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.simple.SimpleLogger;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@@ -33,38 +31,19 @@ public class StatusConsoleListenerTest {
public static final MessageFactory MESSAGE_FACTORY = ParameterizedNoReferenceMessageFactory.INSTANCE;
@Test
- void SimpleLogger_should_be_used() {
-
- // Create a mock `SimpleLoggerFactory`.
- final SimpleLogger logger = Mockito.mock(SimpleLogger.class);
- final LogBuilder logBuilder = Mockito.mock(LogBuilder.class);
- Mockito.when(logger.atLevel(Mockito.any())).thenReturn(logBuilder);
- Mockito.when(logBuilder.withThrowable(Mockito.any())).thenReturn(logBuilder);
- Mockito.when(logBuilder.withLocation(Mockito.any())).thenReturn(logBuilder);
- final SimpleLoggerFactory loggerFactory = Mockito.mock(SimpleLoggerFactory.class);
- Mockito.when(loggerFactory.createSimpleLogger(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
- .thenReturn(logger);
+ void StatusData_getFormattedStatus_should_be_used() {
// Create the listener.
final PrintStream stream = Mockito.mock(PrintStream.class);
- final Level level = Mockito.mock(Level.class);
- final StatusConsoleListener listener = new StatusConsoleListener(level, stream, loggerFactory);
+ final StatusConsoleListener listener = new StatusConsoleListener(Level.ALL, stream);
// Log a message.
- final StackTraceElement caller = Mockito.mock(StackTraceElement.class);
final Message message = Mockito.mock(Message.class);
- final Throwable throwable = Mockito.mock(Throwable.class);
- final StatusData statusData = new StatusData(caller, level, message, throwable, null);
+ final StatusData statusData = Mockito.spy(new StatusData(null, Level.TRACE, message, null, null));
listener.log(statusData);
// Verify the call.
- Mockito.verify(loggerFactory)
- .createSimpleLogger(
- Mockito.eq("StatusConsoleListener"), Mockito.same(level), Mockito.any(), Mockito.same(stream));
- Mockito.verify(logger).atLevel(Mockito.same(level));
- Mockito.verify(logBuilder).withThrowable(Mockito.same(throwable));
- Mockito.verify(logBuilder).withLocation(Mockito.same(caller));
- Mockito.verify(logBuilder).log(Mockito.same(message));
+ Mockito.verify(statusData).getFormattedStatus();
}
@Test
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerLevelTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerLevelTest.java
new file mode 100644
index 00000000000..5dedd835edf
--- /dev/null
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerLevelTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.status;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.logging.log4j.Level;
+import org.junit.jupiter.api.Test;
+
+class StatusLoggerLevelTest {
+
+ @Test
+ void effective_level_should_be_the_least_specific_one() {
+
+ // Verify the initial level
+ final StatusLogger logger = StatusLogger.getLogger();
+ final Level fallbackListenerLevel = Level.ERROR;
+ assertThat(logger.getLevel()).isEqualTo(fallbackListenerLevel);
+
+ // Register a less specific listener
+ final StatusListener listener1 = mock(StatusListener.class);
+ Level listener1Level = Level.WARN;
+ when(listener1.getStatusLevel()).thenReturn(listener1Level);
+ logger.registerListener(listener1);
+ assertThat(listener1Level).isNotEqualTo(fallbackListenerLevel); // Verify that the level is distinct
+ assertThat(logger.getLevel()).isEqualTo(listener1Level); // Verify that the logger level is changed
+
+ // Register a less specific listener
+ final StatusListener listener2 = mock(StatusListener.class);
+ final Level listener2Level = Level.INFO;
+ when(listener2.getStatusLevel()).thenReturn(listener2Level);
+ logger.registerListener(listener2);
+ assertThat(listener2Level)
+ .isNotEqualTo(fallbackListenerLevel)
+ .isNotEqualTo(listener1Level); // Verify that the level is distinct
+ assertThat(logger.getLevel()).isEqualTo(listener2Level); // Verify that the logger level is changed
+
+ // Register a more specific listener
+ final StatusListener listener3 = mock(StatusListener.class);
+ final Level listener3Level = Level.ERROR;
+ when(listener3.getStatusLevel()).thenReturn(listener3Level);
+ logger.registerListener(listener3);
+ assertThat(listener3Level)
+ .isNotEqualTo(listener1Level)
+ .isNotEqualTo(listener2Level); // Verify that the level is distinct
+ assertThat(logger.getLevel()).isEqualTo(listener2Level); // Verify that the logger level is not changed
+
+ // Update a registered listener level
+ listener1Level = Level.DEBUG;
+ when(listener1.getStatusLevel()).thenReturn(listener1Level);
+ assertThat(listener1Level) // Verify that the level is distinct
+ .isNotEqualTo(fallbackListenerLevel)
+ .isNotEqualTo(listener2Level)
+ .isNotEqualTo(listener3Level);
+ assertThat(logger.getLevel()).isEqualTo(listener1Level); // Verify that the logger level is changed
+
+ // Remove the least specific listener
+ logger.removeListener(listener2);
+ assertThat(logger.getLevel()).isEqualTo(listener1Level); // Verify that the level is changed
+
+ // Remove the most specific listener
+ logger.removeListener(listener3);
+ assertThat(logger.getLevel()).isEqualTo(listener1Level); // Verify that the level is not changed
+
+ // Remove the last listener
+ logger.removeListener(listener1);
+ assertThat(logger.getLevel()).isEqualTo(fallbackListenerLevel); // Verify that the level is changed
+ }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
index 8620a5b1714..d9543f17b52 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
@@ -38,7 +38,6 @@
import org.apache.logging.log4j.util.LoaderUtil;
import org.apache.logging.log4j.util.MessageSupplier;
import org.apache.logging.log4j.util.PerformanceSensitive;
-import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.log4j.util.Supplier;
@@ -86,14 +85,14 @@ public abstract class AbstractLogger implements ExtendedLogger, LocationAwareLog
/**
* The default MessageFactory class.
*/
- public static final Class extends MessageFactory> DEFAULT_MESSAGE_FACTORY_CLASS = createClassForProperty(
- "log4j2.messageFactory", ReusableMessageFactory.class, ParameterizedMessageFactory.class);
+ public static final Class extends MessageFactory> DEFAULT_MESSAGE_FACTORY_CLASS =
+ ParameterizedMessageFactory.class;
/**
* The default FlowMessageFactory class.
*/
public static final Class extends FlowMessageFactory> DEFAULT_FLOW_MESSAGE_FACTORY_CLASS =
- createFlowClassForProperty("log4j2.flowMessageFactory", DefaultFlowMessageFactory.class);
+ DefaultFlowMessageFactory.class;
private static final long serialVersionUID = 2L;
@@ -198,32 +197,6 @@ protected Message catchingMsg(final Throwable throwable) {
return messageFactory.newMessage(CATCHING);
}
- private static Class extends MessageFactory> createClassForProperty(
- final String property,
- final Class reusableParameterizedMessageFactoryClass,
- final Class parameterizedMessageFactoryClass) {
- try {
- final String fallback = Constants.ENABLE_THREADLOCALS
- ? reusableParameterizedMessageFactoryClass.getName()
- : parameterizedMessageFactoryClass.getName();
- final String clsName = PropertiesUtil.getProperties().getStringProperty(property, fallback);
- return LoaderUtil.loadClass(clsName).asSubclass(MessageFactory.class);
- } catch (final Throwable throwable) {
- return parameterizedMessageFactoryClass;
- }
- }
-
- private static Class extends FlowMessageFactory> createFlowClassForProperty(
- final String property, final Class defaultFlowMessageFactoryClass) {
- try {
- final String clsName = PropertiesUtil.getProperties()
- .getStringProperty(property, defaultFlowMessageFactoryClass.getName());
- return LoaderUtil.loadClass(clsName).asSubclass(FlowMessageFactory.class);
- } catch (final Throwable throwable) {
- return defaultFlowMessageFactoryClass;
- }
- }
-
private static MessageFactory2 createDefaultMessageFactory() {
try {
final MessageFactory result = LoaderUtil.newInstanceOf(DEFAULT_MESSAGE_FACTORY_CLASS);
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/SimpleLoggerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/SimpleLoggerFactory.java
deleted file mode 100644
index 85e714b8f49..00000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/status/SimpleLoggerFactory.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.status;
-
-import java.io.PrintStream;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.simple.SimpleLogger;
-import org.apache.logging.log4j.util.Strings;
-
-/**
- * {@link org.apache.logging.log4j.simple.SimpleLogger} factory to be used by {@link StatusLogger} and {@link StatusConsoleListener}.
- */
-final class SimpleLoggerFactory {
-
- private static final SimpleLoggerFactory INSTANCE = new SimpleLoggerFactory();
-
- private SimpleLoggerFactory() {}
-
- static SimpleLoggerFactory getInstance() {
- return INSTANCE;
- }
-
- SimpleLogger createSimpleLogger(
- final String name, final Level level, final MessageFactory messageFactory, final PrintStream stream) {
- final String dateFormat = StatusLogger.PROPS.getStringProperty(StatusLogger.STATUS_DATE_FORMAT);
- final boolean dateFormatProvided = Strings.isNotBlank(dateFormat);
- return new SimpleLogger(
- name,
- level,
- false,
- true,
- dateFormatProvided,
- false,
- dateFormat,
- messageFactory,
- StatusLogger.PROPS,
- stream);
- }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java
index 5961b20cbfd..3e30bf19942 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java
@@ -16,24 +16,22 @@
*/
package org.apache.logging.log4j.status;
+import static java.util.Objects.requireNonNull;
+
import java.io.IOException;
+import java.io.OutputStream;
import java.io.PrintStream;
-import java.util.Objects;
import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory;
/**
- * {@link StatusListener} that writes to the console.
+ * A {@link StatusListener} that writes to the console.
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public class StatusConsoleListener implements StatusListener {
- private Level level;
-
- private final PrintStream stream;
+ private volatile Level level;
- private final Logger logger;
+ private volatile PrintStream stream;
/**
* Constructs a {@link StatusConsoleListener} instance writing to {@link System#out} using the supplied level.
@@ -56,50 +54,59 @@ public StatusConsoleListener(final Level level) {
* @throws NullPointerException on null {@code level} or {@code stream}
*/
public StatusConsoleListener(final Level level, final PrintStream stream) {
- this(level, stream, SimpleLoggerFactory.getInstance());
- }
-
- StatusConsoleListener(final Level level, final PrintStream stream, final SimpleLoggerFactory loggerFactory) {
- this.level = Objects.requireNonNull(level, "level");
- this.stream = Objects.requireNonNull(stream, "stream");
- this.logger = Objects.requireNonNull(loggerFactory, "loggerFactory")
- .createSimpleLogger(
- "StatusConsoleListener", level, ParameterizedNoReferenceMessageFactory.INSTANCE, stream);
+ this.level = requireNonNull(level, "level");
+ this.stream = requireNonNull(stream, "stream");
}
/**
* Sets the level to a new value.
- * @param level The new Level.
+ *
+ * @param level the new level
+ * @throws NullPointerException on null {@code level}
*/
public void setLevel(final Level level) {
- this.level = level;
+ this.level = requireNonNull(level, "level");
}
/**
- * Return the Log Level for which the Listener should receive events.
- * @return the Log Level.
+ * Sets the output stream to a new value.
+ *
+ * @param stream the new output stream
+ * @throws NullPointerException on null {@code stream}
+ * @since 2.23.0
+ */
+ public void setStream(final PrintStream stream) {
+ this.stream = requireNonNull(stream, "stream");
+ }
+
+ /**
+ * Returns the level for which the listener should receive events.
+ *
+ * @return the log level
*/
@Override
public Level getStatusLevel() {
- return this.level;
+ return level;
}
/**
* Writes status messages to the console.
- * @param data The StatusData.
+ *
+ * @param data a status data
+ * @throws NullPointerException on null {@code data}
*/
@Override
public void log(final StatusData data) {
- logger
- // Logging using _only_ the following 4 fields set by `StatusLogger#logMessage()`:
- .atLevel(data.getLevel())
- .withThrowable(data.getThrowable())
- .withLocation(data.getStackTraceElement())
- .log(data.getMessage());
+ requireNonNull(data, "data");
+ if (level.isLessSpecificThan(data.getLevel())) {
+ final String formattedStatus = data.getFormattedStatus();
+ stream.println(formattedStatus);
+ }
}
/**
* Adds package name filters to exclude.
+ *
* @param filters An array of package names to exclude.
* @deprecated This method is ineffective and only kept for binary backward compatibility.
*/
@@ -108,9 +115,11 @@ public void setFilters(final String... filters) {}
@Override
public void close() throws IOException {
- // only want to close non-system streams
- if (this.stream != System.out && this.stream != System.err) {
- this.stream.close();
+ // Get local copy of the `volatile` member
+ final OutputStream localStream = stream;
+ // Close only non-system streams
+ if (localStream != System.out && localStream != System.err) {
+ localStream.close();
}
}
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusData.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusData.java
index 10883be8497..e3b13e8dedc 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusData.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusData.java
@@ -16,14 +16,16 @@
*/
package org.apache.logging.log4j.status;
+import static java.util.Objects.requireNonNull;
import static org.apache.logging.log4j.util.Chars.SPACE;
+import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.message.Message;
@@ -34,50 +36,84 @@ public class StatusData implements Serializable {
private static final long serialVersionUID = -4341916115118014017L;
- private final long timestamp;
+ private final Instant instant;
+
+ @Nullable
+ private final DateTimeFormatter instantFormatter;
+
+ @Nullable
private final StackTraceElement caller;
+
private final Level level;
- private final Message msg;
- private String threadName;
+
+ private final Message message;
+
+ private final String threadName;
+
+ @Nullable
private final Throwable throwable;
/**
- * Creates the StatusData object.
+ * Constructs the instance using given properties.
*
- * @param caller The method that created the event.
- * @param level The logging level.
- * @param msg The message String.
- * @param t The Error or Exception that occurred.
- * @param threadName The thread name
+ * @param caller the method that created the event
+ * @param level a logging level
+ * @param message a message
+ * @param throwable the error occurred
+ * @param threadName the thread name
*/
public StatusData(
- final StackTraceElement caller,
+ @Nullable final StackTraceElement caller,
final Level level,
- final Message msg,
- final Throwable t,
- final String threadName) {
- this.timestamp = System.currentTimeMillis();
+ final Message message,
+ @Nullable final Throwable throwable,
+ @Nullable final String threadName) {
+ this(caller, level, message, throwable, threadName, null);
+ }
+
+ StatusData(
+ @Nullable final StackTraceElement caller,
+ final Level level,
+ final Message message,
+ @Nullable final Throwable throwable,
+ @Nullable final String threadName,
+ @Nullable final DateTimeFormatter instantFormatter) {
+ this.instantFormatter = instantFormatter;
+ this.instant = Instant.now();
this.caller = caller;
- this.level = level;
- this.msg = msg;
- this.throwable = t;
- this.threadName = threadName;
+ this.level = requireNonNull(level, "level");
+ this.message = requireNonNull(message, "message");
+ this.throwable = throwable;
+ this.threadName =
+ threadName != null ? threadName : Thread.currentThread().getName();
+ }
+
+ /**
+ * Returns the instant of the event.
+ *
+ * @return the event's instant
+ */
+ public Instant getInstant() {
+ return instant;
}
/**
- * Returns the event's timestamp.
+ * Returns the instant of the event.
*
- * @return The event's timestamp.
+ * @return the event's instant
+ * @deprecated Use {@link #getInstant()} instead.
*/
+ @Deprecated
public long getTimestamp() {
- return timestamp;
+ return instant.toEpochMilli();
}
/**
- * Returns the StackTraceElement for the method that created the event.
+ * Returns the method that created the event.
*
- * @return The StackTraceElement.
+ * @return the method that created the event
*/
+ @Nullable
public StackTraceElement getStackTraceElement() {
return caller;
}
@@ -85,7 +121,7 @@ public StackTraceElement getStackTraceElement() {
/**
* Returns the logging level for the event.
*
- * @return The logging level.
+ * @return the event's logging level
*/
public Level getLevel() {
return level;
@@ -94,32 +130,35 @@ public Level getLevel() {
/**
* Returns the message associated with the event.
*
- * @return The message associated with the event.
+ * @return the message associated with the event
*/
public Message getMessage() {
- return msg;
+ return message;
}
+ /**
+ * Returns the name of the thread associated with the event.
+ *
+ * @return the name of the thread associated with the event
+ */
public String getThreadName() {
- if (threadName == null) {
- threadName = Thread.currentThread().getName();
- }
return threadName;
}
/**
- * Returns the Throwable associated with the event.
+ * Returns the error associated with the event.
*
- * @return The Throwable associated with the event.
+ * @return the error associated with the event
*/
+ @Nullable
public Throwable getThrowable() {
return throwable;
}
/**
- * Formats the StatusData for viewing.
+ * Formats the event in to a log line for viewing.
*
- * @return The formatted status data as a String.
+ * @return the formatted event
*/
@SuppressFBWarnings(
value = "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE",
@@ -127,38 +166,36 @@ public Throwable getThrowable() {
@SuppressWarnings("DefaultCharset")
public String getFormattedStatus() {
final StringBuilder sb = new StringBuilder();
- final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
- sb.append(format.format(new Date(timestamp)));
+ final String formattedInstant =
+ instantFormatter != null ? instantFormatter.format(instant) : instant.toString();
+ sb.append(formattedInstant);
sb.append(SPACE);
sb.append(getThreadName());
sb.append(SPACE);
sb.append(level.toString());
sb.append(SPACE);
- sb.append(msg.getFormattedMessage());
- final Object[] params = msg.getParameters();
- Throwable t;
- if (throwable == null && params != null && params[params.length - 1] instanceof Throwable) {
- t = (Throwable) params[params.length - 1];
+ sb.append(message.getFormattedMessage());
+ final Object[] parameters = message.getParameters();
+ Throwable effectiveThrowable;
+ if (throwable == null && parameters != null && parameters[parameters.length - 1] instanceof Throwable) {
+ effectiveThrowable = (Throwable) parameters[parameters.length - 1];
} else {
- t = throwable;
+ effectiveThrowable = throwable;
}
- if (t != null) {
+ if (effectiveThrowable != null) {
sb.append(SPACE);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- t.printStackTrace(new PrintStream(baos));
+ effectiveThrowable.printStackTrace(new PrintStream(baos));
/*
* https://errorprone.info/bugpattern/DefaultCharset
*
* Since Java 9 we'll be able to provide a charset.
*/
- sb.append(baos.toString());
+ sb.append(baos);
}
return sb.toString();
}
- /**
- * Used in tests
- */
@Override
public String toString() {
return getMessage().getFormattedMessage();
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
index e224eb1045e..af6d6687900 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
@@ -16,295 +16,558 @@
*/
package org.apache.logging.log4j.status;
-import java.io.Closeable;
+import static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
+import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
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.simple.SimpleLogger;
-import org.apache.logging.log4j.simple.SimpleLoggerContext;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.util.Constants;
-import org.apache.logging.log4j.util.PropertiesUtil;
/**
- * Records events that occur in the logging system. By default, only error messages are logged to {@link System#err}.
- * Normally, the Log4j StatusLogger is configured via the root {@code } node in a Log4j
- * configuration file. However, this can be overridden via a system property named
- * {@value #DEFAULT_STATUS_LISTENER_LEVEL} and will work with any Log4j provider.
- *
- * @see SimpleLogger
- * @see SimpleLoggerContext
+ * Records events that occur in the logging system.
+ * {@link StatusLogger} is expected to be a standalone, self-sufficient component that the logging system can rely on for low-level logging purposes.
+ * Listeners
+ *
+ * Each recorded event will first get buffered and then used to notify the registered {@link StatusListener}s.
+ * If none are available, the fallback listener of type {@link StatusConsoleListener} will be used.
+ *
+ *
+ * You can programmatically register listeners using {@link #registerListener(StatusListener)} method.
+ *
+ * Configuration
+ *
+ * The {@code StatusLogger} can be configured in following ways:
+ *
+ *
+ * - Passing system properties to the Java process (e.g., {@code -Dlog4j2.StatusLogger.level=INFO})
+ * - Providing properties in a {@value StatusLogger#PROPERTIES_FILE_NAME} file in the classpath
+ * - Using Log4j configuration (i.e., {@code } in a {@code log4j2.xml} in the classpath)
+ *
+ *
+ * It is crucial to understand that there is a time between the first {@code StatusLogger} access and a configuration file (e.g., {@code log4j2.xml}) read.
+ * Consider the following example:
+ *
+ *
+ * - The default level (of fallback listener) is {@code ERROR}
+ * - You have {@code } in your {@code log4j2.xml}
+ * - Until your {@code log4j2.xml} configuration is read, the effective level will be {@code ERROR}
+ * - Once your {@code log4j2.xml} configuration is read, the effective level will be {@code WARN} as you configured
+ *
+ *
+ * Hence, unless you use either system properties or {@value StatusLogger#PROPERTIES_FILE_NAME} file in the classpath, there is a time window that only the defaults will be effective.
+ *
+ *
+ * {@code StatusLogger} is designed as a singleton class accessed statically.
+ * If you are running an application containing multiple Log4j configurations (e.g., in a servlet environment with multiple containers) and you happen to have differing {@code StatusLogger} configurations (e.g, one {@code log4j2.xml} containing {@code } while the other {@code }), the last loaded configuration will be effective one.
+ *
+ * Configuration properties
+ *
+ * The list of available properties for configuring the {@code StatusLogger} is shared below.
+ *
+ *
+ * available properties for configuring the StatusLogger
+ *
+ * Name |
+ * Default |
+ * Description |
+ *
+ *
+ * {@value MAX_STATUS_ENTRIES} |
+ * 0 |
+ *
+ * The maximum number of events buffered.
+ * Once the limit is reached, older entries will be removed as new entries are added.
+ * |
+ *
+ *
+ * {@value DEFAULT_STATUS_LISTENER_LEVEL} |
+ * {@code ERROR} |
+ *
+ * The {@link Level} name to use as the fallback listener level.
+ * The fallback listener is used when the listener registry is empty.
+ * The fallback listener will accept entries filtered by the level provided in this configuration.
+ * |
+ *
+ *
+ * {@value STATUS_DATE_FORMAT} |
+ * {@code null} |
+ * A {@link java.time.format.DateTimeFormatter} pattern to format the created {@link StatusData}. |
+ *
+ *
+ * {@value #DEBUG_PROPERTY_NAME} |
+ * false |
+ * The debug mode toggle. |
+ *
+ *
+ * Debug mode
+ *
+ * When the {@value Constants#LOG4J2_DEBUG} system property is present, any level-related filtering will be skipped and all events will be notified to listeners.
+ * If no listeners are available, the fallback listener of type {@link StatusConsoleListener} will be used.
+ *
*/
-public final class StatusLogger extends AbstractLogger {
+public class StatusLogger extends AbstractLogger {
+
+ private static final long serialVersionUID = 2L;
+
+ /**
+ * The name of the system property that enables debug mode in its presence.
+ *
+ * This is a local clone of {@link Constants#LOG4J2_DEBUG}.
+ * The cloning is necessary to avoid cyclic initialization.
+ *
+ */
+ private static final String DEBUG_PROPERTY_NAME = "log4j2.debug";
/**
- * System property that can be configured with the number of entries in the queue. Once the limit is reached older
- * entries will be removed as new entries are added.
+ * The name of the system property that can be configured with the maximum number of events buffered.
+ *
+ * Once the limit is reached, older entries will be removed as new entries are added.
+ *
*/
public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
/**
- * System property that can be configured with the {@link Level} name to use as the default level for
- * {@link StatusListener}s.
+ * The name of the system property that can be configured with the {@link Level} name to use as the fallback listener level.
+ *
+ * The fallback listener is used when the listener registry is empty.
+ * The fallback listener will accept entries filtered by the level provided in this configuration.
+ *
+ *
+ * @since 2.8
*/
public static final String DEFAULT_STATUS_LISTENER_LEVEL = "log4j2.StatusLogger.level";
/**
- * System property that can be configured with a date-time format string to use as the format for timestamps
- * in the status logger output. See {@link java.text.SimpleDateFormat} for supported formats.
+ * The name of the system property that can be configured with a {@link java.time.format.DateTimeFormatter} pattern that will be used while formatting the created {@link StatusData}.
+ *
* @since 2.11.0
*/
public static final String STATUS_DATE_FORMAT = "log4j2.StatusLogger.DateFormat";
- private static final long serialVersionUID = 2L;
+ /**
+ * The name of the file to be searched in the classpath to read properties from.
+ *
+ * @since 2.23.0
+ */
+ public static final String PROPERTIES_FILE_NAME = "log4j2.StatusLogger.properties";
+
+ /**
+ * Holder for user-provided {@link StatusLogger} configurations.
+ *
+ * @since 2.23.0
+ */
+ public static final class Config {
+
+ private static final Config INSTANCE = new Config();
+
+ private final boolean debugEnabled;
+
+ private final int bufferCapacity;
+
+ private final Level fallbackListenerLevel;
+
+ @Nullable
+ private final DateTimeFormatter instantFormatter;
+
+ /**
+ * Constructs an instance using the given properties.
+ * Users should not create new instances, but use {@link #getInstance()} instead!
+ *
+ * @param debugEnabled the value of the {@value DEBUG_PROPERTY_NAME} property
+ * @param bufferCapacity the value of the {@value MAX_STATUS_ENTRIES} property
+ * @param fallbackListenerLevel the value of the {@value DEFAULT_STATUS_LISTENER_LEVEL} property
+ * @param instantFormatter the value of the {@value STATUS_DATE_FORMAT} property
+ */
+ public Config(
+ boolean debugEnabled,
+ int bufferCapacity,
+ Level fallbackListenerLevel,
+ @Nullable DateTimeFormatter instantFormatter) {
+ this.debugEnabled = debugEnabled;
+ if (bufferCapacity < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a positive `bufferCapacity`, found: " + bufferCapacity);
+ }
+ this.bufferCapacity = bufferCapacity;
+ this.fallbackListenerLevel = requireNonNull(fallbackListenerLevel, "fallbackListenerLevel");
+ this.instantFormatter = requireNonNull(instantFormatter, "instantFormatter");
+ }
+
+ /**
+ * Constructs an instance using either system properties or a property file (i.e., {@value Config#PROPERTIES_FILE_NAME}) in the classpath, if available.
+ */
+ private Config() {
+ final Properties fileProvidedProperties = readPropertiesFile();
+ this.debugEnabled = readDebugEnabled(fileProvidedProperties);
+ this.bufferCapacity = readBufferCapacity(fileProvidedProperties);
+ this.fallbackListenerLevel = readFallbackListenerLevel(fileProvidedProperties);
+ this.instantFormatter = readInstantFormatter(fileProvidedProperties);
+ }
+
+ /**
+ * Gets the static instance.
+ *
+ * @return a singleton instance
+ */
+ public static Config getInstance() {
+ return INSTANCE;
+ }
+
+ private static boolean readDebugEnabled(final Properties fileProvidedProperties) {
+ final String debug = readProperty(fileProvidedProperties, DEBUG_PROPERTY_NAME);
+ return debug != null;
+ }
- private static final String NOT_AVAIL = "?";
+ private static int readBufferCapacity(final Properties fileProvidedProperties) {
+ final String capacityString = readProperty(fileProvidedProperties, MAX_STATUS_ENTRIES);
+ return capacityString != null ? Integer.parseInt(capacityString) : 0;
+ }
+
+ private static Level readFallbackListenerLevel(final Properties fileProvidedProperties) {
+ final String level = readProperty(fileProvidedProperties, DEFAULT_STATUS_LISTENER_LEVEL);
+ return level != null ? Level.valueOf(level) : Level.ERROR;
+ }
+
+ private static DateTimeFormatter readInstantFormatter(final Properties fileProvidedProperties) {
+ final String format = readProperty(fileProvidedProperties, STATUS_DATE_FORMAT);
+ return format != null ? DateTimeFormatter.ofPattern(format) : null;
+ }
- static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
+ private static String readProperty(final Properties fileProvidedProperties, final String propertyName) {
+ final String systemProvidedValue = System.getProperty(propertyName);
+ return systemProvidedValue != null
+ ? systemProvidedValue
+ : (String) fileProvidedProperties.get(propertyName);
+ }
- private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
+ // We need to roll out our own `.properties` reader.
+ // We could have used `PropertiesUtil`, `PropertyFilePropertySource`, etc.
+ // Consequently, they would delegate to `LoaderUtil`, etc.
+ // All these mechanisms expect a working `StatusLogger`.
+ // Hence, in order to be self-sufficient, we cannot rely on them.
+ private static Properties readPropertiesFile() {
+ final Properties properties = new Properties();
+ final URL url = StatusLogger.class.getResource(PROPERTIES_FILE_NAME);
+ if (url == null) {
+ return properties;
+ }
+ try (final InputStream stream = url.openStream()) {
+ properties.load(stream);
+ } catch (final IOException error) {
+ // There is no logging system at this stage.
+ // There is nothing we can do but simply dumping the failure.
+ error.printStackTrace(System.err);
+ }
+ return properties;
+ }
+ }
- private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty(DEFAULT_STATUS_LISTENER_LEVEL);
+ /**
+ * Wrapper for the default instance for lazy initialization.
+ *
+ * The initialization will be performed when the JVM initializes the class.
+ * Since {@code InstanceHolder} has no other fields or methods, class initialization occurs when the {@code INSTANCE} field is first referenced.
+ *
+ *
+ * @see Double-checked locking: Clever, but broken
+ */
+ private static final class InstanceHolder {
- static final boolean DEBUG_ENABLED =
- PropertiesUtil.getProperties().getBooleanProperty(Constants.LOG4J2_DEBUG, false, true);
+ private static volatile StatusLogger INSTANCE = new StatusLogger();
+ }
- // LOG4J2-1176: normal parameterized message remembers param object, causing memory leaks.
- private static final StatusLogger STATUS_LOGGER = new StatusLogger(
- StatusLogger.class.getName(),
- ParameterizedNoReferenceMessageFactory.INSTANCE,
- SimpleLoggerFactory.getInstance());
+ private final Config config;
- private final SimpleLogger logger;
+ private final StatusConsoleListener fallbackListener;
- private final Collection listeners = new CopyOnWriteArrayList<>();
+ private final List listeners;
- @SuppressWarnings("NonSerializableFieldInSerializableClass")
- // ReentrantReadWriteLock is Serializable
- private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
+ private final transient ReadWriteLock listenerLock = new ReentrantReadWriteLock();
- private final Queue messages = new BoundedQueue<>(MAX_ENTRIES);
+ private final transient Lock listenerReadLock = listenerLock.readLock();
- @SuppressWarnings("NonSerializableFieldInSerializableClass")
- // ReentrantLock is Serializable
- private final Lock msgLock = new ReentrantLock();
+ private final transient Lock listenerWriteLock = listenerLock.writeLock();
- private int listenersLevel;
+ private final Queue buffer = new ConcurrentLinkedQueue<>();
/**
- * Constructs the singleton instance for the STATUS_LOGGER constant.
- *
- * This is now the logger level is set:
- *
- *
- * - If the property {@value Constants#LOG4J2_DEBUG} is {@code "true"}, then use {@link Level#TRACE}, otherwise,
- * - Use {@link Level#ERROR}
- *
- *
- * This is now the listener level is set:
- *
- *
- * - If the property {@value #DEFAULT_STATUS_LISTENER_LEVEL} is set, then use it, otherwise,
- * - Use {@link Level#WARN}
- *
- *
- * See:
- *
- * - LOG4J2-1813 Provide shorter and more intuitive way to switch on Log4j internal debug logging. If system property
- * "log4j2.debug" is defined, print all status logging.
- * - LOG4J2-3340 StatusLogger's log Level cannot be changed as advertised.
- *
- *
+ * Constructs the default instance.
+ */
+ private StatusLogger() {
+ this(
+ StatusLogger.class.getSimpleName(),
+ ParameterizedNoReferenceMessageFactory.INSTANCE,
+ Config.getInstance(),
+ new StatusConsoleListener(Config.getInstance().fallbackListenerLevel));
+ }
+
+ /**
+ * Constructs an instance using given properties.
+ * Users should not create new instances, but use {@link #getLogger()} instead!
*
- * @param name The logger name.
- * @param messageFactory The message factory.
+ * @param name the logger name
+ * @param messageFactory the message factory
+ * @param config the configuration
+ * @param fallbackListener the fallback listener
+ * @throws NullPointerException on null {@code name}, {@code messageFactory}, {@code config}, or {@code fallbackListener}
+ * @since 2.23.0
*/
- private StatusLogger(
- final String name, final MessageFactory messageFactory, final SimpleLoggerFactory loggerFactory) {
- super(name, messageFactory);
- final Level loggerLevel = DEBUG_ENABLED ? Level.TRACE : Level.ERROR;
- this.logger = loggerFactory.createSimpleLogger("StatusLogger", loggerLevel, messageFactory, System.err);
- this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
+ public StatusLogger(
+ final String name,
+ final MessageFactory messageFactory,
+ final Config config,
+ final StatusConsoleListener fallbackListener) {
+ super(requireNonNull(name, "name"), requireNonNull(messageFactory, "messageFactory"));
+ this.config = requireNonNull(config, "config");
+ this.fallbackListener = requireNonNull(fallbackListener, "fallbackListener");
+ this.listeners = new ArrayList<>();
}
/**
- * Retrieve the StatusLogger.
+ * Gets the static instance.
*
- * @return The StatusLogger.
+ * @return the singleton instance
*/
public static StatusLogger getLogger() {
- return STATUS_LOGGER;
+ return InstanceHolder.INSTANCE;
+ }
+
+ /**
+ * Sets the static (i.e., singleton) instance returned by {@link #getLogger()}.
+ * This method is intended for testing purposes and can have unforeseen consequences if used in production code.
+ *
+ * @param logger a logger instance
+ * @throws NullPointerException on null {@code logger}
+ * @since 2.23.0
+ */
+ public static void setLogger(final StatusLogger logger) {
+ InstanceHolder.INSTANCE = requireNonNull(logger, "logger");
}
+ /**
+ * Returns the fallback listener.
+ *
+ * @return the fallback listener
+ */
+ public StatusConsoleListener getFallbackListener() {
+ return fallbackListener;
+ }
+
+ /**
+ * Sets the level of the fallback listener.
+ *
+ * @param level a level
+ * @deprecated Instead use the {@link StatusConsoleListener#setLevel(Level) setLevel(Level)} method on the fallback listener returned by {@link #getFallbackListener()}.
+ */
+ @Deprecated
public void setLevel(final Level level) {
- logger.setLevel(level);
+ requireNonNull(level, "level");
+ fallbackListener.setLevel(level);
}
/**
* Registers a new listener.
*
- * @param listener The StatusListener to register.
+ * @param listener a listener to register
*/
public void registerListener(final StatusListener listener) {
- listenersLock.writeLock().lock();
+ requireNonNull(listener, "listener");
+ listenerWriteLock.lock();
try {
listeners.add(listener);
- final Level lvl = listener.getStatusLevel();
- if (listenersLevel < lvl.intLevel()) {
- listenersLevel = lvl.intLevel();
- }
} finally {
- listenersLock.writeLock().unlock();
+ listenerWriteLock.unlock();
}
}
/**
- * Removes a StatusListener.
+ * Removes the given listener.
*
- * @param listener The StatusListener to remove.
+ * @param listener a listener to remove
*/
public void removeListener(final StatusListener listener) {
- closeSilently(listener);
- listenersLock.writeLock().lock();
+ requireNonNull(listener, "listener");
+ listenerWriteLock.lock();
try {
listeners.remove(listener);
- int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
- for (final StatusListener statusListener : listeners) {
- final int level = statusListener.getStatusLevel().intLevel();
- if (lowest < level) {
- lowest = level;
- }
- }
- listenersLevel = lowest;
+ closeListenerSafely(listener);
} finally {
- listenersLock.writeLock().unlock();
+ listenerWriteLock.unlock();
}
}
- public void updateListenerLevel(final Level status) {
- if (status.intLevel() > listenersLevel) {
- listenersLevel = status.intLevel();
- }
+ /**
+ * Sets the level of the fallback listener.
+ *
+ * @param level a level
+ * @deprecated Instead use the {@link StatusConsoleListener#setLevel(Level) setLevel(Level)} method on the fallback listener returned by {@link #getFallbackListener()}.
+ */
+ @Deprecated
+ public void updateListenerLevel(final Level level) {
+ requireNonNull(level, "level");
+ fallbackListener.setLevel(level);
}
/**
- * Returns a thread safe Iterable for the StatusListener.
+ * Returns the listener collection.
*
- * @return An Iterable for the list of StatusListeners.
+ * @return a thread-safe read-only collection of listeners
*/
public Iterable getListeners() {
- return listeners;
+ listenerReadLock.lock();
+ try {
+ return Collections.unmodifiableCollection(listeners);
+ } finally {
+ listenerReadLock.unlock();
+ }
}
/**
- * Clears the list of status events and listeners.
+ * Clears the event buffer and removes the registered (not the fallback one!) listeners.
*/
public void reset() {
- listenersLock.writeLock().lock();
+ listenerWriteLock.lock();
try {
- for (final StatusListener listener : listeners) {
- closeSilently(listener);
+ final Iterator listenerIterator = listeners.iterator();
+ while (listenerIterator.hasNext()) {
+ final StatusListener listener = listenerIterator.next();
+ closeListenerSafely(listener);
+ listenerIterator.remove();
}
} finally {
- listeners.clear();
- listenersLock.writeLock().unlock();
- // note this should certainly come after the unlock to avoid unnecessary nested locking
- clear();
+ listenerWriteLock.unlock();
}
+ buffer.clear();
}
- private static void closeSilently(final Closeable resource) {
+ private static void closeListenerSafely(final StatusListener listener) {
try {
- resource.close();
- } catch (final IOException ignored) {
- // ignored
+ listener.close();
+ } catch (final IOException error) {
+ final String message = String.format("failed closing listener: %s", listener);
+ new RuntimeException(message, error).printStackTrace(System.err);
}
}
/**
- * Returns a List of all events as StatusData objects.
+ * Returns buffered events.
*
- * @return The list of StatusData objects.
+ * @deprecated Instead of relying on the buffering provided by {@code StatusLogger}, users should register their own listeners to access to logged events.
+ * @return a thread-safe read-only collection of buffered events
*/
+ @Deprecated
public List getStatusData() {
- msgLock.lock();
- try {
- return new ArrayList<>(messages);
- } finally {
- msgLock.unlock();
- }
+ // Wrapping the buffer clone with an unmodifiable list.
+ // By disallowing modifications, we make it clear to the user that mutations will not get propagated.
+ // `Collections.unmodifiableList(new ArrayList<>(...))` should be replaced with `List.of()` in Java 9+.
+ return Collections.unmodifiableList(new ArrayList<>(buffer));
}
/**
- * Clears the list of status events.
+ * Clears the event buffer.
+ *
+ * @deprecated Instead of relying on the buffering provided by {@code StatusLogger}, users should register their own listeners to access to logged events.
*/
+ @Deprecated
public void clear() {
- msgLock.lock();
- try {
- messages.clear();
- } finally {
- msgLock.unlock();
- }
+ buffer.clear();
}
+ /**
+ * Returns the least specific level among listeners, if registered any; otherwise, the fallback listener level.
+ *
+ * @return the least specific listener level, if registered any; otherwise, the fallback listener level
+ */
@Override
public Level getLevel() {
- return logger.getLevel();
+ Level leastSpecificLevel = fallbackListener.getStatusLevel();
+ // noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
+ for (int listenerIndex = 0; listenerIndex < listeners.size(); listenerIndex++) {
+ final StatusListener listener = listeners.get(listenerIndex);
+ final Level listenerLevel = listener.getStatusLevel();
+ if (listenerLevel.isLessSpecificThan(leastSpecificLevel)) {
+ leastSpecificLevel = listenerLevel;
+ }
+ }
+ return leastSpecificLevel;
}
- /**
- * Adds an event.
- *
- * @param marker The Marker
- * @param fqcn The fully qualified class name of the caller
- * @param level The logging level
- * @param msg The message associated with the event.
- * @param t A Throwable or null.
- */
@Override
public void logMessage(
- final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable t) {
- StackTraceElement element = null;
- if (fqcn != null) {
- element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
+ final String fqcn,
+ final Level level,
+ final Marker marker,
+ final Message message,
+ final Throwable throwable) {
+ final StatusData statusData = createStatusData(fqcn, level, message, throwable);
+ buffer(statusData);
+ notifyListeners(statusData);
+ }
+
+ private void buffer(final StatusData statusData) {
+ if (config.bufferCapacity == 0) {
+ return;
+ }
+ buffer.add(statusData);
+ while (buffer.size() >= config.bufferCapacity) {
+ buffer.remove();
}
- final StatusData data = new StatusData(element, level, msg, t, null);
- msgLock.lock();
+ }
+
+ private void notifyListeners(final StatusData statusData) {
+ final boolean foundListeners;
+ listenerReadLock.lock();
try {
- messages.add(data);
+ foundListeners = !listeners.isEmpty();
+ listeners.forEach(listener -> notifyListener(listener, statusData));
} finally {
- msgLock.unlock();
+ listenerReadLock.unlock();
}
- // LOG4J2-1813 if system property "log4j2.debug" is defined, all status logging is enabled
- if (DEBUG_ENABLED || (listeners.size() <= 0)) {
- logger.logMessage(fqcn, level, marker, msg, t);
- } else {
- for (final StatusListener listener : listeners) {
- if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
- listener.log(data);
- }
- }
+ if (!foundListeners) {
+ notifyListener(fallbackListener, statusData);
+ }
+ }
+
+ private void notifyListener(final StatusListener listener, final StatusData statusData) {
+ if (config.debugEnabled || listener.getStatusLevel().isLessSpecificThan(statusData.getLevel())) {
+ listener.log(statusData);
}
}
- private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
+ private StatusData createStatusData(
+ @Nullable final String fqcn,
+ final Level level,
+ final Message message,
+ @Nullable final Throwable throwable) {
+ final StackTraceElement caller = getStackTraceElement(fqcn);
+ return new StatusData(caller, level, message, throwable, null, config.instantFormatter);
+ }
+
+ @Nullable
+ private static StackTraceElement getStackTraceElement(@Nullable final String fqcn) {
if (fqcn == null) {
return null;
}
boolean next = false;
+ final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (final StackTraceElement element : stackTrace) {
final String className = element.getClassName();
if (next && !fqcn.equals(className)) {
@@ -312,7 +575,7 @@ private StackTraceElement getStackTraceElement(final String fqcn, final StackTra
}
if (fqcn.equals(className)) {
next = true;
- } else if (NOT_AVAIL.equals(className)) {
+ } else if ("?".equals(className)) {
break;
}
}
@@ -320,7 +583,7 @@ private StackTraceElement getStackTraceElement(final String fqcn, final StackTra
}
@Override
- public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
+ public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable throwable) {
return isEnabled(level, marker);
}
@@ -462,53 +725,24 @@ public boolean isEnabled(
}
@Override
- public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) {
+ public boolean isEnabled(
+ final Level level, final Marker marker, final CharSequence message, final Throwable throwable) {
return isEnabled(level, marker);
}
@Override
- public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
+ public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable throwable) {
return isEnabled(level, marker);
}
@Override
- public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
+ public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable throwable) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker) {
- if (DEBUG_ENABLED) {
- return true;
- }
- if (listeners.size() > 0) {
- return listenersLevel >= level.intLevel();
- }
- return logger.isEnabled(level, marker);
- }
-
- /**
- * Queues for status events.
- *
- * @param Object type to be stored in the queue.
- */
- private class BoundedQueue extends ConcurrentLinkedQueue {
-
- private static final long serialVersionUID = -3945953719763255337L;
-
- private final int size;
-
- BoundedQueue(final int size) {
- this.size = size;
- }
-
- @Override
- public boolean add(final E object) {
- super.add(object);
- while (messages.size() > size) {
- messages.poll();
- }
- return size > 0;
- }
+ requireNonNull(level, "level");
+ return getLevel().isLessSpecificThan(level);
}
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/package-info.java
index 61af0c23479..407e82cfc0a 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/status/package-info.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/package-info.java
@@ -19,7 +19,7 @@
* used by applications reporting on the status of the logging system
*/
@Export
-@Version("2.20.2")
+@Version("2.23.0")
package org.apache.logging.log4j.status;
import org.osgi.annotation.bundle.Export;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Base64Util.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Base64Util.java
index 34b272856fb..f96bffd28d4 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Base64Util.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Base64Util.java
@@ -18,7 +18,9 @@
import java.lang.reflect.Method;
import java.nio.charset.Charset;
+import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LoggingException;
+import org.apache.logging.log4j.status.StatusLogger;
/**
* Base64 encodes Strings. This utility is only necessary because the mechanism to do this changed in Java 8 and
@@ -26,6 +28,8 @@
*/
public final class Base64Util {
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
private static Method encodeMethod = null;
private static Object encoder = null;
@@ -41,7 +45,7 @@ public final class Base64Util {
final Class> clazz = LoaderUtil.loadClass("javax.xml.bind.DataTypeConverter");
encodeMethod = clazz.getMethod("printBase64Binary");
} catch (Exception ex2) {
- LowLevelLogUtil.logException("Unable to create a Base64 Encoder", ex2);
+ LOGGER.error("Unable to create a Base64 Encoder", ex2);
}
}
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java
index e12841cee27..dc73c5056f6 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java
@@ -20,6 +20,8 @@
import aQute.bnd.annotation.spi.ServiceProvider;
import java.util.Collection;
import java.util.Map;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
/**
* PropertySource implementation that uses environment variables as a source.
@@ -33,7 +35,10 @@
@ServiceProvider(value = PropertySource.class, resolution = Resolution.OPTIONAL)
public class EnvironmentPropertySource implements PropertySource {
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
private static final String PREFIX = "LOG4J_";
+
private static final int DEFAULT_PRIORITY = 100;
@Override
@@ -41,10 +46,9 @@ public int getPriority() {
return DEFAULT_PRIORITY;
}
- private void logException(final SecurityException e) {
+ private void logException(final SecurityException error) {
// There is no status logger yet.
- LowLevelLogUtil.logException(
- "The system environment variables are not available to Log4j due to security restrictions: " + e, e);
+ LOGGER.error("The system environment variables are not available to Log4j due to security restrictions", error);
}
@Override
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
index 51ad5675eac..001ad3a5f5b 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
@@ -27,6 +27,8 @@
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.function.Supplier;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
/**
* Consider this class private. Utility class for ClassLoaders.
@@ -39,6 +41,8 @@
@InternalApi
public final class LoaderUtil {
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
/**
* System property to set to ignore the thread context ClassLoader.
*
@@ -178,8 +182,8 @@ public static boolean isClassAvailable(final String className) {
return true;
} catch (final ClassNotFoundException | LinkageError e) {
return false;
- } catch (final Throwable e) {
- LowLevelLogUtil.logException("Unknown error checking for existence of class: " + className, e);
+ } catch (final Throwable error) {
+ LOGGER.error("Unknown error while checking existence of class `{}`", className, error);
return false;
}
}
@@ -474,8 +478,8 @@ static Collection findUrlResources(final String resource, final boo
while (resourceEnum.hasMoreElements()) {
resources.add(new UrlResource(cl, resourceEnum.nextElement()));
}
- } catch (final IOException e) {
- LowLevelLogUtil.logException(e);
+ } catch (final IOException error) {
+ LOGGER.error("failed to collect resources of name `{}`", resource, error);
}
}
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LowLevelLogUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LowLevelLogUtil.java
deleted file mode 100644
index 8f288566879..00000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/LowLevelLogUtil.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.util;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.io.Writer;
-import java.util.Objects;
-
-/**
- * PrintWriter-based logging utility for classes too low level to use {@link org.apache.logging.log4j.status.StatusLogger}.
- * Such classes cannot use StatusLogger as StatusLogger or {@link org.apache.logging.log4j.simple.SimpleLogger} depends
- * on them for initialization. Other framework classes should stick to using StatusLogger.
- *
- * @since 2.6
- */
-final class LowLevelLogUtil {
-
- /*
- * https://errorprone.info/bugpattern/DefaultCharset
- *
- * We intentionally use the system encoding.
- */
- @SuppressWarnings("DefaultCharset")
- private static PrintWriter writer = new PrintWriter(System.err, true);
-
- /**
- * Logs the given message.
- *
- * @param message the message to log
- * @since 2.9.2
- */
- public static void log(final String message) {
- if (message != null) {
- writer.println(message);
- }
- }
-
- @SuppressFBWarnings(
- value = "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE",
- justification = "Log4j prints stacktraces only to logs, which should be private.")
- public static void logException(final Throwable exception) {
- if (exception != null) {
- exception.printStackTrace(writer);
- }
- }
-
- public static void logException(final String message, final Throwable exception) {
- log(message);
- logException(exception);
- }
-
- /**
- * Sets the underlying OutputStream where exceptions are printed to.
- *
- * @param out the OutputStream to log to
- */
- @SuppressWarnings("DefaultCharset")
- public static void setOutputStream(final OutputStream out) {
- /*
- * https://errorprone.info/bugpattern/DefaultCharset
- *
- * We intentionally use the system encoding.
- */
- LowLevelLogUtil.writer = new PrintWriter(Objects.requireNonNull(out), true);
- }
-
- /**
- * Sets the underlying Writer where exceptions are printed to.
- *
- * @param writer the Writer to log to
- */
- public static void setWriter(final Writer writer) {
- LowLevelLogUtil.writer = new PrintWriter(Objects.requireNonNull(writer), true);
- }
-
- private LowLevelLogUtil() {}
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java
index 95f5588e829..d061ccbd6db 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java
@@ -19,6 +19,7 @@
import java.lang.invoke.MethodHandles.Lookup;
import java.util.Objects;
import java.util.stream.Stream;
+import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.status.StatusLogger;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -26,6 +27,8 @@
public class OsgiServiceLocator {
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
private static final boolean OSGI_AVAILABLE = checkOsgiAvailable();
private static boolean checkOsgiAvailable() {
@@ -38,8 +41,8 @@ private static boolean checkOsgiAvailable() {
return clazz.getMethod("getBundle", Class.class).invoke(null, OsgiServiceLocator.class) != null;
} catch (final ClassNotFoundException | NoSuchMethodException | LinkageError e) {
return false;
- } catch (final Throwable e) {
- LowLevelLogUtil.logException("Unknown error checking OSGI environment.", e);
+ } catch (final Throwable error) {
+ LOGGER.error("Unknown error checking OSGI environment.", error);
return false;
}
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
index 3084a6d816d..daf9163203a 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
@@ -37,6 +37,8 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
/**
* Consider this class private.
@@ -54,8 +56,12 @@
@ServiceConsumer(value = PropertySource.class, resolution = Resolution.OPTIONAL, cardinality = Cardinality.MULTIPLE)
public final class PropertiesUtil {
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
private static final String LOG4J_PROPERTIES_FILE_NAME = "log4j2.component.properties";
+
private static final String LOG4J_SYSTEM_PROPERTIES_FILE_NAME = "log4j2.system.properties";
+
private static final Lazy COMPONENT_PROPERTIES =
Lazy.lazy(() -> new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME, false));
@@ -104,13 +110,13 @@ static Properties loadClose(final InputStream in, final Object source) {
if (null != in) {
try {
props.load(in);
- } catch (final IOException e) {
- LowLevelLogUtil.logException("Unable to read " + source, e);
+ } catch (final IOException error) {
+ LOGGER.error("Unable to read source `{}`", source, error);
} finally {
try {
in.close();
- } catch (final IOException e) {
- LowLevelLogUtil.logException("Unable to close " + source, e);
+ } catch (final IOException error) {
+ LOGGER.error("Unable to close source `{}`", source, error);
}
}
}
@@ -238,8 +244,11 @@ public Charset getCharsetProperty(final String name, final Charset defaultValue)
return Charset.forName(mapped);
}
}
- LowLevelLogUtil.log("Unable to get Charset '" + charsetName + "' for property '" + name + "', using default "
- + defaultValue + " and continuing.");
+ LOGGER.error(
+ "Unable to read charset `{}` from property `{}`. Falling back to the default: `{}`",
+ charsetName,
+ name,
+ defaultValue);
return defaultValue;
}
@@ -424,8 +433,8 @@ public String getStringProperty(final String name, final String defaultValue) {
public static Properties getSystemProperties() {
try {
return new Properties(System.getProperties());
- } catch (final SecurityException ex) {
- LowLevelLogUtil.logException("Unable to access system properties.", ex);
+ } catch (final SecurityException error) {
+ LOGGER.error("Unable to access system properties.", error);
// Sandboxed - can't read System Properties
return new Properties();
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java
index 31d28d4f2c0..3440af04602 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java
@@ -21,6 +21,8 @@
import java.io.InputStream;
import java.net.URL;
import java.util.Properties;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
/**
* PropertySource backed by a properties file. Follows the same conventions as {@link PropertiesPropertySource}.
@@ -29,6 +31,8 @@
*/
public class PropertyFilePropertySource extends PropertiesPropertySource {
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
public PropertyFilePropertySource(final String fileName) {
this(fileName, true);
}
@@ -45,8 +49,8 @@ private static Properties loadPropertiesFile(final String fileName, final boolea
for (final URL url : LoaderUtil.findResources(fileName, useTccl)) {
try (final InputStream in = url.openStream()) {
props.load(in);
- } catch (final IOException e) {
- LowLevelLogUtil.logException("Unable to read " + url, e);
+ } catch (final IOException error) {
+ LOGGER.error("Unable to read URL `{}`", url, error);
}
}
return props;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java
index 7fff12d1667..bc20af0daf3 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java
@@ -20,6 +20,8 @@
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.Predicate;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
/**
* Consider this class private. Provides various methods to determine the caller class.
@@ -51,6 +53,8 @@
@InternalApi
public final class StackLocator {
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
/** TODO Consider removing now that we require Java 8. */
static final int JDK_7U25_OFFSET;
@@ -75,18 +79,18 @@ public final class StackLocator {
} else {
o = getCallerClassMethod.invoke(null, 1);
if (o == sunReflectionClass) {
- LowLevelLogUtil.log(
- "WARNING: Unexpected result from sun.reflect.Reflection.getCallerClass(int), adjusting offset for future calls.");
+ LOGGER.warn(
+ "Unexpected result from `sun.reflect.Reflection.getCallerClass(int)`, adjusting offset for future calls.");
java7u25CompensationOffset = 1;
}
}
} catch (final Exception | LinkageError e) {
if (Constants.JAVA_MAJOR_VERSION > 8) {
- LowLevelLogUtil.log(
- "WARNING: Runtime environment or build system does not support multi-release JARs. This will impact location-based features.");
+ LOGGER.warn(
+ "Runtime environment or build system does not support multi-release JARs. This will impact location-based features.");
} else {
- LowLevelLogUtil.log(
- "WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact location-based features.");
+ LOGGER.warn(
+ "`sun.reflect.Reflection.getCallerClass(int)` is not supported. This will impact location-based features.");
}
getCallerClassMethod = null;
java7u25CompensationOffset = -1;
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java
index 7abb2dd95d0..c5e25a15c66 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.logging.log4j.core.appender;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -29,29 +30,35 @@
import org.apache.logging.log4j.status.StatusData;
import org.apache.logging.log4j.status.StatusLogger;
import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.SetSystemProperty;
/**
* OutputStreamManager Tests.
*/
-public class OutputStreamManagerTest {
+@SetSystemProperty(key = "log4j2.status.entries", value = "10")
+class OutputStreamManagerTest {
@Test
@LoggerContextSource("multipleIncompatibleAppendersTest.xml")
- public void narrow(final LoggerContext context) {
+ void narrow(final LoggerContext context) {
final Logger logger = context.getLogger(OutputStreamManagerTest.class);
logger.info("test");
- final List statusData = StatusLogger.getLogger().getStatusData();
- StatusData data = statusData.get(0);
- if (data.getMessage().getFormattedMessage().contains("WindowsAnsiOutputStream")) {
- data = statusData.get(1);
+ StatusLogger statusLogger = StatusLogger.getLogger();
+ final List events = statusLogger.getStatusData();
+ assertThat(events).isNotEmpty();
+ StatusData event = events.get(0);
+ if (event.getMessage().getFormattedMessage().contains("WindowsAnsiOutputStream")) {
+ event = events.get(1);
}
- assertEquals(Level.ERROR, data.getLevel());
- assertEquals(
- "Could not create plugin of type class org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender for element RollingRandomAccessFile",
- data.getMessage().getFormattedMessage());
- assertEquals(
- "org.apache.logging.log4j.core.config.ConfigurationException: Configuration has multiple incompatible Appenders pointing to the same resource 'target/multiIncompatibleAppender.log'",
- data.getThrowable().toString());
+ assertThat(event.getLevel()).isEqualTo(Level.ERROR);
+ assertThat(event.getMessage().getFormattedMessage())
+ .isEqualTo(
+ "Could not create plugin of type class org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender for element RollingRandomAccessFile");
+ assertThat(event.getThrowable())
+ .isNotNull()
+ .asString()
+ .isEqualTo(
+ "org.apache.logging.log4j.core.config.ConfigurationException: Configuration has multiple incompatible Appenders pointing to the same resource 'target/multiIncompatibleAppender.log'");
}
@Test
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.java
index 49147e3805e..f9f37521857 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.java
@@ -26,15 +26,16 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.status.StatusData;
import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.test.junit.StatusLoggerLevel;
import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.SetSystemProperty;
-@StatusLoggerLevel("WARN")
-public class AbstractActionTest {
+@SetSystemProperty(key = "log4j2.status.entries", value = "10")
+@SetSystemProperty(key = "log4j2.StatusLogger.level", value = "WARN")
+class AbstractActionTest {
// Test for LOG4J2-2658
@Test
- public void testExceptionsAreLoggedToStatusLogger() {
+ void testExceptionsAreLoggedToStatusLogger() {
final StatusLogger statusLogger = StatusLogger.getLogger();
statusLogger.clear();
new TestAction().run();
@@ -53,7 +54,7 @@ public void testExceptionsAreLoggedToStatusLogger() {
}
@Test
- public void testRuntimeExceptionsAreLoggedToStatusLogger() {
+ void testRuntimeExceptionsAreLoggedToStatusLogger() {
final StatusLogger statusLogger = StatusLogger.getLogger();
statusLogger.clear();
new AbstractAction() {
@@ -71,7 +72,7 @@ public boolean execute() {
}
@Test
- public void testErrorsAreLoggedToStatusLogger() {
+ void testErrorsAreLoggedToStatusLogger() {
final StatusLogger statusLogger = StatusLogger.getLogger();
statusLogger.clear();
new AbstractAction() {
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.java
index f8cde81950e..8762a4a4912 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.java
@@ -33,14 +33,16 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+import org.junitpioneer.jupiter.SetSystemProperty;
/**
* Tests the And composite condition.
*/
-public class IfAllTest {
+@SetSystemProperty(key = "log4j2.status.entries", value = "10")
+class IfAllTest {
@Test
- public void testAccept() {
+ void testAccept() {
final PathCondition TRUE = new FixedCondition(true);
final PathCondition FALSE = new FixedCondition(false);
assertTrue(IfAll.createAndCondition(TRUE, TRUE).accept(null, null, null));
@@ -50,12 +52,12 @@ public void testAccept() {
}
@Test
- public void testEmptyIsFalse() {
+ void testEmptyIsFalse() {
assertFalse(IfAll.createAndCondition().accept(null, null, null));
}
@Test
- public void testBeforeTreeWalk() {
+ void testBeforeTreeWalk() {
final CountingCondition counter = new CountingCondition(true);
final IfAll and = IfAll.createAndCondition(counter, counter, counter);
and.beforeFileTreeWalk();
@@ -63,14 +65,14 @@ public void testBeforeTreeWalk() {
}
@Test
- public void testCreateAndConditionCalledProgrammaticallyThrowsNPEWhenComponentsNotSpecified() {
+ void testCreateAndConditionCalledProgrammaticallyThrowsNPEWhenComponentsNotSpecified() {
PathCondition[] components = null;
assertThrows(NullPointerException.class, () -> IfAll.createAndCondition(components));
}
@ParameterizedTest
@ValueSource(strings = "No components provided for IfAll")
- public void testCreateAndConditionCalledByPluginBuilderReturnsNullAndLogsMessageWhenComponentsNotSpecified(
+ void testCreateAndConditionCalledByPluginBuilderReturnsNullAndLogsMessageWhenComponentsNotSpecified(
final String expectedMessage) {
final PluginEntry nullEntry = null;
final PluginType type = new PluginType<>(nullEntry, IfAll.class, "Dummy");
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.java
index aac3eaa326e..7ed92516851 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.java
@@ -33,11 +33,13 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+import org.junitpioneer.jupiter.SetSystemProperty;
/**
* Tests the Or composite condition.
*/
-public class IfAnyTest {
+@SetSystemProperty(key = "log4j2.status.entries", value = "10")
+class IfAnyTest {
@Test
public void test() {
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.java
index b731d25f118..333af1ebd46 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.java
@@ -35,11 +35,13 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+import org.junitpioneer.jupiter.SetSystemProperty;
/**
* Tests the FileAgeFilter class.
*/
-public class IfLastModifiedTest {
+@SetSystemProperty(key = "log4j2.status.entries", value = "10")
+class IfLastModifiedTest {
@Test
public void testGetDurationReturnsConstructorValue() {
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.java
index 698c6d6dee5..2398fb2a501 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.java
@@ -33,11 +33,13 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+import org.junitpioneer.jupiter.SetSystemProperty;
/**
* Tests the Not composite condition.
*/
-public class IfNotTest {
+@SetSystemProperty(key = "log4j2.status.entries", value = "10")
+class IfNotTest {
@Test
public void test() {
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java
index 48d40a263b9..57734ed983e 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java
@@ -27,8 +27,6 @@
import java.util.List;
import java.util.Stack;
import java.util.concurrent.CountDownLatch;
-import java.util.stream.Collectors;
-import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LogEvent;
@@ -39,9 +37,7 @@
import org.apache.logging.log4j.core.jmx.RingBufferAdmin;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.ReflectionUtil;
-import org.apache.logging.log4j.status.StatusData;
import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.test.ListStatusListener;
import org.apache.logging.log4j.test.junit.UsingStatusListener;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Timeout;
@@ -84,35 +80,6 @@ public void run() {
}
}
- protected static class DomainObject {
-
- private final Logger innerLogger;
- private final Unlocker unlocker;
- private final int count;
-
- public DomainObject(final Logger innerLogger, final Unlocker unlocker, final int loggingCount) {
- this.innerLogger = innerLogger;
- this.unlocker = unlocker;
- this.count = loggingCount;
- }
-
- @Override
- public String toString() {
- for (int i = 0; i < count; i++) {
- LOGGER.info(
- "DomainObject logging message {}. Ring buffer capacity was {}, countdown latch was {}.",
- i,
- asyncRemainingCapacity(innerLogger),
- unlocker.countDownLatch.getCount());
- unlocker.countDownLatch.countDown();
- innerLogger.info("Logging in toString() #{}", i);
- }
- return "Who's bad?!";
- }
- }
-
- private ListStatusListener statusListener;
-
protected void testNormalQueueFullKeepsMessagesInOrder(
final LoggerContext ctx, final BlockingAppender blockingAppender) throws Exception {
checkConfig(ctx);
@@ -157,63 +124,6 @@ protected static void asyncTest(
assertThat(actual).isEmpty();
}
- public void testLoggingFromToStringCausesOutOfOrderMessages(
- final LoggerContext ctx, final BlockingAppender blockingAppender) throws Exception {
- checkConfig(ctx);
- // Non-reusable messages will call `toString()` on the main thread and block it.
- final Logger logger = ctx.getLogger(this.getClass());
-
- blockingAppender.countDownLatch = new CountDownLatch(1);
- final Unlocker unlocker = new Unlocker(new CountDownLatch(MESSAGE_COUNT - 1), blockingAppender);
- unlocker.start();
- asyncRecursiveTest(logger, unlocker, blockingAppender);
- unlocker.join();
- }
-
- void asyncRecursiveTest(final Logger logger, final Unlocker unlocker, final BlockingAppender blockingAppender) {
- for (int i = 0; i < 1; i++) {
- LOGGER.info(
- "Test logging message {}. Ring buffer capacity was {}, countdown latch was {}.",
- i,
- asyncRemainingCapacity(logger),
- unlocker.countDownLatch.getCount());
- unlocker.countDownLatch.countDown();
- final DomainObject obj = new DomainObject(logger, unlocker, MESSAGE_COUNT - 1);
- logger.info("Logging naughty object #{}: {}", i, obj);
- }
-
- LOGGER.info(
- "Waiting for message delivery: blockingAppender.logEvents.count={}.",
- blockingAppender.logEvents.size());
- while (blockingAppender.logEvents.size() < MESSAGE_COUNT) {
- Thread.yield();
- }
- LOGGER.info(
- "All {} messages have been delivered: blockingAppender.logEvents.count={}.",
- MESSAGE_COUNT,
- blockingAppender.logEvents.size());
-
- final StatusData mostRecentStatusData = statusListener
- .findStatusData(Level.WARN)
- .reduce((ignored, data) -> data)
- .orElse(null);
- assertThat(mostRecentStatusData).isNotNull();
- assertThat(mostRecentStatusData.getLevel()).isEqualTo(Level.WARN);
- assertThat(mostRecentStatusData.getFormattedStatus())
- .contains("Log4j2 logged an event out of order to prevent deadlock caused by domain "
- + "objects logging from their toString method when the async queue is full");
-
- final List actual = blockingAppender.logEvents.stream()
- .map(e -> e.getMessage().getFormattedMessage())
- .collect(Collectors.toList());
- final String[] expected = new String[MESSAGE_COUNT];
- for (int i = 0; i < MESSAGE_COUNT - 1; i++) {
- expected[i] = "Logging in toString() #" + i;
- }
- expected[MESSAGE_COUNT - 1] = "Logging naughty object #0: Who's bad?!";
- assertThat(actual).hasSize(MESSAGE_COUNT).contains(expected);
- }
-
static Stack transform(final List logEvents) {
final List filtered = new ArrayList<>(logEvents.size());
for (final LogEvent event : logEvents) {
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.java
similarity index 95%
rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java
rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.java
index 0e73bf8c2b1..43329dc1eae 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.java
@@ -24,7 +24,7 @@
/**
* Tests queue full scenarios with AsyncAppender.
*/
-public class QueueFullAsyncAppenderTest extends QueueFullAbstractTest {
+public class QueueFullAsyncAppender1Test extends QueueFullAbstractTest {
@Override
@Test
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender2Test.java
similarity index 94%
rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java
rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender2Test.java
index d3886818c8f..4eab6286a09 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender2Test.java
@@ -24,7 +24,7 @@
* is immutable.
*/
@SetTestProperty(key = "log4j2.formatMsgAsync", value = "true")
-public class QueueFullAsyncAppenderTest2 extends QueueFullAsyncAppenderTest {
+public class QueueFullAsyncAppender2Test extends QueueFullAsyncAppender1Test {
@Override
protected void checkConfig(final LoggerContext ctx) {
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger1Test.java
similarity index 96%
rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java
rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger1Test.java
index 1aaabfe48dd..fe3f42e043f 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger1Test.java
@@ -33,7 +33,7 @@
value = "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector")
@SetTestProperty(key = "log4j2.asyncLoggerRingBufferSize", value = "128")
@Tag(Tags.ASYNC_LOGGERS)
-public class QueueFullAsyncLoggerTest extends QueueFullAbstractTest {
+public class QueueFullAsyncLogger1Test extends QueueFullAbstractTest {
@Override
@Test
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger2Test.java
similarity index 95%
rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest2.java
rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger2Test.java
index 6271849278f..fbfce3951bb 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest2.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger2Test.java
@@ -27,7 +27,7 @@
*/
@SetTestProperty(key = "log4j2.formatMsgAsync", value = "true")
@Tag(Tags.ASYNC_LOGGERS)
-public class QueueFullAsyncLoggerTest2 extends QueueFullAsyncLoggerTest {
+public class QueueFullAsyncLogger2Test extends QueueFullAsyncLogger1Test {
@Override
protected void checkConfig(final LoggerContext ctx) {
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger3Test.java
similarity index 98%
rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java
rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger3Test.java
index 7a048091d26..3be89c6f9bd 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger3Test.java
@@ -44,7 +44,7 @@
@SetTestProperty(key = "log4j2.formatMsgAsync", value = "true")
@SetTestProperty(key = "log4j2.asyncQueueFullPolicy", value = "Discard")
@Tag(Tags.ASYNC_LOGGERS)
-public class QueueFullAsyncLoggerTest3 extends QueueFullAbstractTest {
+public class QueueFullAsyncLogger3Test extends QueueFullAbstractTest {
@Override
protected void checkConfig(final LoggerContext ctx) {
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.java
similarity index 96%
rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java
rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.java
index 93db0470ed4..e797792ddb1 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.java
@@ -29,7 +29,7 @@
*/
@SetTestProperty(key = "log4j2.asyncLoggerConfigRingBufferSize", value = "128")
@Tag(Tags.ASYNC_LOGGERS)
-public class QueueFullAsyncLoggerConfigTest extends QueueFullAbstractTest {
+public class QueueFullAsyncLoggerConfig1Test extends QueueFullAbstractTest {
@Override
@Test
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig2Test.java
similarity index 94%
rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest2.java
rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig2Test.java
index 4c56595a405..a88d4568bd0 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest2.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig2Test.java
@@ -26,7 +26,7 @@
*/
@SetTestProperty(key = "log4j2.formatMsgAsync", value = "true")
@Tag(Tags.ASYNC_LOGGERS)
-public class QueueFullAsyncLoggerConfigTest2 extends QueueFullAsyncLoggerConfigTest {
+public class QueueFullAsyncLoggerConfig2Test extends QueueFullAsyncLoggerConfig1Test {
@Override
protected void checkConfig(final LoggerContext ctx) throws ReflectiveOperationException {
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java
deleted file mode 100644
index e60e8bea227..00000000000
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.core.async;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
-import org.apache.logging.log4j.core.test.junit.Named;
-import org.apache.logging.log4j.core.test.junit.Tags;
-import org.apache.logging.log4j.test.junit.SetTestProperty;
-import org.apache.logging.log4j.util.Constants;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.Test;
-
-/**
- * Tests queue full scenarios with AsyncLoggers in configuration.
- */
-@SetTestProperty(key = "log4j2.enableThreadlocals", value = "true")
-@SetTestProperty(key = "log4j2.isWebapp", value = "false")
-@SetTestProperty(key = "log4j2.asyncLoggerConfigRingBufferSize", value = "128")
-@Tag(Tags.ASYNC_LOGGERS)
-public class QueueFullAsyncLoggerConfigLoggingFromToStringTest extends QueueFullAbstractTest {
-
- @Override
- @Test
- @LoggerContextSource
- public void testLoggingFromToStringCausesOutOfOrderMessages(
- final LoggerContext ctx, final @Named(APPENDER_NAME) BlockingAppender blockingAppender) throws Exception {
- super.testLoggingFromToStringCausesOutOfOrderMessages(ctx, blockingAppender);
- }
-
- @Override
- protected void checkConfig(final LoggerContext ctx) throws ReflectiveOperationException {
- assertAsyncLoggerConfig(ctx, 128);
- assertThat(Constants.IS_WEB_APP).isFalse();
- assertThat(Constants.ENABLE_THREADLOCALS).isTrue();
- }
-}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest.java
deleted file mode 100644
index 8e98f61c4ee..00000000000
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.core.async;
-
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
-import org.apache.logging.log4j.core.test.junit.Named;
-import org.apache.logging.log4j.core.test.junit.Tags;
-import org.apache.logging.log4j.core.util.Constants;
-import org.apache.logging.log4j.test.junit.SetTestProperty;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.Test;
-
-/**
- * Tests queue full scenarios with pure AsyncLoggers (all loggers async).
- */
-@SetTestProperty(
- key = Constants.LOG4J_CONTEXT_SELECTOR,
- value = "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector")
-@SetTestProperty(key = "log4j2.asyncLoggerRingBufferSize", value = "128")
-@Tag(Tags.ASYNC_LOGGERS)
-public class QueueFullAsyncLoggerLoggingFromToStringTest extends QueueFullAbstractTest {
-
- @Override
- @Test
- @LoggerContextSource
- public void testLoggingFromToStringCausesOutOfOrderMessages(
- final LoggerContext ctx, final @Named(APPENDER_NAME) BlockingAppender blockingAppender) throws Exception {
- super.testLoggingFromToStringCausesOutOfOrderMessages(ctx, blockingAppender);
- }
-
- @Override
- protected void checkConfig(final LoggerContext ctx) {
- assertAsyncLogger(ctx, 128);
- }
-}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java
deleted file mode 100644
index a75977e2f31..00000000000
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.core.async;
-
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.test.junit.Tags;
-import org.apache.logging.log4j.test.junit.SetTestProperty;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Tag;
-
-/**
- * Tests queue full scenarios with pure AsyncLoggers (all loggers async).
- */
-@SetTestProperty(key = "log4j2.formatMsgAsync", value = "true")
-@Tag(Tags.ASYNC_LOGGERS)
-public class QueueFullAsyncLoggerLoggingFromToStringTest2 extends QueueFullAsyncLoggerLoggingFromToStringTest {
-
- @Override
- @Disabled("Causes deadlocks")
- public void testLoggingFromToStringCausesOutOfOrderMessages(LoggerContext ctx, BlockingAppender blockingAppender)
- throws Exception {
- super.testLoggingFromToStringCausesOutOfOrderMessages(ctx, blockingAppender);
- }
-
- @Override
- protected void checkConfig(final LoggerContext ctx) {
- super.checkConfig(ctx);
- assertFormatMessagesInBackground();
- }
-}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java
index 325466cf3d6..70e824937e5 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java
@@ -16,8 +16,7 @@
*/
package org.apache.logging.log4j.core.config;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
@@ -26,21 +25,22 @@
import org.apache.logging.log4j.status.StatusData;
import org.apache.logging.log4j.status.StatusLogger;
import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.SetSystemProperty;
/**
* Tests LoggersPlugin.
*/
-@LoggerContextSource("multipleRootLoggersTest.xml")
+@SetSystemProperty(key = "log4j2.status.entries", value = "10")
public class LoggersPluginTest {
@Test
- public void testEmptyAttribute() throws Exception {
+ @LoggerContextSource("multipleRootLoggersTest.xml")
+ public void testEmptyAttribute() {
final Logger logger = LogManager.getLogger();
logger.info("Test");
final StatusData data = StatusLogger.getLogger().getStatusData().get(0);
- // System.out.println(data.getFormattedStatus());
- assertEquals(Level.ERROR, data.getLevel());
- assertTrue(data.getMessage().getFormattedMessage().contains("multiple root loggers"));
+ assertThat(data.getLevel()).isEqualTo(Level.ERROR);
+ assertThat(data.getMessage().getFormattedMessage()).contains("multiple root loggers");
}
}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.java
index 8d467fa6e4f..e7a13f12edc 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.java
@@ -17,66 +17,71 @@
package org.apache.logging.log4j.core.config.xml;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
-import java.util.stream.StreamSupport;
+import edu.umd.cs.findbugs.annotations.Nullable;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.status.StatusConsoleListener;
-import org.apache.logging.log4j.status.StatusListener;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.test.junit.SetTestProperty;
-import org.apache.logging.log4j.test.junit.UsingTestProperties;
+import org.apache.logging.log4j.test.junit.UsingStatusLoggerMock;
import org.junit.jupiter.api.Test;
-import org.opentest4j.AssertionFailedError;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
-@UsingTestProperties
-public class XmlConfigurationPropsTest {
+@UsingStatusLoggerMock
+@ExtendWith(MockitoExtension.class)
+class XmlConfigurationPropsTest {
private static final String CONFIG_NAME = "XmlConfigurationPropsTest";
+
private static final String CONFIG1 = "org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest1.xml";
- private static final String CONFIG1_NAME = "XmlConfigurationPropsTest1";
- private static StatusConsoleListener findStatusConsoleListener() {
- final Iterable listeners = StatusLogger.getLogger().getListeners();
- return StreamSupport.stream(listeners.spliterator(), false)
- .filter(StatusConsoleListener.class::isInstance)
- .map(StatusConsoleListener.class::cast)
- .findAny()
- .orElseThrow(() -> new AssertionFailedError("Missing console status listener."));
- }
+ private static final String CONFIG1_NAME = "XmlConfigurationPropsTest1";
private void testConfiguration(
final Configuration config,
final String expectedConfigName,
- final Level expectedStatusLevel,
- final Level expectedRootLevel) {
+ @Nullable final Level expectedStatusLevel,
+ @Nullable final Level expectedRootLevel) {
assertThat(config)
.isInstanceOf(XmlConfiguration.class)
.extracting(Configuration::getName)
.isEqualTo(expectedConfigName);
+ final StatusConsoleListener fallbackListener = StatusLogger.getLogger().getFallbackListener();
+ if (expectedStatusLevel == null) {
+ verify(fallbackListener, never()).setLevel(any());
+ } else {
+ verify(fallbackListener).setLevel(eq(expectedStatusLevel));
+ }
assertThat(config.getRootLogger().getExplicitLevel()).isEqualTo(expectedRootLevel);
- assertThat(findStatusConsoleListener().getStatusLevel()).isEqualTo(expectedStatusLevel);
}
@Test
+ @SetTestProperty(key = "status.level", value = "using gibberish values to enforce defaults")
+ @SetTestProperty(key = "root.level", value = "using gibberish values to enforce defaults")
@LoggerContextSource
- public void testNoProps(final Configuration config) {
- testConfiguration(config, CONFIG_NAME, Level.ERROR, null);
+ void testNoProps(final Configuration config) {
+ testConfiguration(config, CONFIG_NAME, null, null);
}
@Test
@SetTestProperty(key = Constants.LOG4J_DEFAULT_STATUS_LEVEL, value = "WARN")
@LoggerContextSource(value = CONFIG1)
- public void testDefaultStatus(final Configuration config) {
+ void testDefaultStatus(final Configuration config) {
testConfiguration(config, CONFIG1_NAME, Level.WARN, null);
}
@Test
@SetTestProperty(key = "status.level", value = "INFO")
@LoggerContextSource
- public void testWithConfigProp(final Configuration config) {
+ void testWithConfigProp(final Configuration config) {
testConfiguration(config, CONFIG_NAME, Level.INFO, null);
}
@@ -84,7 +89,7 @@ public void testWithConfigProp(final Configuration config) {
@SetTestProperty(key = "status.level", value = "INFO")
@SetTestProperty(key = "root.level", value = "DEBUG")
@LoggerContextSource
- public void testWithProps(final Configuration config) {
+ void testWithProps(final Configuration config) {
testConfiguration(config, CONFIG_NAME, Level.INFO, Level.DEBUG);
}
}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java
deleted file mode 100644
index 3eb9dd34960..00000000000
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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.core.impl;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.List;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.test.appender.ListAppender;
-import org.apache.logging.log4j.core.test.junit.LoggerContextRule;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-
-/**
- * There are two logger.info() calls here.
- *
- * The outer one (in main()) indirectly invokes the inner one in the Thing.toString() method.
- *
- * The inner one comes out cleanly, but leaves ReusableParameterizedMessage.indices altered and this messes up
- * the output of the outer call (which is still on the stack).
- *
- *
- * 16:35:34.781 INFO [main] problem.demo.apache.log4j2.Log4j2ProblemDemo - getX: values x=3 y=4 z=5
- * 16:35:34.782 INFO [main] problem.demo.apache.log4j2.Log4j2ProblemDemo - getX: values x=3 y=4 z=5[Thing x=3 y=4 z=5]
- *
- * @author lwest
- * @since 2016-09-14 in recursion
- */
-public class NestedLoggingFromToStringTest {
-
- @BeforeClass
- public static void beforeClass() {
- System.setProperty("log4j2.is.webapp", "false");
- }
-
- @Rule
- public LoggerContextRule context = new LoggerContextRule("log4j-sync-to-list.xml");
-
- private ListAppender listAppender;
- private Logger logger;
-
- @Before
- public void before() {
- listAppender = context.getListAppender("List");
- logger = LogManager.getLogger(NestedLoggingFromToStringTest.class);
- }
-
- static class ParameterizedLoggingThing {
- final Logger innerLogger = LogManager.getLogger(ParameterizedLoggingThing.class);
- private final int x = 3, y = 4, z = 5;
-
- public int getX() {
- innerLogger.debug("getX: values x={} y={} z={}", x, y, z);
- return x;
- }
-
- @Override
- public String toString() {
- return "[" + this.getClass().getSimpleName() + " x=" + getX() + " y=" + y + " z=" + z + "]";
- }
- }
-
- static class ObjectLoggingThing1 {
- final Logger innerLogger = LogManager.getLogger(ObjectLoggingThing1.class);
-
- public int getX() {
- innerLogger.trace(new ObjectLoggingThing2());
- return 999;
- }
-
- @Override
- public String toString() {
- return "[" + this.getClass().getSimpleName() + " y=" + getX() + "]";
- }
- }
-
- static class ObjectLoggingThing2 {
- final Logger innerLogger = LogManager.getLogger(ObjectLoggingThing2.class);
-
- public int getX() {
- innerLogger.trace(new ParameterizedLoggingThing());
- return 123;
- }
-
- @Override
- public String toString() {
- return "[" + this.getClass().getSimpleName() + " x=" + getX() + "]";
- }
- }
-
- @Test
- public void testNestedLoggingInLastArgument() {
- final ParameterizedLoggingThing it = new ParameterizedLoggingThing();
- logger.info("main: argCount={} it={}", "2", it);
- final List list = listAppender.getMessages();
-
- final String expect1 =
- "DEBUG org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest.ParameterizedLoggingThing getX: values x=3 y=4 z=5";
- final String expect2 =
- "INFO org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest main: argCount=2 it=[ParameterizedLoggingThing x=3 y=4 z=5]";
- assertEquals(expect1, list.get(0));
- assertEquals(expect2, list.get(1));
- }
-
- @Test
- public void testNestedLoggingInFirstArgument() {
- final ParameterizedLoggingThing it = new ParameterizedLoggingThing();
- logger.info("next: it={} some{} other{}", it, "AA", "BB");
- final List list = listAppender.getMessages();
-
- final String expect1 =
- "DEBUG org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest.ParameterizedLoggingThing getX: values x=3 y=4 z=5";
- final String expect2 =
- "INFO org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest next: it=[ParameterizedLoggingThing x=3 y=4 z=5] someAA otherBB";
- assertEquals(expect1, list.get(0));
- assertEquals(expect2, list.get(1));
- }
-
- @Test
- public void testDoublyNestedLogging() {
- logger.info(new ObjectLoggingThing1());
- final List list = listAppender.getMessages();
-
- final String expect1 =
- "DEBUG org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest.ParameterizedLoggingThing getX: values x=3 y=4 z=5";
- final String expect2 =
- "TRACE org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest.ObjectLoggingThing2 [ParameterizedLoggingThing x=3 y=4 z=5]";
- final String expect3 =
- "TRACE org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest.ObjectLoggingThing1 [ObjectLoggingThing2 x=123]";
- final String expect4 =
- "INFO org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest [ObjectLoggingThing1 y=999]";
- assertEquals(expect1, list.get(0));
- assertEquals(expect2, list.get(1));
- assertEquals(expect3, list.get(2));
- assertEquals(expect4, list.get(3));
- }
-}
diff --git a/log4j-core-test/src/test/resources/log4j-sync-to-list.xml b/log4j-core-test/src/test/resources/log4j-sync-to-list.xml
deleted file mode 100644
index e0e95c2fd0e..00000000000
--- a/log4j-core-test/src/test/resources/log4j-sync-to-list.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.xml
similarity index 100%
rename from log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.xml
rename to log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.xml
diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.xml
similarity index 100%
rename from log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.xml
rename to log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.xml
diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.xml
deleted file mode 100644
index 5322b67a1e6..00000000000
--- a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java
index 616bdfd0881..1a1c22835a7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java
@@ -16,38 +16,37 @@
*/
package org.apache.logging.log4j.core.config.status;
+import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.util.FileUtils;
import org.apache.logging.log4j.core.util.NetUtils;
import org.apache.logging.log4j.status.StatusConsoleListener;
-import org.apache.logging.log4j.status.StatusListener;
import org.apache.logging.log4j.status.StatusLogger;
/**
- * Configuration for setting up {@link StatusConsoleListener} instances.
+ * Configuration for setting up the {@link StatusLogger} fallback listener.
*/
public class StatusConfiguration {
- @SuppressWarnings("UseOfSystemOutOrSystemErr")
- private static final PrintStream DEFAULT_STREAM = System.out;
+ private static final StatusLogger LOGGER = StatusLogger.getLogger();
- private static final Level DEFAULT_STATUS = Level.ERROR;
-
- private final Collection errorMessages = new LinkedBlockingQueue<>();
- private final StatusLogger logger = StatusLogger.getLogger();
+ private final Lock lock = new ReentrantLock();
private volatile boolean initialized;
- private PrintStream destination = DEFAULT_STREAM;
- private Level status = DEFAULT_STATUS;
+ @Nullable
+ private PrintStream output;
+
+ @Nullable
+ private Level level;
/**
* Specifies how verbose the StatusLogger should be.
@@ -72,82 +71,86 @@ public static Verbosity toVerbosity(final String value) {
}
/**
- * Logs an error message to the StatusLogger. If the StatusLogger hasn't been set up yet, queues the message to be
- * logged after initialization.
+ * Logs an error message to the {@link StatusLogger}.
*
- * @param message error message to log.
+ * @param message error message to log
+ * @deprecated Use {@link StatusLogger#getLogger()} and then {@link StatusLogger#error(String)} instead.
*/
+ @Deprecated
public void error(final String message) {
- if (!this.initialized) {
- this.errorMessages.add(message);
- } else {
- this.logger.error(message);
- }
+ LOGGER.error(message);
}
/**
- * Specifies the destination for StatusLogger events. This can be {@code out} (default) for using
- * {@link System#out standard out}, {@code err} for using {@link System#err standard error}, or a file URI to
- * which log events will be written. If the provided URI is invalid, then the default destination of standard
- * out will be used.
+ * Sets the output of the {@link StatusLogger} fallback listener.
+ *
+ * Accepted values are as follows:
+ *
+ *
+ * - {@code out} (i.e., {@link System#out})
+ * - {@code err} (i.e., {@link System#err})
+ * - a URI (e.g., {@code file:///path/to/log4j-status-logs.txt})
+ *
+ *
+ * Invalid values will be ignored.
+ *
*
- * @param destination where status log messages should be output.
+ * @param destination destination where {@link StatusLogger} messages should be output
* @return {@code this}
*/
- public StatusConfiguration withDestination(final String destination) {
+ public StatusConfiguration withDestination(@Nullable final String destination) {
try {
- this.destination = parseStreamName(destination);
- } catch (final URISyntaxException e) {
- this.error("Could not parse URI [" + destination + "]. Falling back to default of stdout.");
- this.destination = DEFAULT_STREAM;
- } catch (final FileNotFoundException e) {
- this.error("File could not be found at [" + destination + "]. Falling back to default of stdout.");
- this.destination = DEFAULT_STREAM;
+ this.output = parseStreamName(destination);
+ } catch (final URISyntaxException error) {
+ LOGGER.error("Could not parse provided URI: {}", destination, error);
+ } catch (final FileNotFoundException error) {
+ LOGGER.error("File could not be found: {}", destination, error);
}
return this;
}
- private PrintStream parseStreamName(final String name) throws URISyntaxException, FileNotFoundException {
- if (name == null || name.equalsIgnoreCase("out")) {
- return DEFAULT_STREAM;
- }
- if (name.equalsIgnoreCase("err")) {
- return System.err;
- }
- final URI destUri = NetUtils.toURI(name);
- final File output = FileUtils.fileFromUri(destUri);
- if (output == null) {
- // don't want any NPEs, no sir
- return DEFAULT_STREAM;
+ @Nullable
+ private static PrintStream parseStreamName(@Nullable final String name)
+ throws URISyntaxException, FileNotFoundException {
+ if (name != null) {
+ if (name.equalsIgnoreCase("out")) {
+ return System.out;
+ } else if (name.equalsIgnoreCase("err")) {
+ return System.err;
+ } else {
+ final URI destUri = NetUtils.toURI(name);
+ final File output = FileUtils.fileFromUri(destUri);
+ if (output != null) {
+ final FileOutputStream fos = new FileOutputStream(output);
+ return new PrintStream(fos, true);
+ }
+ }
}
- final FileOutputStream fos = new FileOutputStream(output);
- return new PrintStream(fos, true);
+ return null;
}
/**
- * Specifies the logging level by name to use for filtering StatusLogger messages.
+ * Sets the level of the {@link StatusLogger} fallback listener.
*
- * @param status name of logger level to filter below.
+ * @param level a level name
* @return {@code this}
- * @see Level
*/
- public StatusConfiguration withStatus(final String status) {
- this.status = Level.toLevel(status, null);
- if (this.status == null) {
- this.error("Invalid status level specified: " + status + ". Defaulting to ERROR.");
- this.status = Level.ERROR;
+ public StatusConfiguration withStatus(@Nullable final String level) {
+ this.level = Level.toLevel(level, null);
+ if (this.level == null) {
+ LOGGER.error("Invalid status level: {}", level);
}
return this;
}
/**
- * Specifies the logging level to use for filtering StatusLogger messages.
+ * Sets the level of the {@link StatusLogger} fallback listener.
*
- * @param status logger level to filter below.
+ * @param level a level
* @return {@code this}
*/
- public StatusConfiguration withStatus(final Level status) {
- this.status = status;
+ public StatusConfiguration withStatus(@Nullable final Level level) {
+ this.level = level;
return this;
}
@@ -180,38 +183,20 @@ public StatusConfiguration withVerboseClasses(final String... verboseClasses) {
* Configures and initializes the StatusLogger using the configured options in this instance.
*/
public void initialize() {
- if (!this.initialized) {
- if (this.status == Level.OFF) {
- this.initialized = true;
- } else {
- final boolean configured = configureExistingStatusConsoleListener();
- if (!configured) {
- final StatusConsoleListener listener = new StatusConsoleListener(this.status, this.destination);
- this.logger.registerListener(listener);
+ lock.lock();
+ try {
+ if (!this.initialized) {
+ final StatusConsoleListener fallbackListener = LOGGER.getFallbackListener();
+ if (output != null) {
+ fallbackListener.setStream(output);
}
- migrateSavedLogMessages();
- }
- }
- }
-
- private boolean configureExistingStatusConsoleListener() {
- boolean configured = false;
- for (final StatusListener statusListener : this.logger.getListeners()) {
- if (statusListener instanceof StatusConsoleListener) {
- final StatusConsoleListener listener = (StatusConsoleListener) statusListener;
- listener.setLevel(this.status);
- this.logger.updateListenerLevel(this.status);
- configured = true;
+ if (level != null) {
+ fallbackListener.setLevel(level);
+ }
+ initialized = true;
}
+ } finally {
+ lock.unlock();
}
- return configured;
- }
-
- private void migrateSavedLogMessages() {
- for (final String message : this.errorMessages) {
- this.logger.error(message);
- }
- this.initialized = true;
- this.errorMessages.clear();
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/package-info.java
index e334546cb24..1e20784cf97 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/package-info.java
@@ -19,7 +19,7 @@
* Configuration classes for the {@link org.apache.logging.log4j.status.StatusLogger} API.
*/
@Export
-@Version("2.20.1")
+@Version("2.20.2")
package org.apache.logging.log4j.core.config.status;
import org.osgi.annotation.bundle.Export;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java
index 03611c6c104..5ffae9c4038 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java
@@ -20,9 +20,15 @@
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
import javax.management.ObjectName;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.status.StatusData;
@@ -33,13 +39,17 @@
* Implementation of the {@code StatusLoggerAdminMBean} interface.
*/
public class StatusLoggerAdmin extends NotificationBroadcasterSupport
- implements StatusListener, StatusLoggerAdminMBean {
+ implements StatusListener, StatusLoggerAdminMBean, MBeanRegistration {
private final AtomicLong sequenceNo = new AtomicLong();
private final ObjectName objectName;
private final String contextName;
private Level level = Level.WARN;
+ private boolean statusListenerRegistered = false;
+
+ private final Lock statusListenerRegistrationGuard = new ReentrantLock();
+
/**
* Constructs a new {@code StatusLoggerAdmin} with the {@code Executor} to
* be used for sending {@code Notification}s asynchronously to listeners.
@@ -62,27 +72,6 @@ public StatusLoggerAdmin(final String contextName, final Executor executor) {
} catch (final Exception e) {
throw new IllegalStateException(e);
}
- removeListeners(contextName);
- StatusLogger.getLogger().registerListener(this);
- }
-
- /**
- * Add listener to StatusLogger for this context, or replace it if it already exists.
- *
- * @param ctxName
- */
- private void removeListeners(final String ctxName) {
- final StatusLogger logger = StatusLogger.getLogger();
- final Iterable listeners = logger.getListeners();
- // Remove any StatusLoggerAdmin listeners already registered for this context
- for (final StatusListener statusListener : listeners) {
- if (statusListener instanceof StatusLoggerAdmin) {
- final StatusLoggerAdmin adminListener = (StatusLoggerAdmin) statusListener;
- if (ctxName != null && ctxName.equals(adminListener.contextName)) {
- logger.removeListener(adminListener);
- }
- }
- }
}
private static MBeanNotificationInfo createNotificationInfo() {
@@ -92,6 +81,61 @@ private static MBeanNotificationInfo createNotificationInfo() {
return new MBeanNotificationInfo(notifTypes, name, description);
}
+ /// BEGIN: Conditional `StatusListener` registration ///////////////////////////////////////////////////////////////
+
+ // `StatusLogger` contains a _fallback listener_ that defaults to a `StatusConsoleListener`.
+ // It is used to propagate logs when no listeners are available.
+ // If JMX registers itself always, unconditionally, this would render the fallback (console) listener ineffective.
+ // That is, no status logs would be written to console when `StatusLoggerAdmin` is in the classpath.
+ // To avoid this undesired behaviour, we register JMX status listener conditionally:
+ // only when there is a party interested in these notifications.
+ // `addNotificationListener()` is a good place to figure out such an interest.
+ // Though `removeNotificationListener()` is not a good place to determine the lack of interest[1].
+ // Hence, we remove the JMX status listener on JMX bean deregistration.
+ //
+ // [1] https://github.com/apache/logging-log4j2/pull/2249#discussion_r1469336334
+
+ @Override
+ public void addNotificationListener(
+ final NotificationListener listener, final NotificationFilter filter, final Object handback) {
+ super.addNotificationListener(listener, filter, handback);
+ statusListenerRegistrationGuard.lock();
+ try {
+ if (!statusListenerRegistered) {
+ StatusLogger.getLogger().registerListener(this);
+ statusListenerRegistered = true;
+ }
+ } finally {
+ statusListenerRegistrationGuard.unlock();
+ }
+ }
+
+ @Override
+ public ObjectName preRegister(final MBeanServer server, final ObjectName name) {
+ return name;
+ }
+
+ @Override
+ public void postRegister(final Boolean registrationDone) {}
+
+ @Override
+ public void preDeregister() {}
+
+ @Override
+ public void postDeregister() {
+ statusListenerRegistrationGuard.lock();
+ try {
+ if (statusListenerRegistered) {
+ StatusLogger.getLogger().removeListener(this);
+ statusListenerRegistered = false;
+ }
+ } finally {
+ statusListenerRegistrationGuard.unlock();
+ }
+ }
+
+ /// END: Conditional `StatusListener` registration /////////////////////////////////////////////////////////////////
+
@Override
public String[] getStatusDataHistory() {
final List data = getStatusData();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/package-info.java
index 07c55a1e0b0..480cc2c6b27 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/package-info.java
@@ -18,7 +18,7 @@
* Log4j 2 JMX support.
*/
@Export
-@Version("2.20.2")
+@Version("2.23.0")
package org.apache.logging.log4j.core.jmx;
import org.osgi.annotation.bundle.Export;