Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make StatusLogger self-contained and testable #2249

Merged
merged 31 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
94c813c
Make `StatusLogger` self-contained and testable
vy Jan 25, 2024
fd67e38
Extend `UsingStatusLoggerMock` with `ExtensionContextAnchor`
vy Jan 26, 2024
21a47ac
Speed-up `isEnabled()` by caching the least specific listener level
vy Jan 26, 2024
cd8912d
Merge remote-tracking branch 'origin/2.x' into 2.x-StatusLogger-revamp
vy Jan 26, 2024
cd3fadb
Bring the fallback `StatusListener` concept back
vy Jan 29, 2024
f1e3e2f
Make `StatusLogger()` ctor private
vy Jan 29, 2024
c4c941e
Document unmodifiable collection usage
vy Jan 29, 2024
3684a2e
Initialize the default `StatusLogger` instance lazy
vy Jan 29, 2024
2c01bcd
Fix `@Version`s
vy Jan 29, 2024
fdcb05b
Fix Spotless issues
vy Jan 29, 2024
40cc721
Rework `StatusLogger` effective level
vy Jan 29, 2024
88eb8fc
Improve `StatusLoggerAdmin` conditional registration
vy Jan 29, 2024
f3fa918
Fix NPE in `AsyncLoggerConfigDisruptor`
vy Jan 30, 2024
325c371
Merge remote-tracking branch 'origin/2.x' into 2.x-StatusLogger-revamp
vy Jan 30, 2024
df82860
Fix code typo in `StatusLogger`
vy Jan 30, 2024
35e78bb
Fix Spotless failures
vy Jan 30, 2024
682604b
Revert "Fix NPE in `AsyncLoggerConfigDisruptor`"
ppkarwasz Jan 30, 2024
e0090d4
Merge branch '2.x' into 2.x-StatusLogger-revamp
ppkarwasz Jan 30, 2024
e2e983f
Merge remote-tracking branch 'origin/2.x' into 2.x-StatusLogger-revamp
vy Feb 6, 2024
8632e09
Update auto-generated files
vy Feb 6, 2024
36c4d48
Remove `announce@apache.org` in `generate-email.sh`
vy Feb 6, 2024
73cd685
Remove nested logging tests
vy Feb 6, 2024
bfb5197
Merge remote-tracking branch 'origin/2.x' into 2.x-StatusLogger-revamp
vy Feb 6, 2024
9720939
Remove `QueueFullAsync*` tests relying on nested logging
vy Feb 7, 2024
c0235e3
Trim recently added public methods to `StatusLogger`
vy Feb 7, 2024
b8e490c
Deprecate buffering provided by `StatusLogger`
vy Feb 7, 2024
bfdbde4
Match level comparison in `StatusConsoleListener` and `StatusLogger`
vy Feb 7, 2024
fab3f15
Add getter for the fallback listener in `StatusLogger`
vy Feb 8, 2024
9f7533f
Merge remote-tracking branch 'origin/2.x' into 2.x-StatusLogger-revamp
vy Feb 8, 2024
4d43e82
Improve `StatusLogger` javadoc
vy Feb 8, 2024
c48bb4f
Fix `javadoc:javadoc` failures
vy Feb 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
@StatusLoggerLevel("WARN")
@ResourceLock(value = Resources.MARKER_MANAGER, mode = ResourceAccessMode.READ)
@SetSystemProperty(key = StatusLogger.BUFFER_CAPACITY_PROPERTY_NAME, value = "200")
@SetSystemProperty(key = StatusLogger.FALLBACK_LISTENER_LEVEL_PROPERTY_NAME, value = "WARN")
public class AbstractLoggerTest {

private static final StringBuilder CHAR_SEQ = new StringBuilder("CharSeq");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
* {@link StatusLogger} is expected to be a standalone, self-sufficient component that the logging system can rely on for low-level logging purposes.
* <h3>Listeners</h3>
* <p>
* Each recorded event will first get buffered and used to notify the registered {@link StatusListener}s.
* Listener registry is always initialized with a <em>default listener</em>, which is a {@link StatusConsoleListener}.
* Each recorded event will first get buffered and then used to notify the registered {@link StatusListener}s.
* If none are available, the <em>fallback listener</em> of type {@link StatusConsoleListener} will be used.
* </p>
* <p>
* You can programmatically register listeners using {@link #registerListener(StatusListener)} method.
Expand All @@ -68,8 +68,8 @@
* Consider the following example:
* </p>
* <ol>
* <li>The default level is {@code ERROR}</li>
* <li>You have <Configuration status="WARN">} in your {@code log4j2.xml}</li>
* <li>The default level (of fallback listener) is {@code ERROR}</li>
* <li>You have {@code <Configuration status="WARN">} in your {@code log4j2.xml}</li>
* <li>Until your {@code log4j2.xml} configuration is read, the effective level will be {@code ERROR}</li>
* <li>Once your {@code log4j2.xml} configuration is read, the effective level will be {@code WARN} as you configured</li>
* </ol>
Expand All @@ -83,6 +83,7 @@
* <h3>Debug mode</h3>
* <p>
* 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 <em>fallback listener</em> of type {@link StatusConsoleListener} will be used.
* </p>
*/
public class StatusLogger extends AbstractLogger {
vy marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -127,45 +128,45 @@ public class StatusLogger extends AbstractLogger {
public static final String MAX_STATUS_ENTRIES = BUFFER_CAPACITY_PROPERTY_NAME;

/**
* The name of the system property that can be configured with the {@link Level} name to use as the default listener level.
* The name of the system property that can be configured with the {@link Level} name to use as the fallback listener level.
* <p>
* The listener registry is initialized with a default listener.
* This default listener will accept entries filtered by the level provided in this configuration.
* 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.
* </p>
*
* @since 2.23.0
*/
public static final String DEFAULT_LISTENER_LEVEL_PROPERTY_NAME = "log4j2.StatusLogger.level";
public static final String FALLBACK_LISTENER_LEVEL_PROPERTY_NAME = "log4j2.StatusLogger.level";

/**
* The default value of the {@link #DEFAULT_LISTENER_LEVEL_PROPERTY_NAME} system property: {@code ERROR}.
* The default value of the {@link #FALLBACK_LISTENER_LEVEL_PROPERTY_NAME} system property: {@code ERROR}.
*
* @since 2.23.0
*/
public static final Level DEFAULT_LISTENER_LEVEL_DEFAULT_VALUE = Level.ERROR;
public static final Level FALLBACK_LISTENER_LEVEL_DEFAULT_VALUE = Level.ERROR;

/**
* The name of the system property that can be configured with the {@link Level} name to use as the default listener level.
* The name of the system property that can be configured with the {@link Level} name to use as the fallback listener level.
* <p>
* The listener registry is initialized with a default listener.
* This default listener will accept entries filtered by the level provided in this configuration.
* 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.
* </p>
*
* @since 2.8
* @deprecated Use {@link #DEFAULT_LISTENER_LEVEL_PROPERTY_NAME} instead.
* @deprecated Use {@link #FALLBACK_LISTENER_LEVEL_PROPERTY_NAME} instead.
*/
@Deprecated
public static final String DEFAULT_STATUS_LISTENER_LEVEL = DEFAULT_LISTENER_LEVEL_PROPERTY_NAME;
public static final String DEFAULT_STATUS_LISTENER_LEVEL = FALLBACK_LISTENER_LEVEL_PROPERTY_NAME;

/**
* The name of the system property that can be configured with a {@link java.time.format.DateTimeFormatter} pattern that will be passed to the default listener.
* 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.23.0
*/
public static final String INSTANT_FORMAT_PROPERTY_NAME = "log4j2.StatusLogger.DateFormat";

/**
* The name of the system property that can be configured with a {@link java.time.format.DateTimeFormatter} pattern that will be passed to the default listener.
* 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
* @deprecated Use {@link #INSTANT_FORMAT_PROPERTY_NAME} instead.
Expand Down Expand Up @@ -193,7 +194,7 @@ public static final class Config {

private final int bufferCapacity;

private final Level defaultListenerLevel;
private final Level fallbackListenerLevel;

@Nullable
private final DateTimeFormatter instantFormatter;
Expand All @@ -204,21 +205,21 @@ public static final class Config {
*
* @param debugEnabled the value of the {@value DEBUG_PROPERTY_NAME} property
* @param bufferCapacity the value of the {@value BUFFER_CAPACITY_PROPERTY_NAME} property
* @param defaultListenerLevel the value of the {@value DEFAULT_LISTENER_LEVEL_PROPERTY_NAME} property
* @param fallbackListenerLevel the value of the {@value FALLBACK_LISTENER_LEVEL_PROPERTY_NAME} property
* @param instantFormatter the value of the {@value INSTANT_FORMAT_PROPERTY_NAME} property
*/
public Config(
boolean debugEnabled,
int bufferCapacity,
Level defaultListenerLevel,
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.defaultListenerLevel = requireNonNull(defaultListenerLevel, "defaultListenerLevel");
this.fallbackListenerLevel = requireNonNull(fallbackListenerLevel, "fallbackListenerLevel");
this.instantFormatter = requireNonNull(instantFormatter, "instantFormatter");
}

Expand All @@ -230,7 +231,7 @@ public Config() {
final Properties fileProvidedProperties = readPropertiesFile();
this.debugEnabled = readDebugEnabled(fileProvidedProperties);
this.bufferCapacity = readBufferCapacity(fileProvidedProperties);
this.defaultListenerLevel = readDefaultListenerLevel(fileProvidedProperties);
this.fallbackListenerLevel = readFallbackListenerLevel(fileProvidedProperties);
this.instantFormatter = readInstantFormatter(fileProvidedProperties);
}

Expand All @@ -253,9 +254,9 @@ private static int readBufferCapacity(final Properties fileProvidedProperties) {
return capacityString != null ? Integer.parseInt(capacityString) : BUFFER_CAPACITY_DEFAULT_VALUE;
}

private static Level readDefaultListenerLevel(final Properties fileProvidedProperties) {
final String level = readProperty(fileProvidedProperties, DEFAULT_LISTENER_LEVEL_PROPERTY_NAME);
return level != null ? Level.valueOf(level) : DEFAULT_LISTENER_LEVEL_DEFAULT_VALUE;
private static Level readFallbackListenerLevel(final Properties fileProvidedProperties) {
final String level = readProperty(fileProvidedProperties, FALLBACK_LISTENER_LEVEL_PROPERTY_NAME);
return level != null ? Level.valueOf(level) : FALLBACK_LISTENER_LEVEL_DEFAULT_VALUE;
}

private static DateTimeFormatter readInstantFormatter(final Properties fileProvidedProperties) {
Expand Down Expand Up @@ -296,7 +297,7 @@ private static Properties readPropertiesFile() {

private final Config config;

private final StatusConsoleListener defaultListener;
private final StatusConsoleListener fallbackListener;

private final Collection<StatusListener> listeners;

Expand Down Expand Up @@ -324,7 +325,7 @@ public StatusLogger() {
StatusLogger.class.getSimpleName(),
ParameterizedNoReferenceMessageFactory.INSTANCE,
Config.getInstance(),
new StatusConsoleListener(Config.getInstance().defaultListenerLevel));
new StatusConsoleListener(Config.getInstance().fallbackListenerLevel));
}

/**
Expand All @@ -334,20 +335,20 @@ public StatusLogger() {
* @param name the logger name
* @param messageFactory the message factory
* @param config the configuration
* @param defaultListener the default listener
* @throws NullPointerException on null {@code name}, {@code messageFactory}, {@code config}, or {@code defaultListener}
* @param fallbackListener the fallback listener
* @throws NullPointerException on null {@code name}, {@code messageFactory}, {@code config}, or {@code fallbackListener}
* @since 2.23.0
*/
public StatusLogger(
final String name,
final MessageFactory messageFactory,
final Config config,
final StatusConsoleListener defaultListener) {
final StatusConsoleListener fallbackListener) {
super(requireNonNull(name, "name"), requireNonNull(messageFactory, "messageFactory"));
this.config = requireNonNull(config, "config");
this.defaultListener = requireNonNull(defaultListener, "defaultListener");
this.listeners = new ArrayList<>(Collections.singleton(defaultListener));
this.leastSpecificListenerLevel = defaultListener.getStatusLevel();
this.fallbackListener = requireNonNull(fallbackListener, "fallbackListener");
this.listeners = new ArrayList<>(Collections.singleton(fallbackListener));
this.leastSpecificListenerLevel = fallbackListener.getStatusLevel();
}

/**
Expand All @@ -372,37 +373,37 @@ public static void setLogger(final StatusLogger logger) {
}

/**
* Sets the level of the default listener.
* Sets the level of the fallback listener.
*
* @param level a level
* @since 2.23.0
*/
public void setDefaultListenerLevel(final Level level) {
public void setFallbackListenerLevel(final Level level) {
requireNonNull(level, "level");
defaultListener.setLevel(level);
fallbackListener.setLevel(level);
}

/**
* Sets the output of the default listener.
* Sets the output of the fallback listener.
*
* @param stream a print stream
* @since 2.23.0
*/
public void setDefaultListenerOutput(final PrintStream stream) {
public void setFallbackListenerOutput(final PrintStream stream) {
requireNonNull(stream, "stream");
defaultListener.setStream(stream);
fallbackListener.setStream(stream);
}

/**
* Sets the level of the default listener.
* Sets the level of the fallback listener.
*
* @param level a level
* @deprecated Use {@link #setDefaultListenerLevel(Level)} instead.
* @deprecated Use {@link #setFallbackListenerLevel(Level)} instead.
*/
@Deprecated
public void setLevel(final Level level) {
requireNonNull(level, "level");
setDefaultListenerLevel(level);
setFallbackListenerLevel(level);
}

/**
Expand Down Expand Up @@ -439,29 +440,29 @@ public void removeListener(final StatusListener listener) {
}

private void updateLeastSpecificListenerLevel() {
Level localLeastSpecificListenerLevel = leastSpecificListenerLevel;
Level foundLeastSpecificListenerLevel = fallbackListener.getStatusLevel();
for (final StatusListener listener : listeners) {
final Level listenerLevel = listener.getStatusLevel();
if (listenerLevel.isLessSpecificThan(localLeastSpecificListenerLevel)) {
localLeastSpecificListenerLevel = listener.getStatusLevel();
if (listenerLevel.isLessSpecificThan(foundLeastSpecificListenerLevel)) {
foundLeastSpecificListenerLevel = listener.getStatusLevel();
}
}
// We must update `leastSpecificListenerLevel` in a single instruction!
// It is `volatile` and accessed by `getLevel()` and `isEnabled()` without listener locks for efficiency.
leastSpecificListenerLevel = localLeastSpecificListenerLevel;
leastSpecificListenerLevel = foundLeastSpecificListenerLevel;
}

/**
*
* Sets the level of the default listener.
* Sets the level of the fallback listener.
*
* @param level a level
* @deprecated Instead either use {@link #setDefaultListenerLevel(Level)}, or {@link #getListeners() get the specific listener} and change its {@link StatusListener#getStatusLevel() level}.
* @deprecated Instead either use {@link #setFallbackListenerLevel(Level)}, or {@link #getListeners() get the specific listener} and change its {@link StatusListener#getStatusLevel() level}.
*/
@Deprecated
public void updateListenerLevel(final Level level) {
requireNonNull(level, "level");
setDefaultListenerLevel(level);
setFallbackListenerLevel(level);
}

/**
Expand All @@ -479,18 +480,16 @@ public Iterable<StatusListener> getListeners() {
}

/**
* Clears the event buffer and removes the <em>registered</em> (not the default one!) listeners.
* Clears the event buffer and removes the <em>registered</em> (not the fallback one!) listeners.
*/
public void reset() {
listenerWriteLock.lock();
try {
final Iterator<StatusListener> listenerIterator = listeners.iterator();
while (listenerIterator.hasNext()) {
final StatusListener listener = listenerIterator.next();
if (listener != defaultListener) {
closeListenerSafely(listener);
listenerIterator.remove();
}
closeListenerSafely(listener);
listenerIterator.remove();
}
} finally {
listenerWriteLock.unlock();
Expand Down Expand Up @@ -556,16 +555,23 @@ private void buffer(final StatusData statusData) {
}

private void notifyListeners(final StatusData statusData) {
final boolean foundListeners;
listenerReadLock.lock();
try {
listeners.forEach(listener -> {
if (config.debugEnabled || listener.getStatusLevel().isLessSpecificThan(statusData.getLevel())) {
listener.log(statusData);
}
});
foundListeners = !listeners.isEmpty();
listeners.forEach(listener -> notifyListener(listener, statusData));
} finally {
listenerReadLock.unlock();
}
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 StatusData createStatusData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import org.junitpioneer.jupiter.SetSystemProperty;

@SetSystemProperty(key = StatusLogger.BUFFER_CAPACITY_PROPERTY_NAME, value = "10")
@SetSystemProperty(key = StatusLogger.DEFAULT_LISTENER_LEVEL_PROPERTY_NAME, value = "WARN")
@SetSystemProperty(key = StatusLogger.FALLBACK_LISTENER_LEVEL_PROPERTY_NAME, value = "WARN")
class AbstractActionTest {

// Test for LOG4J2-2658
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ private void testConfiguration(
.isEqualTo(expectedConfigName);
final StatusLogger statusLogger = StatusLogger.getLogger();
if (expectedStatusLevel == null) {
verify(statusLogger, never()).setDefaultListenerLevel(any());
verify(statusLogger, never()).setFallbackListenerLevel(any());
} else {
verify(statusLogger).setDefaultListenerLevel(eq(expectedStatusLevel));
verify(statusLogger).setFallbackListenerLevel(eq(expectedStatusLevel));
}
assertThat(config.getRootLogger().getExplicitLevel()).isEqualTo(expectedRootLevel);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,10 @@ public void initialize() {
try {
if (!this.initialized) {
if (output != null) {
LOGGER.setDefaultListenerOutput(output);
LOGGER.setFallbackListenerOutput(output);
}
if (level != null) {
LOGGER.setDefaultListenerLevel(level);
LOGGER.setFallbackListenerLevel(level);
}
initialized = true;
}
Expand Down
Loading
Loading