Skip to content

Commit

Permalink
fix: FilteredLoggingMonitor (#11754)
Browse files Browse the repository at this point in the history
Signed-off-by: mxtartaglia <[email protected]>
  • Loading branch information
mxtartaglia-sl authored Feb 28, 2024
1 parent ef52e02 commit be1256a
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 152 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.swirlds.logging.test.fixtures.internal;

import static org.assertj.core.api.Assertions.assertThat;

import com.swirlds.base.context.internal.ThreadLocalContext;
import com.swirlds.base.test.fixtures.concurrent.TestExecutor;
import com.swirlds.base.test.fixtures.concurrent.WithTestExecutor;
import com.swirlds.logging.api.Level;
import com.swirlds.logging.api.Logger;
import com.swirlds.logging.api.Loggers;
import com.swirlds.logging.test.fixtures.LoggingMirror;
import com.swirlds.logging.test.fixtures.WithLoggingMirror;
import jakarta.inject.Inject;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@WithTestExecutor
@WithLoggingMirror
class FilteredLoggingMirrorTest {

private final Logger logger = Loggers.getLogger(FilteredLoggingMirrorTest.class);
private static final String KEY = UUID.randomUUID().toString();
private static final String VALUE = "VALUE";
private static final String KEY2 = UUID.randomUUID().toString();
private static final String TEST_THREAD = UUID.randomUUID().toString();
private static final String VALUE2 = "OTHER VALUE";

@Inject
private LoggingMirror loggingMirror;

@Inject
private TestExecutor executor;

@Test
@DisplayName("filtering By Context Should Return Log Statements Done With right Context")
void testFilterByContext() {
executor.executeAndWait(this::doLog);
assertThat(loggingMirror.filterByContext(KEY, VALUE).getEventCount()).isEqualTo(7);
assertThat(loggingMirror
.filter(logEvent -> logEvent.context().containsKey(KEY))
.getEventCount())
.isEqualTo(7);
}

@Test
@DisplayName("filtering By Context Nesting Conditions Should Return Log Statements Done With right Context")
void testFilterWithNestingByContext() {
executor.executeAndWait(this::doLog);
assertThat(loggingMirror
.filter(logEvent -> logEvent.context().containsKey(KEY))
.filter(logEvent -> logEvent.context().containsKey(KEY2))
.getEventCount())
.isEqualTo(1);
assertThat(loggingMirror
.filterByContext(KEY, VALUE)
.filterByContext(KEY2, VALUE2)
.getEventCount())
.isEqualTo(1);
}

@Test
@DisplayName("filtering By Logger Should Return Log Statements Done With right Logger")
void testFilterByLogger() {
executor.executeAndWait(this::doLog);
assertThat(loggingMirror.filterByLogger(FilteredLoggingMirrorTest.class).getEventCount())
.isEqualTo(19);
assertThat(loggingMirror.filterByLogger("AnotherName").getEventCount()).isEqualTo(0);
}

@Test
@DisplayName("filtering By Thread Should Return Log Statements Done With right Thread")
void testFilterByThread() {
executor.executeAndWait(() -> {
Thread.currentThread().setName(TEST_THREAD);
logger.trace("This is a {} level message", Level.TRACE);
logger.debug("This is a {} level message", Level.DEBUG);
});
assertThat(loggingMirror.filterByThread(TEST_THREAD).getEventCount()).isEqualTo(2);

assertThat(loggingMirror
.filterByThread(TEST_THREAD)
.filterByLevel(Level.TRACE)
.getEventCount())
.isEqualTo(1);

assertThat(loggingMirror
.filterByThread(TEST_THREAD)
.filterByLevel(Level.DEBUG)
.getEventCount())
.isEqualTo(1);

assertThat(loggingMirror
.filterByThread(TEST_THREAD)
.filterByLevel(Level.INFO)
.getEventCount())
.isEqualTo(0);

assertThat(loggingMirror.filterByCurrentThread().getEventCount()).isEqualTo(0);
}

@Test
@DisplayName("filtering By Level Should Return Log Statements Done With right Level")
void testFilterByLevel() {
executor.executeAndWait(this::doLog);
assertThat(loggingMirror.filterByLevel(Level.OFF).getEventCount()).isEqualTo(1);

assertThat(loggingMirror.filterByLevel(Level.TRACE).getEventCount()).isEqualTo(3);

assertThat(loggingMirror.filterByLevel(Level.DEBUG).getEventCount()).isEqualTo(3);

assertThat(loggingMirror.filterByLevel(Level.ERROR).getEventCount()).isEqualTo(3);

assertThat(loggingMirror.filterByLevel(Level.WARN).getEventCount()).isEqualTo(3);

assertThat(loggingMirror.filterByLevel(Level.INFO).getEventCount()).isEqualTo(6);

assertThat(loggingMirror
.filterByLevel(Level.INFO)
.filterByContext(KEY, VALUE)
.getEventCount())
.isEqualTo(3);

assertThat(loggingMirror
.filterByLevel(Level.INFO)
.filterByContext(KEY, VALUE)
.filterByContext(KEY2, VALUE2)
.getEventCount())
.isEqualTo(1);
}

@Test
@DisplayName("filtering Above Level Should Return Log Statements Done With right Level")
void testFilterAboveLevel() {
executor.executeAndWait(() -> {
Thread.currentThread().setName(TEST_THREAD);
logger.error("This is a {} level message", Level.ERROR);
logger.warn("This is a {} level message", Level.WARN);
logger.info("This is a {} level message", Level.INFO);
logger.debug("This is a {} level message", Level.DEBUG);
logger.trace("This is a {} level message", Level.TRACE);
});
assertThat(loggingMirror.filterAboveLevel(Level.OFF).getEventCount()).isEqualTo(0);
assertThat(loggingMirror.filterAboveLevel(Level.ERROR).getEventCount()).isEqualTo(1);
assertThat(loggingMirror.filterAboveLevel(Level.WARN).getEventCount()).isEqualTo(2);
assertThat(loggingMirror.filterAboveLevel(Level.INFO).getEventCount()).isEqualTo(3);
assertThat(loggingMirror.filterAboveLevel(Level.DEBUG).getEventCount()).isEqualTo(4);
assertThat(loggingMirror.filterAboveLevel(Level.TRACE).getEventCount()).isEqualTo(5);
assertThat(loggingMirror
.filterAboveLevel(Level.TRACE)
.filterByThread("WRONG-NAME")
.getEventCount())
.isEqualTo(0);
assertThat(loggingMirror
.filterAboveLevel(Level.TRACE)
.filterByThread(TEST_THREAD)
.getEventCount())
.isEqualTo(5);
}

private void doLog() {

logger.trace("This is a {} level message", Level.TRACE);
logger.debug("This is a {} level message", Level.DEBUG);
logger.info("This is a {} level message", Level.INFO);
logger.error("This is a {} level message", Level.ERROR);
logger.warn("This is a {} level message", Level.WARN);
logger.log(Level.OFF, "This is an off level");

final Logger testMarker = logger.withMarker("TEST_MARKER");
testMarker.info("This is a {} level message", Level.INFO);

try (final AutoCloseable closable = ThreadLocalContext.getInstance().add(KEY, VALUE)) {
logger.trace("This is a {} level message with context", Level.TRACE);
logger.debug("This is a {} level message with context", Level.DEBUG);
logger.info("This is a {} level message with context", Level.INFO);
logger.error("This is a {} level message with context", Level.ERROR);
logger.warn("This is a {} level message with context", Level.WARN);
testMarker.info("This is a {} level message with context and marker", Level.WARN);
} catch (Exception e) {
throw new RuntimeException(e);
}

try (final AutoCloseable closable = ThreadLocalContext.getInstance().add(KEY2, VALUE2)) {
logger.trace("This is a {} level message with 2nd context", Level.TRACE);
logger.debug("This is a {} level message with 2nd context", Level.DEBUG);
logger.info("This is a {} level message with 2nd context", Level.INFO);
logger.error("This is a {} level message with 2nd context", Level.ERROR);
logger.warn("This is a {} level message with 2nd context", Level.WARN);
} catch (Exception e) {
throw new RuntimeException(e);
}

try (final AutoCloseable closable = ThreadLocalContext.getInstance().add(KEY, VALUE);
final AutoCloseable closable2 = ThreadLocalContext.getInstance().add(KEY2, VALUE2)) {
testMarker.info("This is a {} level message with context, 2nd context and marker", Level.INFO);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import com.swirlds.logging.api.extensions.event.LogEvent;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

/**
* A mirror of the logging system that can be used to check the logging events that were generated during a test. A
Expand All @@ -31,46 +33,81 @@
public interface LoggingMirror extends AutoCloseable {

/**
* Returns the number of log events that were generated during a test.
* Returns a mirror that only contains log events were the logEventPredicate evaluates to true.
*
* @return the number of log events that were generated during a test
* @param logEventPredicate the level to filter by
* @return a mirror that only contains log events with the given level
*/
int getEventCount();
@NonNull
LoggingMirror filter(@NonNull Predicate<LogEvent> logEventPredicate);

@Override
default void close() throws Exception {
dispose();
/**
* Returns a mirror that only contains log events with the given logger name (based on the class name).
*
* @param clazz the class to filter by
* @return a mirror that only contains log events with the given logger name
*/
default LoggingMirror filterByLogger(@NonNull final Class<?> clazz) {
return filterByLogger(clazz.getName());
}

/**
* Clears the mirror and disposes it. This method is automatically called before and after a test.
* Returns a list of all log events that were generated during a test.
*
* @return a list of all log events that were generated during a test
*/
List<LogEvent> getEvents();

/**
* Returns the number of log events that were generated during a test.
*
* @return the number of log events that were generated during a test
*/
void dispose();
default int getEventCount() {
return getEvents().size();
}

/**
* Returns a mirror that only contains log events with the given level.
*
* @param level the level to filter by
* @return a mirror that only contains log events with the given level
*/
LoggingMirror filterByLevel(@NonNull final Level level);
@NonNull
default LoggingMirror filterByLevel(@NonNull final Level level) {
Objects.requireNonNull(level, "level must not be null");
Predicate<LogEvent> filter = event -> event.level() == level;
return filter(filter);
}

/**
* Returns a mirror that only contains log events with the given context.
* Returns a mirror that only contains log events above the given level.
*
* @param key the key of the context
* @param value the value of the context
* @return a mirror that only contains log events with the given context
* @param level the level to filter by
* @return a mirror that only contains log events with the given level
*/
LoggingMirror filterByContext(@NonNull final String key, @NonNull final String value);
@NonNull
default LoggingMirror filterAboveLevel(@NonNull final Level level) {
Objects.requireNonNull(level, "level must not be null");
final Predicate<LogEvent> filter =
event -> level.ordinal() >= event.level().ordinal();
return filter(filter);
}

/**
* Returns a mirror that only contains log events with the current thread.
* Returns a mirror that only contains log events with the given context.
*
* @return a mirror that only contains log events with the current thread
* @param key the key of the context
* @param value the value of the context
* @return a mirror that only contains log events with the given context
*/
default LoggingMirror filterByCurrentThread() {
return filterByThread(Thread.currentThread().getName());
@NonNull
default LoggingMirror filterByContext(@NonNull final String key, @NonNull final String value) {
Objects.requireNonNull(key, "key must not be null");
Objects.requireNonNull(value, "value must not be null");
final Predicate<LogEvent> filter = event ->
event.context().containsKey(key) && event.context().get(key).equals(value);
return filter(filter);
}

/**
Expand All @@ -79,16 +116,21 @@ default LoggingMirror filterByCurrentThread() {
* @param threadName the name of the thread
* @return a mirror that only contains log events with the given thread
*/
LoggingMirror filterByThread(@NonNull final String threadName);
@NonNull
default LoggingMirror filterByThread(@NonNull final String threadName) {
Objects.requireNonNull(threadName, "threadName must not be null");
final Predicate<LogEvent> filter = event -> Objects.equals(event.threadName(), threadName);
return filter(filter);
}

/**
* Returns a mirror that only contains log events with the given logger name (based on the class name).
* Returns a mirror that only contains log events with the current thread.
*
* @param clazz the class to filter by
* @return a mirror that only contains log events with the given logger name
* @return a mirror that only contains log events with the current thread
*/
default LoggingMirror filterByLogger(@NonNull final Class<?> clazz) {
return filterByLogger(clazz.getName());
@NonNull
default LoggingMirror filterByCurrentThread() {
return filterByThread(Thread.currentThread().getName());
}

/**
Expand All @@ -97,12 +139,10 @@ default LoggingMirror filterByLogger(@NonNull final Class<?> clazz) {
* @param loggerName the logger name to filter by
* @return a mirror that only contains log events with the given logger name
*/
LoggingMirror filterByLogger(@NonNull final String loggerName);

/**
* Returns a list of all log events that were generated during a test.
*
* @return a list of all log events that were generated during a test
*/
List<LogEvent> getEvents();
@NonNull
default LoggingMirror filterByLogger(@NonNull final String loggerName) {
Objects.requireNonNull(loggerName, "loggerName must not be null");
final Predicate<LogEvent> filter = event -> event.loggerName().startsWith(loggerName);
return filter(filter);
}
}
Loading

0 comments on commit be1256a

Please sign in to comment.