# simple-prometheus
**Repository Path**: Goslee/simple-prometheus
## Basic Information
- **Project Name**: simple-prometheus
- **Description**: 解决方案:轻量级监控告警(开源项目)异常监听,飞书推送,钉钉推送,全局埋点,轻量化
- **Primary Language**: Java
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: https://gitee.com/Goslee/simple-prometheus
- **GVP Project**: No
## Statistics
- **Stars**: 11
- **Forks**: 2
- **Created**: 2024-09-05
- **Last Updated**: 2025-09-06
## Categories & Tags
**Categories**: Uncategorized
**Tags**: SpringBoot, 异常监听, 消息推送, 异常告警
## README
## simple-prometheus
#### 一个轻量级的异常监控(简单点,再简单点)
🍬Make it simple, make it simple.
-------------------------------------------------------------------------------
### 📚背景概述
针对小项目做的一个异常监控,主要是为了方便开发人员快速定位问题,减少沟通成本,提高开发效率。主要围绕着轻量化,快速上手,开箱即用的理念,让开发者或者关心此类问题的人,尽可能减少部分成本去搭建大规模的中间件,如果需要搭建很大规模的中间件和服务,会浪费掉时间和精力以及资金方面的成本,此工具就是让小型企业减少如上所述的成本问题而建立。
### 🧬系统需求
  
### 📐当前版本

