本文的初衷是java后端崩溃后,如何把入参传给 Sentry,从而更快速的定位和解决问题。入参包括:query string、form-data、body,还有 url、headers,当然考虑到分布式部署,还应该拿到当前的服务器 ip 以及端口,至于用户信息,header 里面一般包含了。
先上张效果图:
如果看不清,请点击图片查看原图,谢谢。
这张图里面有body,headers,localAddr、localPort、method、params、remoteHost、url等,可以说足够我们定位那个接口错了(url),用户的入参(params是query string + form data的集合,body 是 post 请求的请求体),当时的服务器 localAddr 以及 运行端口 localPort,另外还有 headers, headers 一般会包含用户的信息,比如 token,如果使用了 网关,那么 headers 可能包含更多信息,比如 用户 id、用户 手机号、等等,具体跟网关有关啦。remoteHost 可能用不着,headers 里面包含了足够多的信息了。
好了,具体相关的 异常上报类如下:
import io.sentry.Sentry; import io.sentry.event.Event; import io.sentry.event.EventBuilder; import io.sentry.event.User; import io.sentry.event.interfaces.ExceptionInterface; import tv.drplay.util.StringUtils; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; public class Crash { public static void onCrash(Exception e, HttpServletRequest request) { String url = request.getRequestURI(); EventBuilder eventBuilder = new EventBuilder(); eventBuilder.withExtra("url", url); eventBuilder.withExtra("method", request.getMethod()); addParams(request, eventBuilder); addHeaders(request, eventBuilder); addUser(request, eventBuilder); eventBuilder.withExtra("localPort", request.getLocalPort()); eventBuilder.withExtra("localAddr", request.getLocalAddr()); eventBuilder.withMessage(e.getMessage()).withLevel(Event.Level.ERROR).withSentryInterface(new ExceptionInterface(e)); Sentry.capture(eventBuilder); } private static void addParams(HttpServletRequest request, EventBuilder eventBuilder) { Map<String, String[]> map = request.getParameterMap(); if (map != null && !map.isEmpty()) { eventBuilder.withExtra("params", map); } try { String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator())); if (!StringUtils.isBlank(body)) { eventBuilder.withExtra("body", body); } } catch (Exception ignore) { } } private static void addHeaders(HttpServletRequest request, EventBuilder eventBuilder) { Map<String, String> headers = getHeadersInfo(request); if (!headers.isEmpty()) { eventBuilder.withExtra("headers", headers); } } private static Map<String, String> getHeadersInfo(HttpServletRequest request) { Map<String, String> map = new HashMap<>(); try { Enumeration headerNames = request.getHeaderNames(); if (headerNames == null) return map; while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); map.put(key, value); } return map; } catch (Exception ignore) { } return map; } private static void addUser(HttpServletRequest request, EventBuilder eventBuilder) { String host = request.getRemoteHost(); if (!StringUtils.isBlank(host)) { // User user = new User(null, null, host, null); // Sentry.getContext().setUser(user); eventBuilder.withExtra("remoteHost", host); } } }
使用的类可能如下:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(CustomException.class) @ResponseBody Response handleCustomException(CustomException customException) { customException.printStackTrace(); return customException.getError(); } }
本文本质上是对 自建 sentry 并与 spring boot 集成 和 spring boot 全局异常拦截 并通过 webhook 发送到 钉钉机器人 的补充。