本文的初衷是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 发送到 钉钉机器人 的补充。
评论