diff --git a/grace-bootstrap/src/main/groovy/org/grails/exceptions/reporting/DefaultStackTraceFilterer.java b/grace-bootstrap/src/main/groovy/org/grails/exceptions/reporting/DefaultStackTraceFilterer.java index 5d6e938a62..9393a360b3 100644 --- a/grace-bootstrap/src/main/groovy/org/grails/exceptions/reporting/DefaultStackTraceFilterer.java +++ b/grace-bootstrap/src/main/groovy/org/grails/exceptions/reporting/DefaultStackTraceFilterer.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2022 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,9 @@ /** * Default implementation of StackTraceFilterer. * - * @since 2.0 * @author Graeme Rocher + * @author Michael Yan + * @since 2.0 */ public class DefaultStackTraceFilterer implements StackTraceFilterer { @@ -37,16 +38,18 @@ public class DefaultStackTraceFilterer implements StackTraceFilterer { private static final String[] DEFAULT_INTERNAL_PACKAGES = new String[] { "org.codehaus.groovy.runtime.", "org.codehaus.groovy.reflection.", + "org.codehaus.groovy.vmplugin.", "org.codehaus.groovy.ast.", "org.springframework.web.filter", "org.springframework.boot.actuate", - "org.mortbay.", "groovy.lang.", "org.apache.catalina.", "org.apache.coyote.", "org.apache.tomcat.", "net.sf.cglib.proxy.", "sun.", + "jdk.internal.reflect.", + "java.lang.", "java.lang.reflect.", "org.springframework.boot.devtools.", "org.springsource.loaded.", @@ -69,6 +72,7 @@ public DefaultStackTraceFilterer(boolean shouldFilter) { this.packagesToFilter.addAll(Arrays.asList(DEFAULT_INTERNAL_PACKAGES)); } + @Override public void addInternalPackage(String name) { if (name == null) { throw new IllegalArgumentException("Package name cannot be null"); @@ -76,10 +80,12 @@ public void addInternalPackage(String name) { this.packagesToFilter.add(name); } + @Override public void setCutOffPackage(String cutOffPackage) { this.cutOffPackage = cutOffPackage; } + @Override public Throwable filter(Throwable source, boolean recursive) { if (recursive) { Throwable current = source; @@ -87,10 +93,12 @@ public Throwable filter(Throwable source, boolean recursive) { current = filter(current); current = current.getCause(); } + return current; } return filter(source); } + @Override public Throwable filter(Throwable source) { if (this.shouldFilter) { StackTraceElement[] trace = source.getStackTrace(); @@ -149,8 +157,14 @@ protected boolean isApplicationClass(String className) { return true; } + @Override public void setShouldFilter(boolean shouldFilter) { this.shouldFilter = shouldFilter; } + @Override + public boolean isShouldFilter() { + return this.shouldFilter; + } + } diff --git a/grace-bootstrap/src/main/groovy/org/grails/exceptions/reporting/StackTraceFilterer.java b/grace-bootstrap/src/main/groovy/org/grails/exceptions/reporting/StackTraceFilterer.java index 91ec271abe..4a8567393c 100644 --- a/grace-bootstrap/src/main/groovy/org/grails/exceptions/reporting/StackTraceFilterer.java +++ b/grace-bootstrap/src/main/groovy/org/grails/exceptions/reporting/StackTraceFilterer.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2022 the original author or authors. + * Copyright 2011-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,9 @@ /** * Improves the output of stack traces produced by exceptions in a Grails application. * - * @since 2.0 * @author Graeme Rocher + * @author Michaael Yan + * @since 2.0 */ public interface StackTraceFilterer { @@ -62,4 +63,9 @@ public interface StackTraceFilterer { */ void setShouldFilter(boolean shouldFilter); + /** + * @return boolean Whether to filter stack traces or not + */ + boolean isShouldFilter(); + } diff --git a/grace-core/src/main/groovy/grails/validation/ValidationException.java b/grace-core/src/main/groovy/grails/validation/ValidationException.java index 0059c64e2c..1e5c1fbf13 100644 --- a/grace-core/src/main/groovy/grails/validation/ValidationException.java +++ b/grace-core/src/main/groovy/grails/validation/ValidationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2022 the original author or authors. + * Copyright 2009-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ * Thrown when validation fails during a .save(). * * @author Jeff Brown + * @author Michael Yan * @since 1.2 */ public class ValidationException extends GrailsException { @@ -54,8 +55,11 @@ public static String formatErrors(Errors errors) { public static String formatErrors(Errors errors, String msg) { StringBuilder b = new StringBuilder(); - if (msg != null && msg.length() > 0) { - b.append(msg).append(":\n"); + if (msg != null && !msg.isEmpty()) { + b.append(msg); + } + if (errors.hasErrors()) { + b.append(":\n"); } for (ObjectError error : errors.getAllErrors()) { b.append("- ").append(error).append("\n"); diff --git a/grace-core/src/main/groovy/org/grails/core/exceptions/DefaultErrorsPrinter.groovy b/grace-core/src/main/groovy/org/grails/core/exceptions/DefaultErrorsPrinter.groovy index c44da1556a..205a16d79c 100644 --- a/grace-core/src/main/groovy/org/grails/core/exceptions/DefaultErrorsPrinter.groovy +++ b/grace-core/src/main/groovy/org/grails/core/exceptions/DefaultErrorsPrinter.groovy @@ -31,6 +31,7 @@ import org.grails.exceptions.reporting.StackTracePrinter * * @author Graeme Rocher * @author Marc Palmer + * @author Michael Yan * @since 2.0 */ class DefaultErrorsPrinter extends DefaultStackTracePrinter implements CodeSnippetPrinter { @@ -82,7 +83,7 @@ class DefaultErrorsPrinter extends DefaultStackTracePrinter implements CodeSnipp } } - if (!className || !lineNumber) { + if (!className || lineNumber < 0) { continue } diff --git a/grace-core/src/test/groovy/org/grails/exception/reporting/StackTracePrinterSpec.groovy b/grace-core/src/test/groovy/org/grails/exception/reporting/StackTracePrinterSpec.groovy index 2326049c94..b399922297 100644 --- a/grace-core/src/test/groovy/org/grails/exception/reporting/StackTracePrinterSpec.groovy +++ b/grace-core/src/test/groovy/org/grails/exception/reporting/StackTracePrinterSpec.groovy @@ -29,10 +29,9 @@ class StackTracePrinterSpec extends Specification { then:"The formatting is correctly applied" result != null - result.contains '| 7 | callMe . . . . . . in test.FooController' + result.contains '->> 7 | callMe in test.FooController' } - @Requires({jvm.isJava8()}) void "Test pretty print nested stack trace"() { given: "a controller that throws an exception" final gcl = new GroovyClassLoader() diff --git a/grace-gradle-plugin/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy b/grace-gradle-plugin/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy index e41db4d46a..8a040237ac 100644 --- a/grace-gradle-plugin/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy +++ b/grace-gradle-plugin/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy @@ -316,7 +316,6 @@ class GrailsGradlePlugin extends GroovyPlugin { task.systemProperty Metadata.APPLICATION_VERSION, project.version task.systemProperty Metadata.APPLICATION_GRAILS_VERSION, grailsVersion task.systemProperty Environment.KEY, defaultGrailsEnv - task.systemProperty Environment.FULL_STACKTRACE, System.getProperty(Environment.FULL_STACKTRACE) ?: '' if (task.minHeapSize == null) { task.minHeapSize = '768m' } diff --git a/grace-plugin-controllers/src/main/groovy/org/grails/plugins/web/controllers/ControllersPluginConfiguration.java b/grace-plugin-controllers/src/main/groovy/org/grails/plugins/web/controllers/ControllersPluginConfiguration.java index 0f4d47bf55..4d7d581fea 100644 --- a/grace-plugin-controllers/src/main/groovy/org/grails/plugins/web/controllers/ControllersPluginConfiguration.java +++ b/grace-plugin-controllers/src/main/groovy/org/grails/plugins/web/controllers/ControllersPluginConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import jakarta.servlet.MultipartConfigElement; import jakarta.servlet.Servlet; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -43,7 +44,10 @@ import grails.config.Config; import grails.config.Settings; import grails.core.GrailsApplication; +import grails.util.Environment; +import org.grails.exceptions.reporting.DefaultStackTraceFilterer; +import org.grails.exceptions.reporting.StackTraceFilterer; import org.grails.web.errors.GrailsExceptionResolver; import org.grails.web.filters.HiddenHttpMethodFilter; import org.grails.web.filters.OrderedHiddenHttpMethodFilter; @@ -81,11 +85,30 @@ public FilterRegistrationBean grailsWebRequestFilter( } @Bean - public GrailsExceptionResolver exceptionHandler() { + @ConditionalOnMissingBean + public StackTraceFilterer stackTraceFilterer(ObjectProvider grailsApplicationProvider) { + GrailsApplication grailsApplication = grailsApplicationProvider.getIfAvailable(); + Config config = grailsApplication.getConfig(); + boolean shouldFilter = !config.getProperty(Environment.FULL_STACKTRACE, Boolean.class, Boolean.FALSE); + Class filtererClass = config.getProperty(Settings.SETTING_LOGGING_STACKTRACE_FILTER_CLASS, + Class.class, DefaultStackTraceFilterer.class); + + StackTraceFilterer filtererBean = BeanUtils.instantiateClass(filtererClass, StackTraceFilterer.class); + filtererBean.setShouldFilter(shouldFilter); + return filtererBean; + } + + @Bean + public GrailsExceptionResolver exceptionHandler(ObjectProvider grailsApplicationProvider, + ObjectProvider stackTraceFiltererObjectProvider) { GrailsExceptionResolver exceptionResolver = new GrailsExceptionResolver(); + exceptionResolver.setGrailsApplication(grailsApplicationProvider.getIfAvailable()); + exceptionResolver.setStackTraceFilterer(stackTraceFiltererObjectProvider.getIfAvailable()); + Properties exceptionMappings = new Properties(); exceptionMappings.put("java.lang.Exception", "/error"); exceptionResolver.setExceptionMappings(exceptionMappings); + return exceptionResolver; } diff --git a/grace-plugin-controllers/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/grace-plugin-controllers/src/main/resources/META-INF/additional-spring-configuration-metadata.json index d998d39c22..589b3c03f7 100644 --- a/grace-plugin-controllers/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/grace-plugin-controllers/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -37,6 +37,12 @@ "type": "java.lang.String", "defaultValue": "\/static\/**" }, + { + "name": "grails.full.stacktrace", + "description": "Whether the display of full stack traces is needed.", + "type": "java.lang.Boolean", + "defaultValue": false + }, { "name": "grails.exceptionresolver.logRequestParameters", "description": "Whether to log request parameters in the console.", diff --git a/grace-plugin-gsp/src/main/groovy/org/grails/plugins/web/GroovyPagesAutoConfiguration.java b/grace-plugin-gsp/src/main/groovy/org/grails/plugins/web/GroovyPagesAutoConfiguration.java index 7c8020cf6f..fb200fd762 100644 --- a/grace-plugin-gsp/src/main/groovy/org/grails/plugins/web/GroovyPagesAutoConfiguration.java +++ b/grace-plugin-gsp/src/main/groovy/org/grails/plugins/web/GroovyPagesAutoConfiguration.java @@ -21,6 +21,7 @@ import java.util.Map; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -157,7 +158,6 @@ public CachingGrailsConventionGroovyPageLocator groovyPageLocator(GroovyPagesPro } @Bean - @ConditionalOnMissingBean public ResourceLocator grailsResourceLocator(GroovyPagesProperties groovyPagesProperties, ObjectProvider grailsApplication) { Config config = grailsApplication.getIfAvailable().getConfig(); Environment env = Environment.getCurrent(); @@ -168,6 +168,7 @@ public ResourceLocator grailsResourceLocator(GroovyPagesProperties groovyPagesPr (developmentMode && env == Environment.DEVELOPMENT) ? 0L : groovyPagesProperties.getReload().getInterval()); CachingGroovyPageStaticResourceLocator groovyPageStaticResourceLocator = new CachingGroovyPageStaticResourceLocator(); + groovyPageStaticResourceLocator.setSearchLocation(BuildSettings.BASE_DIR.getAbsolutePath()); if (enableReload) { groovyPageStaticResourceLocator.setCacheTimeout(gspCacheTimeout); @@ -178,7 +179,8 @@ public ResourceLocator grailsResourceLocator(GroovyPagesProperties groovyPagesPr @Bean @ConditionalOnMissingBean - public ErrorsViewStackTracePrinter errorsViewStackTracePrinter(ObjectProvider grailsResourceLocator) { + public ErrorsViewStackTracePrinter errorsViewStackTracePrinter( + @Qualifier("grailsResourceLocator") ObjectProvider grailsResourceLocator) { return new ErrorsViewStackTracePrinter(grailsResourceLocator.getObject()); } diff --git a/grace-test-suite-uber/src/test/groovy/org/grails/web/errors/GrailsExceptionResolverTests.groovy b/grace-test-suite-uber/src/test/groovy/org/grails/web/errors/GrailsExceptionResolverTests.groovy index aa73015c87..adf87a6527 100644 --- a/grace-test-suite-uber/src/test/groovy/org/grails/web/errors/GrailsExceptionResolverTests.groovy +++ b/grace-test-suite-uber/src/test/groovy/org/grails/web/errors/GrailsExceptionResolverTests.groovy @@ -8,6 +8,7 @@ import grails.web.CamelCaseUrlConverter import grails.web.UrlConverter import grails.web.mapping.UrlMappingsHolder import org.grails.config.PropertySourcesConfig +import org.grails.exceptions.reporting.DefaultStackTraceFilterer import org.grails.plugins.testing.GrailsMockHttpServletRequest import org.grails.plugins.testing.GrailsMockHttpServletResponse import org.grails.support.MockApplicationContext @@ -89,6 +90,7 @@ class GrailsExceptionResolverTests { resolver.servletContext = mockContext resolver.exceptionMappings = ['java.lang.Exception': '/error'] as Properties resolver.grailsApplication = application + resolver.stackFilterer = new DefaultStackTraceFilterer() def ex = new Exception() def request = webRequest.currentRequest @@ -118,6 +120,7 @@ class GrailsExceptionResolverTests { resolver.servletContext = mockContext resolver.exceptionMappings = ['java.lang.Exception': '/error'] as Properties resolver.grailsApplication = application + resolver.stackFilterer = new DefaultStackTraceFilterer() def ex = new Exception() def request = webRequest.currentRequest @@ -149,6 +152,7 @@ class GrailsExceptionResolverTests { resolver.servletContext = mockContext resolver.exceptionMappings = ['java.lang.Exception': '/error'] as Properties resolver.grailsApplication = application + resolver.stackFilterer = new DefaultStackTraceFilterer() def ex = new Exception() def request = webRequest.currentRequest @@ -175,14 +179,21 @@ grails.exceptionresolver.params.exclude = ['jennysPhoneNumber'] request.addParameter "jennysPhoneNumber", "8675309" System.setProperty(Environment.KEY, Environment.DEVELOPMENT.name) - def msg = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))).getRequestLogMessage(new RuntimeException("bad things happened"), request) - - assertEquals '''RuntimeException occurred when processing request: [GET] /execute/me - parameters: -foo: bar -one: two -jennysPhoneNumber: *** -bad things happened. Stacktrace follows:'''.replaceAll('[\n\r]', ''), msg.replaceAll('[\n\r]', '') - + def resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))) + resolver.stackFilterer = new DefaultStackTraceFilterer() + def msg = resolver.getRequestLogMessage(new RuntimeException("bad things happened"), request) + + assertEquals '''RuntimeException occurred when processing request: +URI: /execute/me +Method: GET +Message: bad things happened +Parameters: + - foo: bar + - one: two + - jennysPhoneNumber: [FILTERED] + +Filtered stacktrace: +'''.replaceAll('[\n\r]', ''), msg.replaceAll('[\n\r]', '') } @Test @@ -199,18 +210,24 @@ grails.exceptionresolver.params.exclude = ['jennysPhoneNumber'] request.addParameter "jennysPhoneNumber", "8675309" System.setProperty(Environment.KEY, Environment.DEVELOPMENT.name) - def msg = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))).getRequestLogMessage(request) - - assertEquals '''Exception occurred when processing request: [GET] /execute/me - parameters: -foo: bar -one: two -jennysPhoneNumber: *** -Stacktrace follows:'''.replaceAll('[\n\r]', ''), msg.replaceAll('[\n\r]', '') + def resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))) + resolver.stackFilterer = new DefaultStackTraceFilterer() + def msg = resolver.getRequestLogMessage(request) + + assertEquals '''Exception occurred when processing request: +URI: /execute/me +Method: GET +Parameters: + - foo: bar + - one: two + - jennysPhoneNumber: [FILTERED] + +Filtered stacktrace: +'''.replaceAll('[\n\r]', ''), msg.replaceAll('[\n\r]', '') } @Test void testDisablingRequestParameterLogging() { - def oldEnvName = Environment.current.name try { def request = new MockHttpServletRequest() @@ -219,23 +236,36 @@ Stacktrace follows:'''.replaceAll('[\n\r]', ''), msg.replaceAll('[\n\r]', '') request.addParameter "foo", "bar" request.addParameter "one", "two" - def msgWithParameters = '''Exception occurred when processing request: [GET] /execute/me - parameters: -foo: bar -one: two -Stacktrace follows:'''.replaceAll('[\n\r]', '') - def msgWithoutParameters = '''Exception occurred when processing request: [GET] /execute/me -Stacktrace follows:'''.replaceAll('[\n\r]', '') + def msgWithParameters = '''Exception occurred when processing request: +URI: /execute/me +Method: GET +Parameters: + - foo: bar + - one: two + +Filtered stacktrace:'''.replaceAll('[\n\r]', '') + def msgWithoutParameters = '''Exception occurred when processing request: +URI: /execute/me +Method: GET + +Filtered stacktrace:'''.replaceAll('[\n\r]', '') System.setProperty(Environment.KEY, Environment.DEVELOPMENT.name) - def msg = new GrailsExceptionResolver(grailsApplication:application).getRequestLogMessage(request) + def resolver = new GrailsExceptionResolver(grailsApplication:application) + resolver.stackFilterer = new DefaultStackTraceFilterer() + def msg = resolver.getRequestLogMessage(request) assertEquals msgWithParameters, msg.replaceAll('[\n\r]', '') System.setProperty(Environment.KEY, Environment.PRODUCTION.name) - msg = new GrailsExceptionResolver(grailsApplication:application).getRequestLogMessage(request) + resolver = new GrailsExceptionResolver(grailsApplication:application) + resolver.stackFilterer = new DefaultStackTraceFilterer() + msg = resolver.getRequestLogMessage(request) assertEquals msgWithoutParameters, msg.replaceAll('[\n\r]', '') System.setProperty(Environment.KEY, Environment.TEST.name) - msg = new GrailsExceptionResolver(grailsApplication:application).getRequestLogMessage(request) + resolver = new GrailsExceptionResolver(grailsApplication:application) + resolver.stackFilterer = new DefaultStackTraceFilterer() + msg = resolver.getRequestLogMessage(request) assertEquals msgWithoutParameters, msg.replaceAll('[\n\r]', '') def config = new ConfigSlurper().parse(''' @@ -243,15 +273,21 @@ grails.exceptionresolver.logRequestParameters = false ''') System.setProperty(Environment.KEY, Environment.DEVELOPMENT.name) - msg = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))).getRequestLogMessage(request) + resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))) + resolver.stackFilterer = new DefaultStackTraceFilterer() + msg = resolver.getRequestLogMessage(request) assertEquals msgWithoutParameters, msg.replaceAll('[\n\r]', '') System.setProperty(Environment.KEY, Environment.PRODUCTION.name) - msg = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))).getRequestLogMessage(request) + resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))) + resolver.stackFilterer = new DefaultStackTraceFilterer() + msg = resolver.getRequestLogMessage(request) assertEquals msgWithoutParameters, msg.replaceAll('[\n\r]', '') System.setProperty(Environment.KEY, Environment.TEST.name) - msg = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))).getRequestLogMessage(request) + resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))) + resolver.stackFilterer = new DefaultStackTraceFilterer() + msg = resolver.getRequestLogMessage(request) assertEquals msgWithoutParameters, msg.replaceAll('[\n\r]', '') config = new ConfigSlurper().parse(''' @@ -259,15 +295,21 @@ grails.exceptionresolver.logRequestParameters = true ''') System.setProperty(Environment.KEY, Environment.DEVELOPMENT.name) - msg = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))).getRequestLogMessage(request) + resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))) + resolver.stackFilterer = new DefaultStackTraceFilterer() + msg = resolver.getRequestLogMessage(request) assertEquals msgWithParameters, msg.replaceAll('[\n\r]', '') System.setProperty(Environment.KEY, Environment.PRODUCTION.name) - msg = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))).getRequestLogMessage(request) + resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))) + resolver.stackFilterer = new DefaultStackTraceFilterer() + msg = resolver.getRequestLogMessage(request) assertEquals msgWithParameters, msg.replaceAll('[\n\r]', '') System.setProperty(Environment.KEY, Environment.TEST.name) - msg = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))).getRequestLogMessage(request) + resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config))) + resolver.stackFilterer = new DefaultStackTraceFilterer() + msg = resolver.getRequestLogMessage(request) assertEquals msgWithParameters, msg.replaceAll('[\n\r]', '') } finally { System.setProperty(Environment.KEY, oldEnvName) diff --git a/grace-web-mvc/src/main/groovy/org/grails/web/errors/ErrorsViewStackTracePrinter.groovy b/grace-web-mvc/src/main/groovy/org/grails/web/errors/ErrorsViewStackTracePrinter.groovy index 1ca7cd7498..7025178027 100644 --- a/grace-web-mvc/src/main/groovy/org/grails/web/errors/ErrorsViewStackTracePrinter.groovy +++ b/grace-web-mvc/src/main/groovy/org/grails/web/errors/ErrorsViewStackTracePrinter.groovy @@ -17,14 +17,17 @@ package org.grails.web.errors import org.springframework.core.io.Resource +import grails.util.BuildSettings +import grails.util.GrailsStringUtils + import org.grails.core.exceptions.DefaultErrorsPrinter import org.grails.core.io.ResourceLocator -import org.grails.io.support.GrailsResourceUtils /** * Customized Stack trace output for the errors view. * * @author Graeme Rocher + * @author Michael Yan * @since 2.0 */ class ErrorsViewStackTracePrinter extends DefaultErrorsPrinter { @@ -59,10 +62,13 @@ class ErrorsViewStackTracePrinter extends DefaultErrorsPrinter { String path = resource.filename // try calc better path try { - String abs = resource.file.absolutePath - int i = abs.indexOf(GrailsResourceUtils.GRAILS_APP_DIR) - if (i > -1) { - path = abs[i..-1] + String abs = resource.getFile().absolutePath + String base = GrailsStringUtils.cleanPath(BuildSettings.BASE_DIR.absolutePath) + if (abs.startsWith(base)) { + path = abs - base + if (path.startsWith(File.separator)) { + path = path.substring(1) + } } } catch (ignored) { diff --git a/grace-web-mvc/src/main/groovy/org/grails/web/errors/GrailsExceptionResolver.java b/grace-web-mvc/src/main/groovy/org/grails/web/errors/GrailsExceptionResolver.java index 7f411fe349..74e6cb87a1 100644 --- a/grace-web-mvc/src/main/groovy/org/grails/web/errors/GrailsExceptionResolver.java +++ b/grace-web-mvc/src/main/groovy/org/grails/web/errors/GrailsExceptionResolver.java @@ -29,7 +29,6 @@ import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.runtime.InvokerInvocationException; -import org.springframework.beans.BeanUtils; import org.springframework.web.context.ServletContextAware; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; @@ -47,7 +46,6 @@ import org.grails.core.exceptions.GrailsRuntimeException; import org.grails.exceptions.ExceptionUtils; -import org.grails.exceptions.reporting.DefaultStackTraceFilterer; import org.grails.exceptions.reporting.StackTraceFilterer; import org.grails.web.mapping.DefaultUrlMappingInfo; import org.grails.web.mapping.UrlMappingUtils; @@ -59,13 +57,14 @@ * Wraps any runtime exceptions with a GrailsWrappedException instance. * * @author Graeme Rocher + * @author Michael Yan */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class GrailsExceptionResolver extends SimpleMappingExceptionResolver implements ServletContextAware, GrailsApplicationAware { public static final String EXCEPTION_ATTRIBUTE = WebUtils.EXCEPTION_ATTRIBUTE; - protected static final String LINE_SEPARATOR = System.getProperty("line.separator"); + protected static final String LINE_SEPARATOR = System.lineSeparator(); protected ServletContext servletContext; @@ -114,7 +113,6 @@ public void setServletContext(ServletContext servletContext) { @Override public void setGrailsApplication(GrailsApplication grailsApplication) { this.grailsApplication = grailsApplication; - createStackFilterer(); } /** @@ -265,8 +263,9 @@ protected String getRequestLogMessage(String exceptionName, HttpServletRequest r StringBuilder sb = new StringBuilder(); sb.append(exceptionName) - .append(" occurred when processing request: ") - .append("[").append(request.getMethod().toUpperCase()).append("] "); + .append(" occurred when processing request:") + .append(LINE_SEPARATOR); + sb.append("URI: "); if (request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE) != null) { sb.append(request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE)); @@ -275,6 +274,13 @@ protected String getRequestLogMessage(String exceptionName, HttpServletRequest r sb.append(request.getRequestURI()); } + sb.append(LINE_SEPARATOR); + sb.append("Method: ").append(request.getMethod().toUpperCase()).append(LINE_SEPARATOR); + + if (message != null) { + sb.append("Message: ").append(message).append(LINE_SEPARATOR); + } + Config config = this.grailsApplication != null ? this.grailsApplication.getConfig() : null; boolean shouldLogRequestParameters = config != null ? config.getProperty(Settings.SETTING_LOG_REQUEST_PARAMETERS, Boolean.class, Environment.getCurrent() == Environment.DEVELOPMENT) : false; @@ -285,9 +291,8 @@ protected String getRequestLogMessage(String exceptionName, HttpServletRequest r if (params.hasMoreElements()) { String param; String[] values; - int i; - sb.append(" - parameters:"); + sb.append("Parameters:"); List blackList = (config.getProperty(Settings.SETTING_EXCEPTION_RESOLVER_PARAM_EXCLUDES, List.class, Collections.emptyList())); @@ -299,41 +304,39 @@ protected String getRequestLogMessage(String exceptionName, HttpServletRequest r param = params.nextElement(); values = request.getParameterValues(param); - if (values != null) { - for (i = 0; i < values.length; i++) { - sb.append(LINE_SEPARATOR).append(param).append(": "); + String paramName = param; + if (paramName.endsWith("[]")) { + paramName = paramName.substring(0, paramName.length() - 2); + } - if (blackList.contains(param)) { - sb.append("***"); - } - else { - sb.append(values[i]); - } + sb.append(LINE_SEPARATOR).append(" - ").append(paramName).append(": "); + if (values != null && values.length > 0) { + if (blackList.contains(paramName)) { + sb.append("[FILTERED]"); + } + else { + sb.append(String.join(", ", values)); } } } + + sb.append(LINE_SEPARATOR); } } sb.append(LINE_SEPARATOR); - if (message != null) { - sb.append(message).append(". "); + if (this.stackFilterer.isShouldFilter()) { + sb.append("Filtered stacktrace:"); + } + else { + sb.append("Full stacktrace:"); } - sb.append("Stacktrace follows:"); return sb.toString(); } - protected void createStackFilterer() { - try { - Class filtererClass = this.grailsApplication.getConfig().getProperty(Settings.SETTING_LOGGING_STACKTRACE_FILTER_CLASS, - Class.class, DefaultStackTraceFilterer.class); - this.stackFilterer = BeanUtils.instantiateClass(filtererClass, StackTraceFilterer.class); - } - catch (Throwable t) { - logger.error("Problem instantiating StackTracePrinter class, using default: " + t.getMessage()); - this.stackFilterer = new DefaultStackTraceFilterer(); - } + public void setStackTraceFilterer(StackTraceFilterer stackTraceFilterer) { + this.stackFilterer = stackTraceFilterer; } }