### 🍊实现方式
#### 1、基于spring的aop方式实现监控,发送消息用event监听发布订阅
> 针对单机来说具有很好的性能,分布式部署建议将event替换为MQ
1. [x] 环绕切面实现拦截异常,新创建一个异常类型,主要用于异常推送监控
切面的拦截与日志的拦截规则一致,分别为【RequestMapping、GetMapping、PostMapping】,方式为环绕通知,不影响原功能使用增强型方式。
2. [x] 如果是系统异常未被指定(Exception)则发送至开发群
3. [x] 如果是指定监控异常(SPrometheusException)则发送至相关业务人员群
4. [x] 如果是非指定监控异常则无需处理,视为普通业务异常
5. [x] 如果不想抛异常,只想在指定位置发送一条消息,则使用对外提供的工具类即可
#### 2、代码报错位置查找设计
异常信息是此功能的核心部分,那么就会涉及到如何能准确的拿到我们需要的异常信息以及堆栈,其实除了异常信息描述外,堆栈信息才是找到问题的关键, 由于栈信息有很大部分我们是不需要关注的,我们只需要关注业务代码报错部分即可,再者其是先进后出,那么说明抛异常的最终位置在最外层,那么第一个筛选后的栈信息就是最终业务报错的位置
### 🐞异常通知的具体内容
服务名称、请求地址、接口描述、异常信息、异常追踪、错误码、异常定位、链路id、异常时间、接口耗时、请求IP、请求参数
### 👉快速上手
- 在需要使用的模块引入pom依赖
```txt
io.gitee.goslee
spring-boot-starter-simple-prometheus
1.0.1
```
- 如果启动失败尝试添加如下依赖(后续我会修复)
```txt
net.jodah
expiringmap
0.5.8
true
```
- application.yml中做如下的配置
飞书示例
```yaml
#监控
sprometheus:
default: "feishu"
feishu:
#是否开启异常告警
enabled: true
#需要包含的trace包路径
includedTracePackage:
- "com.gosling"
- "com.mysql"
- "java.sql.SQLException"
- "java.net"
#飞书群消息
business:
# 业务消息群1 飞书的 webhook
- url: "https://open.feishu.cn/open-apis/bot/v2/hook/xxx"
code: ",200000,200001,200002,200003,"
developer:
# 开发消息群飞书的 webhook
- url: "https://open.feishu.cn/open-apis/bot/v2/hook/xxx"
code: ",500,"
```
钉钉示例
```yaml
#监控
sprometheus:
default: "dingding"
dingding:
#是否开启异常告警
enabled: true
#需要包含的trace包路径
includedTracePackage:
- "com.gosling"
- "com.mysql"
- "java.sql.SQLException"
- "java.net"
#钉钉群消息
business:
# 业务消息群1 钉钉
- url: "https://oapi.dingtalk.com/robot/send?access_token=3c36"
code: ",200000,200001,200002,200003,"
secret: "SEC9a6b2202a0b92847fd7098630d20b4"
developer:
# 开发消息群钉钉的
- url: "https://oapi.dingtalk.com/robot/send?access_token=3c36"
code: ",500,"
secret: "SEC9a6b2202a0b92847fd7098630d20b4"
```
- 配置说明
| 名称 | 参数类型 | 说明 | 必须配置 |
|------------------------|---------|-----------------------------|------|
| default | string | 默认发送至那个配置下 | 是 |
| enabled | boolean | 是否开启异常告警 | 是 |
| included_trace_package | string | 需要包含的trace包路径 | 是 |
| url | string | 业务/开发消息群集合 飞书(或其他)的 webhook | 是 |
| code | string | 业务码 | 是 |
| secret | string | 钉钉的加签串 | 否-钉钉必填 |
### 🍺代码层面使用(多种方式)
#### 方式一:
```java
//在报错的位置编写此代码
SPrometheusUtil.insert("异常测试", SPrometheusTitleEnum.OTHER);
```
#### 方式二:
```java
//在报错的位置编写此代码
SPrometheusUtil.insert("异常测试", SPrometheusTitleEnum.OTHER, JSONObject.toJSONString(req));
```
#### 方式三:
```java
//在报错的位置编写此代码
Exp exp = new Exp();
exp.setMsg("test...已经收到监听事件");
exp.setUri("/admin/test1");
exp.setSendChannel(SendChannel.FEISHU);
exp.setAtAll(SConstants.YES);
exp.setReqData(JSONObject.toJSONString(req));
exp.setTitleEnum(SPrometheusTitleEnum.OTHER);
SPrometheusUtil.insert(exp);
```
#### 方式四:
```java
Exp exp = new Exp();
//...
exp.setMsg(originMsg);
//如果你想要保留你发送消息的原格式,不做任何更改
//例如你需要发送飞书的表格,针对飞书文档编写的msg消息或者你自己想要保留的格式,那么可以用此方式
SPrometheusUtil.insertOrg(exp);
```
> Exp实体类说明
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Exp {
@Msg(msg = "监控分类")
private SPrometheusTitleEnum titleEnum = SPrometheusTitleEnum.OTHER;
@Msg(msg = "接口地址")
private String uri;
@Msg(msg = "错误信息")
private String msg = SConstants.DEFAULT_MSG;
@Msg(msg = "请求参数")
private String reqData;
@Msg(msg = "是否@所有人 @all")
private Integer atAll = SConstants.NO;
@Msg(msg = "推送媒介 1:飞书(默认) 2:钉钉 3:邮件")
private String sendChannel = SendChannel.FEISHU;
}
```
#### 方式五:手动抛出异常 SPrometheusException
```java
throw new SPrometheusException(SPrometheusTitleEnum.OTHER, "用户信息为空");
```
#### 方式六:异常监听注解 @SPrometheus
```java
@SPrometheus(title = SPrometheusTitleEnum.OTHER, uri = "admin/test1", scene = Scene.BUSINESS)
public void test(String id) {
if (StrUtil.isBlank(id)){
System.out.println("主键ID为空");
throw new NullPointerException("主键ID为空");
}
}
```
#### 可预知的异常
如:参数校验异常,业务流程中提前被定义的异常等
这种异常的等级并不是特别高,有些需要业务人员参与关注,而大多数情况下,业务人员不需要关注、开发人员更不需要去关注
所以这种异常需要控制通知的频率以及通知的范围,避免群消息过于繁多,导致在关键时候错过想要关注的重要消息
#### 无法预估的异常
开发的未知异常则无需做指定位置和提前定义,如:空指针异常,类型转换错误,sql执行异常,超时等,如果出现这种情况,那么这种异常需要及时处理,异常的等级也是最高的P0级别这种异常将会直接发送至开发群
### 📦实际效果
> 当系统出现了一个业务异常后,那么就会有如下通知
创建一个异常测试
```java
@PostMapping("/test/test")
@Operation(summary = "APP[风控系统回调发起支付]")
public void prepayment(@Valid @RequestBody LoanOrderQueryVO vo) {
if (1 == 1) {
throw new PrometheusException("空指针");
}
}
```
飞书收到群消息
```textmate
服务名称:gosling-server
请求地址:/app-api/test/test
接口描述:其它|系统异常
异常信息:请求参数缺失:id
异常追踪:com.gosling.framework.web.core.handler.GlobalExceptionHandler.missingServletRequestParameterExceptionHandler(GlobalExceptionHandler.java:110)
错误码:100000
异常定位:com.gosling.framework.web.core.handler.GlobalExceptionHandler.missingServletRequestParameterExceptionHandler(GlobalExceptionHandler.java:110)
链路id:94bb8b920be3401e9e0474224c30939e
异常时间:2024-09-06 11:05:15
接口耗时:1.0091 秒
请求IP:192.168.1.1
active:test
请求参数:{"id":""}
```
### 🚽全局异常处使用(可选)
如果想要通知更全面,可以选择
由于全局异常拦截时,类似于参数缺失,请求方式不正确等异常,将不会进入到切面监听中,则需要手动设置异常监控(后续会纳入自动监控中)
```java
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public BaseResult> missingServletRequestParameterExceptionHandler(HttpServletRequest request, MissingServletRequestParameterException ex) {
log.error("全局异常处理:参数传参异常", ex);
String message = String.format("请求参数缺失:%s", ex.getParameterName());
Exp exp = new Exp();
exp.setUri(request.getRequestURI());
exp.setMsg(message);
exp.setReqData(getParams(request));
exp.setTitleEnum(PrometheusTitleEnum.OTHER);
PrometheusUtil.insert(repayReq);
return BaseResult.error(message);
}
```
### 🍐框架结构

### 📖后续 TODO pmo
- 完善email通知
- 完善钉钉通知--已完成
- 完善QQ通知
- 完善微信通知
- 完善NACOS监听
- 完善通知到个人
### ⭐Star simple-prometheus⭐
### 🐞提供bug反馈或建议
1、在gitee上提交issues
2、扫描下方二维码,关注公众号,发送消息,我会第一时间回复

谢谢您宝贵的star O(∩_∩)O~~