Enable logging of resolved exceptions

Previously, if an exception was resolved by a `HandlerExceptionResolver`
nothing the log indicated a failure to process the query.

This commit adds a new property `spring.mvc.log-resolved-exception` that
enables warning logs for supported `HandlerExceptionResolver` instances.

When Devtools is enabled, this flag is enabled by default.

Closes gh-2176
This commit is contained in:
Stephane Nicoll 2016-05-24 13:34:22 +02:00
parent 1175879fcc
commit 5656e83ba9
5 changed files with 73 additions and 1 deletions

View File

@ -68,6 +68,7 @@ import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.filter.HttpPutFormContentFilter;
import org.springframework.web.filter.RequestContextFilter;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
@ -81,6 +82,7 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
@ -415,6 +417,25 @@ public class WebMvcAutoConfiguration {
}
return super.createExceptionHandlerExceptionResolver();
}
@Override
protected void configureHandlerExceptionResolvers(
List<HandlerExceptionResolver> exceptionResolvers) {
super.configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
if (this.mvcProperties.isLogResolvedException()) {
for (HandlerExceptionResolver resolver : exceptionResolvers) {
if (resolver instanceof AbstractHandlerExceptionResolver) {
((AbstractHandlerExceptionResolver) resolver)
.setWarnLogCategory(resolver.getClass()
.getName());
}
}
}
}
}
@Configuration

View File

@ -78,6 +78,11 @@ public class WebMvcProperties {
*/
private boolean throwExceptionIfNoHandlerFound = false;
/**
* Enable warn logging of exceptions resolved by a "HandlerExceptionResolver".
*/
private boolean logResolvedException = false;
/**
* Maps file extensions to media types for content negotiation, e.g. yml->text/yaml.
*/
@ -144,6 +149,14 @@ public class WebMvcProperties {
this.throwExceptionIfNoHandlerFound = throwExceptionIfNoHandlerFound;
}
public boolean isLogResolvedException() {
return this.logResolvedException;
}
public void setLogResolvedException(boolean logResolvedException) {
this.logResolvedException = logResolvedException;
}
public Map<String, MediaType> getMediaTypes() {
return this.mediaTypes;
}

View File

@ -28,12 +28,14 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.assertj.core.api.Condition;
import org.joda.time.DateTime;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter;
@ -58,15 +60,18 @@ import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.filter.HttpPutFormContentFilter;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.resource.AppCacheManifestTransformer;
@ -523,6 +528,37 @@ public class WebMvcAutoConfigurationTests {
.isNotInstanceOf(MyRequestMappingHandlerAdapter.class);
}
@Test
public void defaultLogResolvedException() {
load();
testLogResolvedExceptionCustomization(false);
}
@Test
public void customLogResolvedException() {
load("spring.mvc.log-resolved-exception:true");
testLogResolvedExceptionCustomization(true);
}
private void testLogResolvedExceptionCustomization(final boolean expected) {
HandlerExceptionResolver exceptionResolver = this.context.getBean(
HandlerExceptionResolver.class);
assertThat(exceptionResolver).isInstanceOf(HandlerExceptionResolverComposite.class);
List<HandlerExceptionResolver> delegates =
((HandlerExceptionResolverComposite) exceptionResolver).getExceptionResolvers();
for (HandlerExceptionResolver delegate : delegates) {
if (delegate instanceof AbstractHandlerMethodAdapter) {
assertThat(new DirectFieldAccessor(delegate).getPropertyValue("warnLogger"))
.is(new Condition<Object>() {
@Override
public boolean matches(Object value) {
return (expected ? value != null : value == null);
}
});
}
}
}
private void load(Class<?> config, String... environment) {
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, environment);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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.
@ -52,6 +52,7 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
properties.put("spring.h2.console.enabled", "true");
properties.put("spring.resources.cache-period", "0");
properties.put("spring.template.provider.cache", "false");
properties.put("spring.mvc.log-resolved-exception", "true");
PROPERTIES = Collections.unmodifiableMap(properties);
}

View File

@ -338,6 +338,7 @@ content into your application; rather pick only the properties that you need.
spring.mvc.ignore-default-model-on-redirect=true # If the content of the "default" model should be ignored during redirect scenarios.
spring.mvc.locale= # Locale to use. By default, this locale is overridden by the "Accept-Language" header.
spring.mvc.locale-resolver=accept-header # Define how the locale should be resolved.
spring.mvc.log-resolved-exception=false # Enable warn logging of exceptions resolved by a "HandlerExceptionResolver".
spring.mvc.media-types.*= # Maps file extensions to media types for content negotiation.
spring.mvc.message-codes-resolver-format= # Formatting strategy for message codes. For instance `PREFIX_ERROR_CODE`.
spring.mvc.servlet.load-on-startup=-1 # Load on startup priority of the Spring Web Services servlet.