No message available问题解决
概述
在EFK日志查询平台断断续续看到若干个应用的报错信息:
排查
上述截图里报错的类(省略掉Import语句后):
@Slf4j
@RestController
public class FilterErrorController extends BasicErrorController {public FilterErrorController() {super(new DefaultErrorAttributes(), new ErrorProperties());}@Override@RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));HttpStatus status = getStatus(request);// 第35行log.error(body.get("message").toString());Map<String, Object> responseBody = new HashMap<>(16);responseBody.put("code","9000");responseBody.put("message","服务器开小差了");return new ResponseEntity<>(responseBody, status);}@Overridepublic String getErrorPath() {return "error/error";}
}
自定义的FilterErrorController继承BasicErrorController,而BasicErrorController则继承AbstractErrorController,继续跟进源码:
public class BasicErrorController extends AbstractErrorController {
}
找到AbstractErrorController.getErrorAttributes()
方法:
public abstract class AbstractErrorController implements ErrorController {protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {WebRequest webRequest = new ServletWebRequest(request);return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);}
}
进一步定位到DefaultErrorAttributes.addErrorDetails()
@Order(Integer.MIN_VALUE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {Map<String, Object> errorAttributes = new LinkedHashMap();errorAttributes.put("timestamp", new Date());this.addStatus(errorAttributes, webRequest);this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);this.addPath(errorAttributes, webRequest);return errorAttributes;}private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace) {Throwable error = this.getError(webRequest);if (error != null) {while(true) {if (!(error instanceof ServletException) || error.getCause() == null) {if (this.includeException) {errorAttributes.put("exception", error.getClass().getName());} this.addErrorMessage(errorAttributes, error);if (includeStackTrace) {this.addStackTrace(errorAttributes, error);}break;}error = ((ServletException)error).getCause();}}Object message = this.getAttribute(webRequest, "javax.servlet.error.message");if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null) && !(error instanceof BindingResult)) {errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message);}}
}
源码是定位到。但是为啥呢?排查无果,搁置一阵子。
后续
后来在本地使用postman调试另一个服务的接口 http://localhost:8099/api/cbs/area/getProvinceList,这个地址URL是从我们一个内部后台管理系统(基于开源管理平台【若依】改造)复制而来,也发生相同的报错:
控制台打印信息如下:aba-cbs-provider | [http-nio-8099-exec-4] | | ERROR | com.aba.web.controller.FilterErrorController | error | 35 | - No message available
既然本地可以复现,那就好办。
看了下,项目工程里controller层代码的URL并没有前缀/api
,为啥在后台管理系统里要加上/api
,推测下来是统一化与规范化处理。
postman请求 http://localhost:8099/api/cbs/area/getProvinceList,正常返回。
gateway请求转发?突然想到之前在Apollo看到的几个gateway相关的配置:
spring.cloud.gateway.routes[24].id = aba-cbs-provider
spring.cloud.gateway.routes[24].uri = lb://aba-cbs-provider
spring.cloud.gateway.routes[24].predicates[0] = Path=/api/cbs/
spring.cloud.gateway.routes[24].filters[0] = StripPrefix=1
其中有些服务有spring.cloud.gateway.routes[24].filters[0] = StripPrefix=1
这个配置项,而有些服务则无。
cbs
应用添加spring.cloud.gateway.routes[24].filters[0] = StripPrefix=1
配置后,再次通过postman请求接口 http://localhost:8099/api/cbs/area/getProvinceList,报错消失!
理论知识
StripPrefix这个Spring Cloud Gateway配置项作用是啥。搜索官方文档,StripPrefix参数表示在将请求发送到下游之前从请求中剥离的路径个数。
spring:cloud:gateway:routes:- id: aba-cbs-provideruri: http://cbspredicates:# - Path=/cbs/filters:- StripPrefix=1
当通过网关服务向cbs服务发起api/cbs/area/getProvinceList
发出请求时,转发到cbs服务的请求变成cbs/area/getProvinceList
StripPrefix=2
时,则会从路径URLapi/cbs/area/getProvinceList
里去掉2个前缀层级,即得到area/getProvinceList
。