Sentry
- 基本介绍
- 简单使用
- Java项目应用
- 代码侵入(不建议,耦合性大)
- 全局拦截器捕获(建议)
- Lockback.xml 配置(建议)
基本介绍
Sentry 是一个开源的实时错误报告工具,支持 web 前后端、移动应用以及游戏,支持 Python、OC、Java、Go、Node、Django、RoR 等主流编程语言和框架 ,还提供了 GitHub、Slack、Trello 等常见开发工具的集成。
Senty是专门用来干异常日志监控的,它的核心就是围绕异常日志来建模和设计的,它有很多的异常日志监控特性,包括智能错误分析,归类汇总,自动分配告警到相关团队等等,这些虽然理论上ELK也能实现,但是实现成本比较高。
Sentry是一个应用监控系统,可以用于前后端各种技术栈的线上监控和错误分析。
简单使用
首先打开Sentry 的官网sentry官网,并且进行一系列的注册,创建一个组织,这里我创建一个组织,命名叫做ah
创建项目
获取dsn
Java项目应用
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry</artifactId>
<version>5.7.3</version>
</dependency>
代码侵入(不建议,耦合性大)
配置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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> 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<T> 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 <T> ResultModel<T> error(ErrorCode errorCode){
return new ResultModel<>(errorCode);
}
public static <T> ResultModel<T> error(long errorCode,String msg){
return new ResultModel<>(errorCode,msg);
}
public static <T> ResultModel<T> error(long errorCode, String msg, T data){
return new ResultModel<>(errorCode,data,msg);
}
public static <T> ResultModel<T> fail(){
return new ResultModel<>(ErrorCode.FAIL);
}
public static <T> ResultModel<T> succ(){
return new ResultModel<>(ErrorCode.SUCCESS);
}
public static <T> ResultModel<T> 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!";
}
Sentry显示如下
Lockback.xml 配置(建议)
xml配置
<configuration>
<!-- Configure the Console appender -->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Configure the Sentry appender, overriding the logging threshold to the WARN level -->
<appender name="Sentry" class="io.sentry.logback.SentryAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
</appender>
<!-- Enable the Console and Sentry appenders, Console is provided as an example
of a non-Sentry logger that is set to a different logging threshold -->
<root level="INFO">
<appender-ref ref="Console" />
<appender-ref ref="Sentry" />
</root>
</configuration>
代码使用
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配合使用,效果更好