Sentry 是一个开源的实时错误报告工具,支持 web 前后端、移动应用以及游戏,支持 Python、OC、Java、Go、Node、Django、RoR 等主流编程语言和框架 ,还提供了 GitHub、Slack、Trello 等常见开发工具的集成。
Senty是专门用来干异常日志监控的,它的核心就是围绕异常日志来建模和设计的,它有很多的异常日志监控特性,包括智能错误分析,归类汇总,自动分配告警到相关团队等等,这些虽然理论上ELK也能实现,但是实现成本比较高。
Sentry是一个应用监控系统,可以用于前后端各种技术栈的线上监控和错误分析。
首先打开Sentry 的官网sentry官网,并且进行一系列的注册,创建一个组织,这里我创建一个组织,命名叫做ah
io.sentry
sentry
5.7.3
配置Sentry
@SpringBootApplication
public class ThriftRpcApplication {
public static void main(String[] args) {
SpringApplication.run(ThriftRpcApplication.class, args);
Sentry.init(options -> {
options.setDsn("https://90cd056919fxxxxxxxxxxxxxxxxx954c6186db4@o1207430.ingest.sentry.io/6340908");
});
}
}
@RestController
@Api(value = "测试", tags = "测试")
@RequestMapping("/tt")
public class Controller {
/**
* 方法会发生500错误
*
* @return
*/
@GetMapping("/helloworld")
@ApiOperation("ceshi")
public String helloworld() {
try {
int x = 1 / 0;
} catch (Exception e) {
Sentry.captureException(e);
}
return "Hello World!";
}
@GetMapping("/test")
@ApiOperation("tt")
public String test() {
try {
System.out.println("测试代码");
throw new Exception("测试错误。。。。");
} catch (Exception e) {
Sentry.captureException(e);
}
return "Hello World!";
}
}
全局异常拦截类
@ControllerAdvice
@RestController
public class GlobalExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(BusinessException.class)
@ResponseBody
public ResultModel businessExceptionHandler(BusinessException e, ServletRequest request) {
Sentry.captureException(e);
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
logger.error("error in \nurl :{} \ncode:{} \nmsg:{} \nparams:{}\n body:{}", ((ContentCachingRequestWrapper) request).getRequestURI(), e.getErrorCode(), e.getMsg(), JSON.toJSONString(request.getParameterMap()), StringUtils.toEncodedString(wrapper.getContentAsByteArray(), Charset.forName(wrapper.getCharacterEncoding())));
return ResultModel.error(e.getErrorCode(), e.getMsg());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ResultModel methodArgumentNotValidHandler(MethodArgumentNotValidException e, ServletRequest request) {
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
logger.error("error in \nurl :{} \nmsg:{} \nparams:{}\n body:{}", ((ContentCachingRequestWrapper) request).getRequestURI(), message, JSON.toJSONString(request.getParameterMap()), StringUtils.toEncodedString(wrapper.getContentAsByteArray(), Charset.forName(wrapper.getCharacterEncoding())));
Sentry.captureException(e);
return ResultModel.error(ErrorCode.FAIL.getCode(), message);
}
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseBody
public ResultModel methodArgumentNotValidHandler(MissingServletRequestParameterException e, ServletRequest request) {
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
String message = e.getMessage();
logger.error("error in \nurl :{} \nmsg:{} \nparams:{}\n body:{}", ((ContentCachingRequestWrapper) request).getRequestURI(), message, JSON.toJSONString(request.getParameterMap()), StringUtils.toEncodedString(wrapper.getContentAsByteArray(), Charset.forName(wrapper.getCharacterEncoding())));
Sentry.captureException(e);
return ResultModel.error(ErrorCode.FAIL.getCode(), "必填参数为空");
}
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public ResultModel illegalArgumentHandler(IllegalArgumentException e, ServletRequest request) {
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
logger.error("error in \nurl :{} \nmsg:{} \nparams:{}\n body:{}", ((ContentCachingRequestWrapper) request).getRequestURI(), e.getMessage(), JSON.toJSONString(request.getParameterMap()), StringUtils.toEncodedString(wrapper.getContentAsByteArray(), Charset.forName(wrapper.getCharacterEncoding())));
Sentry.captureException(e);
return ResultModel.error(ErrorCode.FAIL.getCode(), e.getMessage()
);
}
@ExceptionHandler(Exception.class)
@ResponseBody
public ResultModel exceptionHandler(Exception e, ServletRequest request) {
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
logger.error("error in \nurl :{} \nparams:{}\nbody:{}", ((ContentCachingRequestWrapper) request).getRequestURI(), JSON.toJSONString(request.getParameterMap()), StringUtils.toEncodedString(wrapper.getContentAsByteArray(), Charset.forName(wrapper.getCharacterEncoding())));
logger.error("程序运行出现异常!", e);
Sentry.captureException(e);
return ResultModel.error(ErrorCode.UNDEFINED);
}
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseBody
public ResultModel exceptionHandler(HttpMessageNotReadableException e, ServletRequest request) {
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
logger.error("error in \nurl :{} \nparams:{}\nbody:{}", ((ContentCachingRequestWrapper) request).getRequestURI(), JSON.toJSONString(request.getParameterMap()), StringUtils.toEncodedString(wrapper.getContentAsByteArray(), Charset.forName(wrapper.getCharacterEncoding())));
logger.error("程序运行出现异常!", e);
Sentry.captureException(e);
return ResultModel.error(ErrorCode.PARAM_ERROR.getCode(), "json格式错误:" + e.getLocalizedMessage());
}
}
错误码接口
public interface IErrorCode {
long getCode();
String getMessage();
}
错误码类
public enum ErrorCode implements IErrorCode {
/**
* 成功
*/
SUCCESS(0L, "成功"),
/**
* 失败
*/
FAIL(1L, "失败"),
/**
* 参数异常
*/
PARAM_ERROR(2L, "参数异常"),
/**
* 服务连接异常
*/
CONNECTION_ERROR(3L, "服务连接异常"),
/**
* 未明确定义名称异常
*/
UNDEFINED(10001, "未明确定义名称异常"),
;
private long code;
private String message;
ErrorCode(long code, String message) {
this.code = code;
this.message = message;
}
@Override
public long getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
返回对象封装
@Data
public class ResultModel implements Serializable {
/**
* 返回错误码
*/
@ApiModelProperty(value="返回错误码数")
private long code = ErrorCode.SUCCESS.getCode();
/**
* 返回错误信息
*/
@ApiModelProperty(value="返回错误信息")
private String message = ErrorCode.SUCCESS.getMessage();
/**
* 数据
*/
@ApiModelProperty(value="数据")
private T data;
@ApiModelProperty(value="时间戳")
private long timestamp = System.currentTimeMillis();
/**是否加密**/
@ApiModelProperty(value="是否加密")
private boolean encryption = false;
/**加密数据**/
@ApiModelProperty(value="加密数据")
private String ciphertext;
public ResultModel(long code, T data, String message) {
super();
this.code = code;
this.message = message;
this.data = data;
}
public ResultModel(long code, String message) {
super();
this.code = code;
this.message = message;
}
public ResultModel(ErrorCode errorCode) {
super();
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
public ResultModel() {
}
public ResultModel(T data) {
this.data = data;
}
public static ResultModel error(ErrorCode errorCode){
return new ResultModel<>(errorCode);
}
public static ResultModel error(long errorCode,String msg){
return new ResultModel<>(errorCode,msg);
}
public static ResultModel error(long errorCode, String msg, T data){
return new ResultModel<>(errorCode,data,msg);
}
public static ResultModel fail(){
return new ResultModel<>(ErrorCode.FAIL);
}
public static ResultModel succ(){
return new ResultModel<>(ErrorCode.SUCCESS);
}
public static ResultModel succ(T data){
return new ResultModel<>(data);
}
}
业务异常类
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class BusinessException extends RuntimeException {
/** 异常码 */
private Long errorCode = ErrorCode.UNDEFINED.getCode();
/** 对用户友好的错误信息 */
private String msg;
public BusinessException(Long errorCode, String message) {
super(message);
this.errorCode = errorCode;
this.msg = message;
}
public BusinessException(String message) {
super(message);
this.msg = message;
}
public BusinessException(IErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode.getCode();
this.msg = errorCode.getMessage();
}
}
项目使用
/**
* 方法会发生500错误
*
* @return
*/
@GetMapping("/helloworld")
@ApiOperation("ceshi")
public String helloworld() {
try {
int x = 1 / 0;
} catch (Exception e) {
throw new BusinessException(e.getMessage());
}
return "Hello World!";
}
xml配置
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
WARN
代码使用
private final Logger LOGGER = LoggerFactory.getLogger(getClass());
@GetMapping("/log")
@ApiOperation("logback")
public String log() {
System.out.println("测试日志级别");
if (true) {
LOGGER.error("测试erro日志....你有个错误哦");
}
return "Hello World!";
}
小结
建议 全局捕获异常和lockback配合使用,效果更好