fix: capture STACK_LOG output via a Logback appender instead of System.err - #16067
fix: capture STACK_LOG output via a Logback appender instead of System.err#16067borinquenkid wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes two failing grails-core specs by capturing DefaultStackTraceFilterer’s Full Stack Trace: output via the actual logging pipeline (SLF4J/Logback) instead of attempting to capture System.err, which no longer observes that output when commons-logging is bridged to SLF4J.
Changes:
- Replace
System.errswapping in two specs with a LogbackListAppenderattached toDefaultStackTraceFilterer.STACK_LOG_NAME(StackTrace). - Add small attach/detach helpers and assertions that inspect captured
ILoggingEvents forStackTraceFilterer.FULL_STACK_TRACE_MESSAGE.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| grails-core/src/test/groovy/org/apache/grails/core/GrailsBootstrapRegistryInitializerSpec.groovy | Switches the “logFullStackTraceOnFilter” default/override assertions from System.err capture to a Logback ListAppender on the StackTrace logger. |
| grails-core/src/test/groovy/grails/util/GrailsUtilStackFiltererSpec.groovy | Switches the DefaultStackTraceFilterer positive/negative control assertions from System.err capture to a Logback ListAppender on the StackTrace logger. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address Copilot review on #16067: grails-core already has org.apache.grails.core.testing.support.LogCapture for exactly this purpose (used by StackTraceFiltererSpec), which also restores logger level and additivity on close. Drop the duplicated attach/detach helpers in favor of it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Heads up - #16011 was merged ( On Rather than close this, I'd like to repurpose it as the definitive cleanup for this failure mode. It has now surfaced two or three separate times, and there is still a live latent instance. 1. Diagnosis is slightly off, and the correction mattersThe description attributes the failure to <configuration>
<root level="WARN" />
</configuration>Root at The evidence that this is the real cause: 2. Remaining latent instance
3. Suggested scope for this PR
Optionally, a short note in the 4. Re: the logging changes that just merged#15757 (stop scaffolding Rebase noteSince the two specs already changed on |
…m.err DefaultStackTraceFilterer.STACK_LOG routes through commons-logging, which resolves to a jcl-over-slf4j binding on this classpath -- so its output never touches System.err, regardless of test ordering or timing. Swapping System.err therefore never observes the emitted message, making GrailsUtilStackFiltererSpec and GrailsBootstrapRegistryInitializerSpec fail deterministically. Attach a ListAppender directly to the public STACK_LOG_NAME logger instead, which is unaffected by which commons-logging backend wins the classpath. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ystem.err capture Address jamesfredley's review on #16067: this was the one remaining latent instance of the same fragility -- three sites asserting on rendered System.err output for the STACK_LOG and GrailsExceptionResolver loggers, including one match on the literal console layout string 'ERROR StackTrace '. It passed only because grails-web-mvc's test classpath happened to carry slf4j-simple, whose SYS_ERR output choice re-reads System.err per call rather than caching it -- the same trap that broke these tests in grails-core once that module got a deterministic logback-test.xml. Swap the module's test logging binding from slf4j-simple to grails-core's test fixtures (which bring logback-classic transitively), and rewrite the three specs to assert on captured ILoggingEvents instead of console text -- including inspecting each event's throwableProxy stack frames directly rather than counting substrings in rendered output. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5ec758d to
a8227fd
Compare
|
Thanks for the correction and for pushing this further -- addressed all three points: 1. Diagnosis corrected -- updated the PR description to point at the actual trigger ( 2. Rebased onto current 3. Held off on rolling the deterministic |
✅ All tests passed ✅🏷️ Commit: a8227fd Learn more about TestLens at testlens.app. |
Summary
GrailsUtilStackFiltererSpecandGrailsBootstrapRegistryInitializerSpeceach have a positive-controltest asserting that
DefaultStackTraceFiltereremits aFull Stack Trace:message by default. Bothoriginally captured that output by swapping
System.errfor aByteArrayOutputStreamaround the call,which stopped working once
grails-core'slogback-test.xml(added ind1c8d0e439) setroot level="WARN"with zero appenders -- the message isn't merely misrouted, it's written nowhere at all,so the
System.errswap never observes anything regardless of timing or test ordering.Note on scope: while this PR was in flight, #16011 independently merged its own fix for the same two
specs (commit
bdb98a6636), usingLogCapture('StackTrace')(string literal). Per @jamesfredley's reviewbelow, this PR has been repurposed as the definitive cleanup for this failure mode rather than closed:
8.0.x; kept theSTACK_LOG_NAMEconstant (over the string literal#16011used)in the two already-fixed specs.
grails-web-mvc'sGrailsExceptionResolverSpec(3 sites),which only passed today because that module's test classpath carried
slf4j-simple(whoseSYS_ERRoutput choice re-reads
System.errper call instead of caching it) -- the same trap that broke thegrails-core specs once that module got a deterministic
logback-test.xml. One of the three assertionsmatched the literal console layout string
'ERROR StackTrace ', which is about as brittle as it gets.grails-web-mvc's test logging binding fromslf4j-simpletograils-core's test fixtures(brings
logback-classictransitively, needed sinceLogCaptureattaches to a real LogbackLogger),and rewrote the three specs to assert on captured
ILoggingEvents -- including inspecting each event'sthrowableProxystack frames directly instead of counting substrings in rendered console text.No production code changes; behavior of
DefaultStackTraceFilterer/GrailsExceptionResolverisunchanged throughout.
Test plan
:grails-core:test --tests grails.util.GrailsUtilStackFiltererSpec --tests org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec --tests org.grails.exception.reporting.StackTraceFiltererSpec-- all pass:grails-web-mvc:test --tests org.grails.web.errors.GrailsExceptionResolverSpec(and full module) -- all pass:grails-core:codeStyle,:grails-web-mvc:codeStyle-- pass🤖 Generated with Claude Code