Conversation
|
We need tests to accept a change like this. |
There was a problem hiding this comment.
Pull request overview
This PR aims to reduce duplicate ERROR log entries emitted by DefaultStackTraceFilterer by filtering stack traces for the full exception cause-chain without logging each cause separately, and logging the full stack trace only once for the top-level exception.
Changes:
- Extracted the filtering/trimming logic into a new private
doFilter(Throwable)method. - Updated recursive filtering to call
doFilter(...)when walking the cause chain. - Updated
filter(Throwable)to log conditionally (intended: once per top-level exception).
Comments suppressed due to low confidence (1)
grails-bootstrap/src/main/groovy/org/grails/exceptions/reporting/DefaultStackTraceFilterer.java:88
- In
filter(Throwable source, boolean recursive), the recursive walk callsdoFilter(current)starting withcurrent = source, and thenfilter(source)is invoked afterward. This filters the top-level exception twice and, more importantly, ensures the later log infilter(source)will observe an already-filtered stack trace. Consider starting the loop atsource.getCause()(filter causes only) so the top-level exception is filtered/logged exactly once infilter(source).
public Throwable filter(Throwable source, boolean recursive) {
if (recursive) {
Throwable current = source;
while (current != null) {
doFilter(current);
current = current.getCause();
}
}
return filter(source);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public Throwable filter(Throwable source) { | ||
| if (shouldFilter) { | ||
| StackTraceElement[] trace = source.getStackTrace(); | ||
| List<StackTraceElement> newTrace = filterTraceWithCutOff(trace, cutOffPackage); | ||
|
|
||
| if (newTrace.isEmpty()) { | ||
| // filter with no cut-off so at least there is some trace | ||
| newTrace = filterTraceWithCutOff(trace, null); | ||
| } | ||
|
|
||
| // Only trim the trace if there was some application trace on the stack | ||
| // if not we will just skip sanitizing and leave it as is | ||
| if (!newTrace.isEmpty()) { | ||
| // We don't want to lose anything, so log it | ||
| boolean modified = doFilter(source); | ||
| if (modified) { | ||
| // Log the full stack trace once for the top-level exception (includes causes) | ||
| STACK_LOG.error(FULL_STACK_TRACE_MESSAGE, source); | ||
| StackTraceElement[] clean = new StackTraceElement[newTrace.size()]; | ||
| newTrace.toArray(clean); | ||
| source.setStackTrace(clean); | ||
| } |
There was a problem hiding this comment.
filter(Throwable source) currently calls doFilter(source) (which mutates source via setStackTrace) before logging FULL_STACK_TRACE_MESSAGE. That means the "Full Stack Trace" log is no longer the full/original trace, but the already-filtered trace. To preserve prior behavior and the PR description, log the exception before applying the filtered stack trace (e.g., have doFilter compute/return the cleaned trace without mutating, then apply after logging, or temporarily save/restore the original trace around the log call).
| if (shouldFilter) { | ||
| StackTraceElement[] trace = source.getStackTrace(); | ||
| List<StackTraceElement> newTrace = filterTraceWithCutOff(trace, cutOffPackage); | ||
|
|
||
| if (newTrace.isEmpty()) { | ||
| // filter with no cut-off so at least there is some trace | ||
| newTrace = filterTraceWithCutOff(trace, null); | ||
| } | ||
|
|
||
| // Only trim the trace if there was some application trace on the stack | ||
| // if not we will just skip sanitizing and leave it as is | ||
| if (!newTrace.isEmpty()) { | ||
| // We don't want to lose anything, so log it | ||
| boolean modified = doFilter(source); | ||
| if (modified) { | ||
| // Log the full stack trace once for the top-level exception (includes causes) | ||
| STACK_LOG.error(FULL_STACK_TRACE_MESSAGE, source); | ||
| StackTraceElement[] clean = new StackTraceElement[newTrace.size()]; | ||
| newTrace.toArray(clean); | ||
| source.setStackTrace(clean); | ||
| } |
There was a problem hiding this comment.
This change modifies when/how often the StackTrace logger logs (STACK_LOG.error(...)) during recursive filtering, but existing specs (e.g., grails-core/src/test/.../StackTraceFiltererSpec.groovy) only assert the sanitized stack trace contents and don't cover log emission/count. Adding a test that captures the StackTrace logger output and asserts a single "Full Stack Trace" entry for filter(e, true) would help prevent regressions.
| boolean modified = doFilter(source); | ||
| if (modified) { | ||
| // Log the full stack trace once for the top-level exception (includes causes) | ||
| STACK_LOG.error(FULL_STACK_TRACE_MESSAGE, source); |
There was a problem hiding this comment.
Isn't this a different meaning now? Before it would have been the full stack trace while now it's the filtered. Wouldn't a better solution be to set the logging to trace or something for these detailed stack traces ? Instead of this change why not disable STACK_LOG if you don't want this output?
There was a problem hiding this comment.
@jdaugherty sensible defaults. logging a stracktrace 4x that has no useful information (especially with the groovy 4 clutter) quickly leads to a scenario where you can run out of disk space fast.
I have yet to see an example where the 4x logging provides anything useful. do you have one?
|
The more I have thought about this, the more I realize this is not a bug fix. This is a feature - we are not trimming stacktraces that got longer due to groovy 4. We are instead hiding traces that get logged multiple times. I think the original behavior needs to stay and we need to make the new feature opt in. |
This comment has been minimized.
This comment has been minimized.
|
I would probably label this as a bug. Or is this behavior intentional? This is the log output when visiting package com.example
class StackTraceController {
def show() {
display()
}
private void display() {
notHere
}
}> <
> ____ _ _ <
> / ___|_ __ __ _(_) |___ <
> | | _| '__/ _` | | / __| <
> | |_| | | | (_| | | \__ \ <
> \____|_| \__,_|_|_|___/ <
> https://grails.apache.org <
> <
Grails application running at http://localhost:8080 in environment: development
2026-04-09T13:48:34.488+02:00 ERROR 2325051 --- [p1601249101-110] StackTrace : Full Stack Trace:
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:118)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:215)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:192)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:110)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:527)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:751)
at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1622)
at org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:195)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:80)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:110)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1555)
at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:822)
at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:438)
at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:470)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575)
at org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:717)
at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1102)
at org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)
at org.eclipse.jetty.server.handler.EventsHandler.handle(EventsHandler.java:81)
at org.eclipse.jetty.server.Server.handle(Server.java:182)
at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:721)
at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:416)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:981)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1211)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1166)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: groovy.lang.MissingPropertyException: No such property: notHere for class: com.example.StackTraceController
at grails.artefact.gsp.TagLibraryInvoker$Trait$Helper.propertyMissing(TagLibraryInvoker.groovy:125)
at com.example.StackTraceController.propertyMissing(StackTraceController.groovy)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343)
at groovy.lang.MetaClassImpl.invokeMissingProperty(MetaClassImpl.java:852)
at groovy.lang.MetaClassImpl$11.getProperty(MetaClassImpl.java:2181)
at org.codehaus.groovy.runtime.callsite.GetEffectivePogoPropertySite.getProperty(GetEffectivePogoPropertySite.java:90)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:341)
at com.example.StackTraceController.display(StackTraceController.groovy:10)
at com.example.StackTraceController.show(StackTraceController.groovy:6)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
... 55 common frames omitted
2026-04-09T13:48:34.498+02:00 ERROR 2325051 --- [p1601249101-110] StackTrace : Full Stack Trace:
groovy.lang.MissingPropertyException: No such property: notHere for class: com.example.StackTraceController
at grails.artefact.gsp.TagLibraryInvoker$Trait$Helper.propertyMissing(TagLibraryInvoker.groovy:125)
at com.example.StackTraceController.propertyMissing(StackTraceController.groovy)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343)
at groovy.lang.MetaClassImpl.invokeMissingProperty(MetaClassImpl.java:852)
at groovy.lang.MetaClassImpl$11.getProperty(MetaClassImpl.java:2181)
at org.codehaus.groovy.runtime.callsite.GetEffectivePogoPropertySite.getProperty(GetEffectivePogoPropertySite.java:90)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:341)
at com.example.StackTraceController.display(StackTraceController.groovy:10)
at com.example.StackTraceController.show(StackTraceController.groovy:6)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:215)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:192)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:110)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:527)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:751)
at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1622)
at org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:195)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:80)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:110)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1555)
at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:822)
at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:438)
at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:470)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575)
at org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:717)
at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1102)
at org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)
at org.eclipse.jetty.server.handler.EventsHandler.handle(EventsHandler.java:81)
at org.eclipse.jetty.server.Server.handle(Server.java:182)
at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:721)
at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:416)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:981)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1211)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1166)
at java.base/java.lang.Thread.run(Thread.java:1583)
2026-04-09T13:48:34.502+02:00 ERROR 2325051 --- [p1601249101-110] StackTrace : Full Stack Trace:
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:118)
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:215)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:192)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:110)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:751)
at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1622)
at org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:195)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:80)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1555)
at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:822)
at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:438)
at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:470)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575)
at org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:717)
at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1102)
at org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)
at org.eclipse.jetty.server.handler.EventsHandler.handle(EventsHandler.java:81)
at org.eclipse.jetty.server.Server.handle(Server.java:182)
at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:721)
at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:416)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:981)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1211)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1166)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: groovy.lang.MissingPropertyException: No such property: notHere for class: com.example.StackTraceController
at grails.artefact.gsp.TagLibraryInvoker$Trait$Helper.propertyMissing(TagLibraryInvoker.groovy:125)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at com.example.StackTraceController.display(StackTraceController.groovy:10)
at com.example.StackTraceController.show(StackTraceController.groovy:6)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
... 44 common frames omitted
2026-04-09T13:48:34.524+02:00 ERROR 2325051 --- [p1601249101-110] o.g.web.errors.GrailsExceptionResolver : MissingPropertyException occurred when processing request: [GET] /stackTrace/show
No such property: notHere for class: com.example.StackTraceController. Stacktrace follows:
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:118)
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:215)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:192)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:110)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:751)
at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1622)
at org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:195)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:80)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1555)
at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:822)
at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:438)
at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:470)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575)
at org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:717)
at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1102)
at org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)
at org.eclipse.jetty.server.handler.EventsHandler.handle(EventsHandler.java:81)
at org.eclipse.jetty.server.Server.handle(Server.java:182)
at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:721)
at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:416)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:981)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1211)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1166)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: groovy.lang.MissingPropertyException: No such property: notHere for class: com.example.StackTraceController
at grails.artefact.gsp.TagLibraryInvoker$Trait$Helper.propertyMissing(TagLibraryInvoker.groovy:125)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at com.example.StackTraceController.display(StackTraceController.groovy:10)
at com.example.StackTraceController.show(StackTraceController.groovy:6)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
... 44 common frames omitted
|
|
This is the log output with these changes: > <
> ____ _ _ <
> / ___|_ __ __ _(_) |___ <
> | | _| '__/ _` | | / __| <
> | |_| | | | (_| | | \__ \ <
> \____|_| \__,_|_|_|___/ <
> https://grails.apache.org <
> <
grails-test-examples-app5: 0.1 | JVM: BellSoft 17.0.18
Grails: 7.1.0-SNAPSHOT | Groovy: 4.0.31 | Spring Boot: 3.5.13
Spring: 6.2.17
Grails application running at http://localhost:8080 in environment: development
2026-04-09 15:32:18.490 ERROR --- [nio-8080-exec-1] StackTrace : Full Stack Trace:
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:215)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:192)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:110)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:80)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70)
at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: groovy.lang.MissingPropertyException: No such property: notHere for class: app5.TargetController
at grails.artefact.gsp.TagLibraryInvoker$Trait$Helper.propertyMissing(TagLibraryInvoker.groovy:125)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at app5.TargetController.display(TargetController.groovy:41)
at app5.TargetController.show(TargetController.groovy:37)
... 13 common frames omitted
2026-04-09 15:32:18.517 ERROR --- [nio-8080-exec-1] o.g.web.errors.GrailsExceptionResolver : MissingPropertyException occurred when processing request: [GET] /target/show
No such property: notHere for class: app5.TargetController. Stacktrace follows:
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:215)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:192)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:110)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:80)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70)
at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: groovy.lang.MissingPropertyException: No such property: notHere for class: app5.TargetController
at grails.artefact.gsp.TagLibraryInvoker$Trait$Helper.propertyMissing(TagLibraryInvoker.groovy:125)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at app5.TargetController.display(TargetController.groovy:41)
at app5.TargetController.show(TargetController.groovy:37)
... 13 common frames omitted |
- cleanup and clarify current tests - add test for logging once
Let `CountingStackTraceFilterer` call `super.filter(source)`.
|
I think the intent is to disable the logging on this filter, yes? Then you don't see the the full stacktraces. |
|
Why not change the default configuration to just hide logs from grails-bootstrap/src/main/groovy/org/grails/exceptions/reporting/DefaultStackTraceFilterer.java then there wouldn't be a duplicate log right? We also appear to have removed the cut off package, which means it could filter our own package |
Yes, that might be one option/solution! <logger name="StackTrace" level="OFF" />> <
> ____ _ _ <
> / ___|_ __ __ _(_) |___ <
> | | _| '__/ _` | | / __| <
> | |_| | | | (_| | | \__ \ <
> \____|_| \__,_|_|_|___/ <
> https://grails.apache.org <
> <
grails-test-examples-app5: 0.1 | JVM: BellSoft 17.0.18
Grails: 8.0.0-SNAPSHOT | Groovy: 4.0.31 | Spring Boot: 3.5.13
Spring: 6.2.17
Grails application running at http://localhost:8080 in environment: development
2026-04-09 16:33:53.713 ERROR --- [nio-8080-exec-1] o.g.web.errors.GrailsExceptionResolver : MissingPropertyException occurred when processing request: [GET] /target/show
No such property: notHere for class: app5.TargetController. Stacktrace follows:
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:215)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:192)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:110)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:80)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70)
at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: groovy.lang.MissingPropertyException: No such property: notHere for class: app5.TargetController
at grails.artefact.gsp.TagLibraryInvoker$Trait$Helper.propertyMissing(TagLibraryInvoker.groovy:125)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at app5.TargetController.display(TargetController.groovy:41)
Caused by: groovy.lang.MissingPropertyException: No such property: notHere for class: app5.TargetController
at app5.TargetController.show(TargetController.groovy:37)
... 13 common frames omitted |
|
Curiosly enough, setting the system property |
✅ All tests passed ✅🏷️ Commit: ce60438 Learn more about TestLens at testlens.app. |
|
Hmm, it looks to me like the console logging from So, if that is the case, disabling logging output for <logger name="StackTrace" level="OFF" />However, In that case I do think the logger should have a more descriptive name, like |
Fix duplicate stack trace logging in DefaultStackTraceFilterer
When an exception occurs in a Grails controller, the same error is logged multiple times to the
StackTracelogger. For a typicalInvocationTargetExceptionwrapping aRuntimeException, this produces 3-4 separate ERROR log entries for a single request:StackTracelogger: Full stack trace of the wrapper exceptionStackTracelogger: Full stack trace of the root causeStackTracelogger: Full stack trace of the wrapper again (condensed)GrailsExceptionResolverlogger: The exception with request contextEntries 1-3 all come from
DefaultStackTraceFilterer.filter(). Thefilter(Throwable source, boolean recursive)method walks thecause chain and calls
filter(Throwable source)for each throwable. Butfilter(Throwable source)logs the full stack trace viaSTACK_LOG.error(FULL_STACK_TRACE_MESSAGE, source)every time it trims a trace — so each cause in the chain gets its own log entry.This is compounded by the fact that
GrailsExceptionResolver.logStackTrace()then logs the exception a final time with requestcontext. The filterer and the resolver were written independently — the filterer doesn't know the resolver will also log, and the
resolver doesn't know the filterer already logged.
Fix
Extract the stack trace trimming logic into a private
doFilter(Throwable)method that filters without logging. The recursive walkcalls
doFilter()for each cause in the chain. The publicfilter(Throwable source)method callsdoFilter()and then logs thefull stack trace once for the top-level exception (which naturally includes its causes in the log output).
This reduces the
StackTracelogger output from N entries (one per cause) to 1 entry, while preserving all existing behavior:StackTraceFiltererinterface is unchangedGrailsExceptionResolversubclasses are unaffected