Class LogCapture
- All Implemented Interfaces:
AutoCloseable
Deliberately narrow (#323). It is for the call sites where the log is the report: a handler whose whole behaviour is to log and drop, a failure absorbed during a shutdown the mailbox can no longer observe, a quota warning the job builds straight through, an abandoned teardown whose counter says one happened but not which client. Where a test already asserts a counter, a returned value or an absorbed exception that identifies the event, the log is a second report of something already covered — and asserting it there buys little while coupling the test to the message's wording, which is the cost that keeps this class from spreading.
Abbreviated, not compiled: the asserted message depends on the test scenario.
try (LogCapture capture = LogCapture.of(FailureHandlers.LogAndDrop.class)) {
FailureHandler.logAndDrop().handle(element);
assertThat(capture.getMessages()).singleElement().satisfies(m -> ...);
}
The backend is log4j2, which is what the test classpath actually carries —
log4j-slf4j-impl reaches every module transitively through flink-test-utils. Logback is
absent, so the ListAppender shape does not apply, and log4j's own ListAppender
ships only in a log4j-core test-jar this build does not resolve. No log4j2 type appears
in this class's signature, so replacing the backend is a change to this file alone.
Everything below exists to rule out one failure: a capture that collects nothing while looking exactly like a log that was never emitted.
- The logger name is derived as slf4j derives it,
Class#getName, because the code under test logs through slf4j.LogManager.getLogger(Class)does not agree: it prefersClass#getCanonicalName, so for a nested type such asFailureHandlers.LogAndDropthe two names differ by$versus.. They are not even in an ancestor relationship, since log4j2 splits a name on.alone — so taking log4j2's name would attach to a logger nothing ever writes to. - The level is forced on the
LoggerConfig, not on theLogger. Every module ships alog4j2-test.propertiesatrootLogger.level = WARN, so a WARN capture needs no help — but a capture below that does, and a module added without one falls back to log4j2's rootERROR, where even a WARN would be collected nowhere.Logger#setLevelwould widen it, but it writes to the logger's cached config, which any laterupdateLoggers()— including one triggered by a second capture opening on an unrelated logger — silently discards. - The level is only ever widened, never narrowed, and is restored to the explicit level found, which may be none. Saving the effective level and writing it back would turn an inherited level into an explicit one and outlive the capture.
Restoration matters beyond tidiness: surefire reuses forks, so a LoggerConfig or a
widened level left behind reaches every later test class in that fork (the shape of #316).
No level guard (if (LOG.isDebugEnabled())) exists in this repository's main sources,
so widening cannot change what the code under test does. A guard added later would make that
untrue for a capture taken below the ambient level.
A test that asserts a log was not emitted should sit beside one that asserts a log was, on the same logger: an empty capture is the expected result of both a working capture and a broken one, and only the positive case can tell them apart.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classOne collected log event, flattened out of log4j2's own type.static enumThe levels a capture can be taken at, so no log4j2 type reaches this class's signature. -
Method Summary
Modifier and TypeMethodDescriptionvoidclose()Detaches the capture and restores what it changed.The events collected so far, in the order they were logged.The formatted messages collected so far, in the order they were logged.static LogCaptureCapturesWARNand above from the given class's logger, attaching immediately.static LogCaptureof(Class<?> owner, LogCapture.Level level) Captures the given level and above from the given class's logger, attaching immediately.
-
Method Details
-
of
CapturesWARNand above from the given class's logger, attaching immediately.- Parameters:
owner- the class whose logger to capture, as it was passed togetLogger
-
of
Captures the given level and above from the given class's logger, attaching immediately.- Parameters:
owner- the class whose logger to capture, as it was passed togetLoggerlevel- the least specific level to collect
-
getMessages
The formatted messages collected so far, in the order they were logged. -
getEvents
The events collected so far, in the order they were logged.For the sites that log a cause — an absorbed shutdown report, an abandoned stream — where the attached throwable is part of what the line reports.
-
close
public void close()Detaches the capture and restores what it changed. Idempotent.- Specified by:
closein interfaceAutoCloseable
-