diff --git a/grails-core/src/test/groovy/grails/util/GrailsUtilStackFiltererSpec.groovy b/grails-core/src/test/groovy/grails/util/GrailsUtilStackFiltererSpec.groovy index c0c419bcd3b..e6c132315ab 100644 --- a/grails-core/src/test/groovy/grails/util/GrailsUtilStackFiltererSpec.groovy +++ b/grails-core/src/test/groovy/grails/util/GrailsUtilStackFiltererSpec.groovy @@ -105,8 +105,8 @@ class GrailsUtilStackFiltererSpec extends Specification { } def 'installed DefaultStackTraceFilterer honours logFullStackTraceOnFilter=false'() { - given: 'a configured log appender to capture the StackTrace log entry' - def logCapture = new LogCapture('StackTrace') + given: 'a capture of the dedicated STACK_LOG logger' + def logCapture = new LogCapture(DefaultStackTraceFilterer.STACK_LOG_NAME) and: 'a filterer with the side-effect emission disabled' def quietFilterer = new DefaultStackTraceFilterer() @@ -117,15 +117,15 @@ class GrailsUtilStackFiltererSpec extends Specification { GrailsUtil.deepSanitize(exceptionWithApplicationFrame()) then: "no 'Full Stack Trace:' entry is emitted" - logCapture.events.count { it.formattedMessage.contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE) } == 0 + logCapture.events.every { !it.formattedMessage.contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE) } cleanup: logCapture.close() } def 'installed DefaultStackTraceFilterer emits Full Stack Trace by default'() { - given: 'a configured log appender to capture the StackTrace log entry' - def logCapture = new LogCapture('StackTrace') + given: 'a capture of the dedicated STACK_LOG logger' + def logCapture = new LogCapture(DefaultStackTraceFilterer.STACK_LOG_NAME) and: 'a filterer with the default (enabled) side-effect emission' def loudFilterer = new DefaultStackTraceFilterer() diff --git a/grails-core/src/test/groovy/org/apache/grails/core/GrailsBootstrapRegistryInitializerSpec.groovy b/grails-core/src/test/groovy/org/apache/grails/core/GrailsBootstrapRegistryInitializerSpec.groovy index 6fe1db680cd..a3576936f42 100644 --- a/grails-core/src/test/groovy/org/apache/grails/core/GrailsBootstrapRegistryInitializerSpec.groovy +++ b/grails-core/src/test/groovy/org/apache/grails/core/GrailsBootstrapRegistryInitializerSpec.groovy @@ -226,14 +226,14 @@ class GrailsBootstrapRegistryInitializerSpec extends Specification { def context = contextWithProperties([ (Settings.SETTING_LOG_FULL_STACKTRACE_ON_FILTER): 'false' ]) - def logCapture = new LogCapture('StackTrace') + def logCapture = new LogCapture(DefaultStackTraceFilterer.STACK_LOG_NAME) when: closeBootstrapContext(context) GrailsUtil.deepSanitize(exceptionWithApplicationFrame()) then: "no 'Full Stack Trace:' entry is emitted" - logCapture.events.count { it.formattedMessage.contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE) } == 0 + logCapture.events.every { !it.formattedMessage.contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE) } cleanup: logCapture.close() @@ -242,7 +242,7 @@ class GrailsBootstrapRegistryInitializerSpec extends Specification { def 'defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer'() { given: def context = contextWithProperties([:]) - def logCapture = new LogCapture('StackTrace') + def logCapture = new LogCapture(DefaultStackTraceFilterer.STACK_LOG_NAME) when: closeBootstrapContext(context) diff --git a/grails-web-mvc/build.gradle b/grails-web-mvc/build.gradle index 29f94b12cc2..eaa941c5d4c 100644 --- a/grails-web-mvc/build.gradle +++ b/grails-web-mvc/build.gradle @@ -62,7 +62,7 @@ dependencies { api 'org.slf4j:jcl-over-slf4j' // Testing - testImplementation 'org.slf4j:slf4j-simple' + testImplementation(testFixtures(project(':grails-core'))) testImplementation 'org.spockframework:spock-core' } diff --git a/grails-web-mvc/src/test/groovy/org/grails/web/errors/GrailsExceptionResolverSpec.groovy b/grails-web-mvc/src/test/groovy/org/grails/web/errors/GrailsExceptionResolverSpec.groovy index 2b8dd9ab987..5bd86ab59b4 100644 --- a/grails-web-mvc/src/test/groovy/org/grails/web/errors/GrailsExceptionResolverSpec.groovy +++ b/grails-web-mvc/src/test/groovy/org/grails/web/errors/GrailsExceptionResolverSpec.groovy @@ -22,6 +22,7 @@ import grails.config.Config import grails.core.GrailsApplication import grails.web.mapping.UrlMappingsHolder import grails.web.mapping.exceptions.UrlMappingException +import org.apache.grails.core.testing.support.LogCapture import org.grails.exceptions.reporting.DefaultStackTraceFilterer import org.apache.grails.core.GrailsBootstrapRegistryInitializer import org.grails.exceptions.reporting.StackTraceFilterer @@ -53,10 +54,9 @@ class GrailsExceptionResolverSpec extends Specification { } void "logStackTrace emits only the resolver log"() { - given: "Captured System.err" - def originalErr = System.err - def baos = new ByteArrayOutputStream() - System.setErr(new PrintStream(baos, true)) + given: "captures of both the resolver logger and the StackTrace logger" + def resolverLog = new LogCapture(GrailsExceptionResolver) + def stackLog = new LogCapture(DefaultStackTraceFilterer.STACK_LOG_NAME) and: "A resolver with no grailsApplication wired" def resolver = new GrailsExceptionResolver() @@ -67,21 +67,17 @@ class GrailsExceptionResolverSpec extends Specification { resolver.logStackTrace(exception, request) then: "Only the GrailsExceptionResolver logger emits; StackTrace logger is silent" - System.err.flush() - def captured = baos.toString() - captured.contains('o.g.web.errors.GrailsExceptionResolver') || - captured.contains('org.grails.web.errors.GrailsExceptionResolver') - !captured.contains('ERROR StackTrace ') + resolverLog.events.any { it.loggerName == GrailsExceptionResolver.name } + stackLog.events.isEmpty() cleanup: - System.setErr(originalErr) + resolverLog.close() + stackLog.close() } void "logFullStackTraceIfEnabled is a no-op when the opt-in property is unset"() { - given: "Captured System.err" - def originalErr = System.err - def baos = new ByteArrayOutputStream() - System.setErr(new PrintStream(baos, true)) + given: "a capture of the StackTrace logger" + def stackLog = new LogCapture(DefaultStackTraceFilterer.STACK_LOG_NAME) and: "A resolver with no grailsApplication wired" def resolver = new GrailsExceptionResolver() @@ -91,11 +87,10 @@ class GrailsExceptionResolverSpec extends Specification { resolver.logFullStackTraceIfEnabled(exception) then: "No StackTrace log entry is emitted" - System.err.flush() - !baos.toString().contains('ERROR StackTrace ') + stackLog.events.isEmpty() cleanup: - System.setErr(originalErr) + stackLog.close() } void "getRequestLogMessage appends auditor when logAuditor is enabled and the lookup returns a value"() { @@ -264,10 +259,9 @@ class GrailsExceptionResolverSpec extends Specification { } void "logFullStackTraceIfEnabled emits the unfiltered trace when opt-in is enabled, and filterStackTrace then removes internal frames so the resolver log only sees the filtered trace"() { - given: "Captured System.err" - def originalErr = System.err - def baos = new ByteArrayOutputStream() - System.setErr(new PrintStream(baos, true)) + given: "captures of both the resolver logger and the StackTrace logger" + def resolverLog = new LogCapture(GrailsExceptionResolver) + def stackLog = new LogCapture(DefaultStackTraceFilterer.STACK_LOG_NAME) and: "A resolver whose config opts in to full stack trace logging" def config = Mock(Config) @@ -297,22 +291,30 @@ class GrailsExceptionResolverSpec extends Specification { resolver.filterStackTrace(exception) resolver.logStackTrace(exception, request) - then: "Both loggers emit" - System.err.flush() - def captured = baos.toString() - captured.contains('ERROR StackTrace ') - captured.contains('Full Stack Trace:') - captured.contains('o.g.web.errors.GrailsExceptionResolver') || - captured.contains('org.grails.web.errors.GrailsExceptionResolver') + then: "Both loggers emit exactly once" + stackLog.events.size() == 1 + resolverLog.events.size() == 1 + stackLog.events[0].formattedMessage.contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE) + resolverLog.events[0].loggerName == GrailsExceptionResolver.name - and: "The application frame appears in both log entries" - captured.count('com.example.MyController.show(MyController.groovy:10)') == 2 + and: "The application frame appears in both the unfiltered and filtered log entries" + [stackLog.events[0], resolverLog.events[0]].every { event -> + event.throwableProxy.stackTraceElementProxyArray.any { + it.stackTraceElement.className == 'com.example.MyController' + } + } - and: "The internal frame appears only once — in the unfiltered StackTrace entry, not in the filtered resolver entry" - captured.count('java.lang.reflect.Method.invoke(Method.java:580)') == 1 + and: "The internal frame appears only in the unfiltered StackTrace entry, not in the filtered resolver entry" + stackLog.events[0].throwableProxy.stackTraceElementProxyArray.any { + it.stackTraceElement.className == 'java.lang.reflect.Method' + } + resolverLog.events[0].throwableProxy.stackTraceElementProxyArray.every { + it.stackTraceElement.className != 'java.lang.reflect.Method' + } cleanup: - System.setErr(originalErr) + resolverLog.close() + stackLog.close() } void "getRequestLogMessage masks excluded request parameters case-insensitively"() {