Compare commits

...
86 Commits
Author SHA1 Message Date
Logic 7dcb4c3566 test(alerter): add mock for AlertSseManager in AlertNoticeDispatchTest
- Introduce mock object for AlertSseManager
- Inject the mock into AlertNoticeDispatch instance
- This change prepares for testing server-sent events (SSE) related functionality
2025-02-05 19:37:26 +08:00
Logic 206f256e96 test(alerter): add mock for AlertSseManager in AlertNoticeDispatchTest
- Introduce mock object for AlertSseManager
- Inject the mock into AlertNoticeDispatch instance
- This change prepares for testing server-sent events (SSE) related functionality
2025-02-05 18:40:13 +08:00
Logic 1c7ea61b59 Merge remote-tracking branch 'origin/alert-sse' into alert-sse 2025-02-05 17:36:20 +08:00
Logic 5932645422 refactor(alerter): add class comments for AlertSseController and AlertSseManager
- Added class comment for AlertSseController: "SSE controller for alert"
- Added class comment for AlertSseManager: "SSE manager for alert"
- Improved code readability and maintainability
2025-02-05 17:35:26 +08:00
Logic 6b7b9e0476 Merge branch 'master' into alert-sse 2025-02-05 17:31:29 +08:00
Logicandgithub-actions[bot] 9e482e985d Update hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertSseController.java
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Logic <zqr10159@126.com>
2025-02-05 17:25:42 +08:00
Logicandgithub-actions[bot] b49353a567 Update hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/config/AlertSseManager.java
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Logic <zqr10159@126.com>
2025-02-05 17:25:32 +08:00
Logic 2111280283 feat(alert): implement new alert animation and filtering
- Add slide-in animation for new alerts
- Implement alert filtering based on status and content
- Update alert list handling in component
- Modify alert store and dispatch logic
- Format alert timestamps in JSON response
2025-02-05 17:20:16 +08:00
yunfan24andtomsun28 f1b1e3f7d6 [improve] Search ignores case sensitivity. (#3042)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-02-05 15:56:31 +08:00
yunfan24 921b8e5713 [update] upgrade actions upload-artifact to v4 (#3046) 2025-02-05 11:30:42 +08:00
Logic bb729dd33e feat(alerter): add logging before broadcasting alert
- Add log statement in AlertNoticeDispatch before broadcasting alert to SSE client
- Improve traceability and debugging for alert broadcasting process
2025-02-04 23:01:54 +08:00
Logic 95ee0d272f feat(alert): implement real-time alert updates using Server-Sent Events (SSE)
- Add SSE subscription to receive real-time alerts
- Update alert list with new data from SSE
- Handle SSE errors and close connection on component destruction- Remove refresh button as real-time updates eliminate the need for manual refreshing
2025-02-04 17:28:13 +08:00
Logic 5f1bf5ea95 feat(alerter): implement server-sent events (SSE) for real-time alert notifications
- Add AlertSseManager to handle SSE emitters and broadcast alerts
- Create AlertSseController to manage SSE connections
- Update AlertNoticeDispatch to send alerts via SSE
- Modify NotifyComponent to use SSE for real-time updates
- Update Manager to enable async support
2025-02-03 22:04:24 +08:00
yunfan24andtomsun28 4009525f81 [improve] Replaced hardcoded text with internationalized string. (#3035)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-30 22:01:32 +08:00
yunfan24 e0c7b4e111 [bugfix] Fixed the bug in the threshold rules search box. (#3034) 2025-01-30 21:43:51 +08:00
yunfan24andaias00 5e01482caa [feature] Add pagination and name-based search functionality in notification module (#2948)
Co-authored-by: aias00 <rokkki@163.com>
2025-01-27 14:12:55 +08:00
yunfan24 028e2fbcbe [bugfix] fix and enable some unit tests. (#3031) 2025-01-27 12:58:14 +08:00
tomsun28 d00cc6dcf4 [feature] periodic alert threshold (#3024) 2025-01-26 18:37:57 +08:00
4b7fdd6985 OnlineParserTest doesn't test anything (#3010)
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: leo <1552443053@qq.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-26 16:13:21 +08:00
tomsun28andJast e45ec431aa [doc] update new contributor wall (#3025)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: Jast <shenghang@apache.org>
2025-01-26 15:50:05 +08:00
Jastandtomsun28 8fa52fbca1 [Feature] Add API e2e code (#3029)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-26 11:23:52 +08:00
Jastandtomsun28 132fd00ab1 [Improve] update english doc (#3028)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-26 11:12:43 +08:00
12ba2faa19 [MINOR UPDATE] Use Encode to string when possible (Base64) (#3016)
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-24 18:44:14 +08:00
PJ Fanningandaias00 2f3abfbad4 [issue-2998] remove invalid check in isValidLabelValue (#3015)
Co-authored-by: aias00 <rokkki@163.com>
2025-01-24 18:38:43 +08:00
myangle1120andaias00 95739d9114 [bugfix] Modify mask issue (#3018)
Co-authored-by: aias00 <rokkki@163.com>
2025-01-24 09:27:43 +08:00
Logicandtomsun28 428ae1f7d5 [bugfix]Fix page not found (#3014)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-23 19:49:02 +08:00
PJ Fanningandaias00 6509b8bfc6 [MINOR UPDATE] always specify the char encoding in getBytes (#3011)
Co-authored-by: aias00 <rokkki@163.com>
2025-01-23 18:40:59 +08:00
yunfan24andtomsun28 783bd8de89 [improve] Freeze the 'Operate' column on the right side of the list. (#3009)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-23 15:52:10 +08:00
Logicandtomsun28 d1115b44d1 [feature](alert) implement drag-and-drop functionality for alert templates (#3005)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-22 23:43:50 +08:00
NikhilMurugesanandaias00 68e37d613a [bugfix] fix some unit tests that failed to run (#3007)
Co-authored-by: aias00 <rokkki@163.com>
2025-01-22 23:41:08 +08:00
jonasHanhan 7db0df11ba [feature] Add alerter_zh_TW.properties configuration to adapt to mult… (#3004) 2025-01-21 16:30:48 +08:00
wanhaoandLogic e244bdc5a7 [feature] Complete multiple languages (#3002)
Co-authored-by: Logic <zqr10159@dromara.org>
2025-01-21 09:13:21 +08:00
tomsun28andLogic 0b7aa487f0 [webapp] bugfix edit monitor http query params error (#3001)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: Logic <zqr10159@dromara.org>
2025-01-20 13:09:27 +08:00
PJ FanningandLogic 59d8764dfb [MINOR UPDATE] refactor base64 code to simplify the conversions (#2999)
Co-authored-by: Logic <zqr10159@dromara.org>
2025-01-20 10:13:52 +08:00
yunfan24 8a38a34965 [feature] Support monitoring for StarRocks FE and StarRocks BE. (#2997) 2025-01-20 10:03:18 +08:00
tomsun28 fd7e1b0e05 [bugfix] fix wrong http user-agent content (#2996) 2025-01-20 01:16:50 +08:00
PJ Fanning 5441838888 [MINOR UPDATE] fix more instances of unclosed Http Responses (#2995) 2025-01-19 22:45:28 +08:00
PJ FanningandLogic 56b1dcb502 [MINOR UPDATE] close http response in PrometheusAutoCollectImpl (#2994)
Signed-off-by: PJ Fanning <pjfanning@users.noreply.github.com>
Co-authored-by: Logic <zqr10159@dromara.org>
2025-01-19 17:56:16 +08:00
PJ Fanningandtomsun28 efbaf7feac [MINOR UPDATE] close HttpResponse in HttpCollectImpl (#2993)
Signed-off-by: PJ Fanning <pjfanning@users.noreply.github.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-19 17:48:21 +08:00
Logic 7003b8a150 [Improve]Add Copy token button (#2992) 2025-01-19 14:54:01 +08:00
wanhaoandaias00 aed524a88b [feature] Added multilingual defaults for forms (#2991)
Co-authored-by: aias00 <rokkki@163.com>
2025-01-18 20:54:57 +08:00
aias00 9b20ab8434 [type:fix] remove matomo ip (#2990) 2025-01-17 23:15:44 +08:00
5c39b98eaa [feature] Add privateKey passphrase config for linux monitor (#2982)
Co-authored-by: yinyijun <yinyijun6@mgtv.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: aias00 <rokkki@163.com>
2025-01-17 22:05:14 +08:00
aias00 43ac859939 [type:improve] fix dependencies vulnerabilites (#2989) 2025-01-17 20:31:57 +08:00
helei1030andtomsun28 b5c2eca819 [docs]added spark Chinese documents and changed the original spar… (#2987)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-17 10:41:19 +08:00
PJ Fanning cd57f51892 [MINOR UPDATE] improve xml parsing code (#2988)
Signed-off-by: PJ Fanning <pjfanning@users.noreply.github.com>
2025-01-17 09:17:57 +08:00
tomsun28 a33966de9f [improve] update i18n json stru and update search ui (#2986) 2025-01-16 20:46:32 +08:00
4ff9452926 [bugfix] fix hbase dashboard display anomalies and turn on HTTPS (#2980)
Co-authored-by: zhuoyuchen <>
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: Jast <shenghang@apache.org>
2025-01-15 18:20:21 +08:00
Jast 67da9bdbba [Feature] Support copy monitoring (#2981) 2025-01-15 17:03:53 +08:00
tomsun28 8bb4493b43 [bugfix] fix alert integration extern source bug (#2979) 2025-01-14 18:22:21 +08:00
ghyandshown 92edd53f8c [webapp] key-value-input component hover effect fixed (#2972)
Signed-off-by: ghy <ghyghoo8@qq.com>
Co-authored-by: shown <yuluo08290126@gmail.com>
2025-01-13 15:44:14 +08:00
d70ad75dcc [bugfix] Fix NullPointerException (#2849)
Signed-off-by: Ayu <127600988+ayu-v0@users.noreply.github.com>
Signed-off-by: Zhang Yuxuan <1373529784@qq.com>
Co-authored-by: Ayu <14359932+lin-Ayu0v0@user.noreply.gitee.com>
Co-authored-by: shown <yuluo08290126@gmail.com>
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: Zhang Yuxuan <1373529784@qq.com>
2025-01-12 22:52:37 +08:00
tomsun28andLogic 2e76c12fe1 [feature] alert integration extern source (#2978)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: Logic <zqr10159@dromara.org>
2025-01-12 16:13:52 +08:00
Nick Guoandtomsun28 7e158eb778 [Improve] Add more test cases for Kafka junit tests (#2976)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-11 17:05:04 +08:00
Logicandtomsun28 db23c94882 [bugfix](warehouse) add metrics data update logic in memory storage (#2973)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-10 20:10:01 +08:00
yunfan24andaias00 e905f36de2 [bugfix] Fix the search functionality issue. (#2970)
Co-authored-by: aias00 <rokkki@163.com>
2025-01-10 19:36:22 +08:00
aias00andshown 037957dbe6 [feature] replace googletagmanager to matomo (#2877)
Co-authored-by: shown <yuluo08290126@gmail.com>
2025-01-09 16:28:17 +08:00
liutianyouandtomsun28 01d18f7935 [Improve] add no popup option after next login (#2969)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-08 23:47:47 +08:00
88e350de1c [BUG] "Advanced Settings" is all white in dark mode (#2965)
Co-authored-by: shown <yuluo08290126@gmail.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-08 23:43:27 +08:00
Nick Guoandtomsun28 07d22a8712 [Improve] Standardize Kafka metric naming (#2961)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-08 23:38:29 +08:00
tomsun28 7847fd78b7 [bufix] fix collector job scheduler error (#2966)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-01-08 21:44:42 +08:00
tomsun28 f7676ac8ca [improve] update alarm inhibit rule and alarm ui (#2957) 2025-01-08 10:04:07 +08:00
Jastandtomsun28 dd1cc700d9 [Imporve] Improve Huaweicloud (#2954)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-07 23:05:05 +08:00
Nick Guo d8dabe251a [Doc]Add English version of documentation for Kafka Consumer Detail (#2953) 2025-01-07 10:12:47 +08:00
Nick GuoandLogic f2e647d777 【Improve】adjust log level from INFO to WARN. (#2952)
Co-authored-by: Logic <zqr10159@dromara.org>
2025-01-06 13:50:09 +08:00
tomsun28 7bac0245a2 [improve] fix some alarm relate bug, update alarm center ui (#2951)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-01-06 11:25:52 +08:00
Jast d6e5399121 [Improve]Modify Chinese comments (#2950) 2025-01-06 00:36:29 +08:00
doveLinandaias00 00076fc704 [Feature] Support customized JMX monitoring through the Factory Pattern. (#2932)
Co-authored-by: aias00 <rokkki@163.com>
2025-01-05 17:59:42 +08:00
yunfan24andtomsun28 4a3d61bd9e [doc] Add Supported MySQL Versions. (#2949)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-05 16:53:50 +08:00
Logic af59b487e4 [bugfix](db): optimize column update. (#2947) 2025-01-05 16:48:56 +08:00
4cd3c3c97f [alarm] refactor new alarm (#2902)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Calvin <naruse_shinji@163.com>
Co-authored-by: a-little-fool <2030509072@qq.com>
Co-authored-by: Logic <zqr10159@dromara.org>
2025-01-04 22:18:27 +08:00
69a21ffaa1 [refactor] refactoring methods replaceCryPlaceholder and replaceSmilingPlace (#2832)
Co-authored-by: Zhang Yuxuan <1373529784@qq.com>
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: Calvin <naruse_shinji@163.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-04 19:54:05 +08:00
Jast 8ab71e672f [Improve]Improve e2e code (#2945) 2025-01-04 15:58:22 +08:00
7625a0b508 [home] updated navbar css #2928 (#2934)
Signed-off-by: Vedant7789 <147625492+Vedant7789@users.noreply.github.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: shown <yuluo08290126@gmail.com>
Co-authored-by: aias00 <rokkki@163.com>
2025-01-03 17:43:51 +08:00
dc963833e8 [improve] Refactor and Split the Message Notification Component. (#2924)
Co-authored-by: shown <yuluo08290126@gmail.com>
Co-authored-by: aias00 <rokkki@163.com>
2025-01-03 17:15:56 +08:00
Craaaaazy77andshown 87d4379efb [bugfix] Modify the doris_be.md document into an English version (#2936)
Signed-off-by: Craaaaazy77 <417989286@qq.com>
Co-authored-by: shown <yuluo08290126@gmail.com>
2025-01-03 14:40:42 +08:00
Simon Sigréandtomsun28 bffd7c1d4f [bugfix] Corrected case 'DashBoard' is a lower case 'B' (#2935)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-02 20:27:24 +08:00
yunfan24 ba21ef5cdb [improve] Improve the synchronization of the mute status. (#2927) 2025-01-02 18:59:44 +08:00
Simon Sigréandtomsun28 3bf237a4eb [feature] Adding CPU Temperature Check Into Default Ubuntu Checks (#2930)
Signed-off-by: Simon Sigré <simon.sigre@gmail.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-02 00:57:05 +08:00
ponfeeandtomsun28 19e7f128a3 [improve] optimize website navbar css(#2928) (#2929)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-01 14:49:57 +08:00
Jastandtomsun28 5c409d7512 [Improve] Optimize the e2e code structure (#2926)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-01 14:14:26 +08:00
6bf20ba21a [bugfix] Fixed the 'java.lang.UnsupportedOperationException' exception caused by getCurrentMetricsData (#2923)
Co-authored-by: bcxc <lixiaobaivvv@gmail.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-01-01 14:00:32 +08:00
Simon Sigré 8c9b1f3f92 [doc] Update app-windows_script.yml (#2922)
Signed-off-by: Simon Sigré <simon.sigre@gmail.com>
2025-01-01 00:28:34 +08:00
yunfan24 b3eb0083df [bugfix] Bug fix for alarm voice. (#2920) 2024-12-30 23:41:48 +08:00
LogicandJast e51accb3e6 [improve](warehouse): replace empty json object key with empty string (#2919)
Co-authored-by: Jast <shenghang@apache.org>
2024-12-30 20:08:33 +08:00
Logic a9e219811a [bugfix]: fix setColumns method in CollectRep class (#2918) 2024-12-30 19:58:00 +08:00
516 changed files with 59319 additions and 15664 deletions
+136
View File
@@ -2162,6 +2162,142 @@
"contributions": [
"code"
]
},
{
"login": "lctking",
"name": "nullwli",
"avatar_url": "https://avatars.githubusercontent.com/u/168249998?v=4",
"profile": "https://github.com/lctking",
"contributions": [
"code"
]
},
{
"login": "simonsigre",
"name": "Simon Sigré",
"avatar_url": "https://avatars.githubusercontent.com/u/14932913?v=4",
"profile": "https://simonsigre.com/",
"contributions": [
"doc"
]
},
{
"login": "ponfee",
"name": "ponfee",
"avatar_url": "https://avatars.githubusercontent.com/u/46117331?v=4",
"profile": "http://www.ponfee.cn/",
"contributions": [
"code"
]
},
{
"login": "Vedant7789",
"name": "Vedant7789",
"avatar_url": "https://avatars.githubusercontent.com/u/147625492?v=4",
"profile": "https://github.com/Vedant7789",
"contributions": [
"code"
]
},
{
"login": "Craaaaazy77",
"name": "Craaaaazy77",
"avatar_url": "https://avatars.githubusercontent.com/u/23025522?v=4",
"profile": "https://github.com/Craaaaazy77",
"contributions": [
"doc"
]
},
{
"login": "Suvrat1629",
"name": "Suvrat1629",
"avatar_url": "https://avatars.githubusercontent.com/u/140749446?v=4",
"profile": "https://github.com/Suvrat1629",
"contributions": [
"code"
]
},
{
"login": "ghyghoo8",
"name": "ghy",
"avatar_url": "https://avatars.githubusercontent.com/u/363129?v=4",
"profile": "http://ghyghoo8.github.io/",
"contributions": [
"code"
]
},
{
"login": "helei1030",
"name": "helei1030",
"avatar_url": "https://avatars.githubusercontent.com/u/11839080?v=4",
"profile": "https://github.com/helei1030",
"contributions": [
"doc"
]
},
{
"login": "pjfanning",
"name": "PJ Fanning",
"avatar_url": "https://avatars.githubusercontent.com/u/11783444?v=4",
"profile": "https://medium.com/@pjfanning",
"contributions": [
"code"
]
},
{
"login": "MonsterChenzhuo",
"name": "monster",
"avatar_url": "https://avatars.githubusercontent.com/u/60029759?v=4",
"profile": "https://github.com/MonsterChenzhuo",
"contributions": [
"code"
]
},
{
"login": "MasamiYui",
"name": "Sherlock Yin",
"avatar_url": "https://avatars.githubusercontent.com/u/22274133?v=4",
"profile": "https://github.com/MasamiYui",
"contributions": [
"code"
]
},
{
"login": "wanhao23",
"name": "wanhao",
"avatar_url": "https://avatars.githubusercontent.com/u/29560961?v=4",
"profile": "https://github.com/wanhao23",
"contributions": [
"code",
"doc"
]
},
{
"login": "jonasHanhan",
"name": "jonasHanhan",
"avatar_url": "https://avatars.githubusercontent.com/u/130035609?v=4",
"profile": "https://github.com/jonasHanhan",
"contributions": [
"code"
]
},
{
"login": "NikhilMurugesan",
"name": "NikhilMurugesan",
"avatar_url": "https://avatars.githubusercontent.com/u/49281792?v=4",
"profile": "https://github.com/NikhilMurugesan",
"contributions": [
"code"
]
},
{
"login": "myangle1120",
"name": "myangle1120",
"avatar_url": "https://avatars.githubusercontent.com/u/19237013?v=4",
"profile": "https://github.com/myangle1120",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
+1 -1
View File
@@ -73,7 +73,7 @@ jobs:
# upload application logs
- name: Upload logs & API test reports
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: always()
with:
name: hz-logs-${{ github.run_id }}
+2 -2
View File
@@ -39,7 +39,7 @@ jobs:
run: npx docusaurus-prince-pdf -u https://hertzbeat.apache.org/docs --output docs-en.pdf
- name: Upload results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: docs-cn-pdf
path: docs-cn.pdf
@@ -47,7 +47,7 @@ jobs:
retention-days: 1
- name: Upload results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: docs-en-pdf
path: docs-en.pdf
+21
View File
@@ -487,6 +487,27 @@ Thanks to these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/doveLin0818"><img src="https://avatars.githubusercontent.com/u/190927907?v=4?s=100" width="100px;" alt="doveLin"/><br /><sub><b>doveLin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=doveLin0818" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://zzrl.cc/"><img src="https://avatars.githubusercontent.com/u/91836599?v=4?s=100" width="100px;" alt="yunfan24"/><br /><sub><b>yunfan24</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lctking"><img src="https://avatars.githubusercontent.com/u/168249998?v=4?s=100" width="100px;" alt="nullwli"/><br /><sub><b>nullwli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lctking" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://simonsigre.com/"><img src="https://avatars.githubusercontent.com/u/14932913?v=4?s=100" width="100px;" alt="Simon Sigré"/><br /><sub><b>Simon Sigré</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.ponfee.cn/"><img src="https://avatars.githubusercontent.com/u/46117331?v=4?s=100" width="100px;" alt="ponfee"/><br /><sub><b>ponfee</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ponfee" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Vedant7789"><img src="https://avatars.githubusercontent.com/u/147625492?v=4?s=100" width="100px;" alt="Vedant7789"/><br /><sub><b>Vedant7789</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Vedant7789" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Craaaaazy77"><img src="https://avatars.githubusercontent.com/u/23025522?v=4?s=100" width="100px;" alt="Craaaaazy77"/><br /><sub><b>Craaaaazy77</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Craaaaazy77" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Suvrat1629"><img src="https://avatars.githubusercontent.com/u/140749446?v=4?s=100" width="100px;" alt="Suvrat1629"/><br /><sub><b>Suvrat1629</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Suvrat1629" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://ghyghoo8.github.io/"><img src="https://avatars.githubusercontent.com/u/363129?v=4?s=100" width="100px;" alt="ghy"/><br /><sub><b>ghy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ghyghoo8" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/helei1030"><img src="https://avatars.githubusercontent.com/u/11839080?v=4?s=100" width="100px;" alt="helei1030"/><br /><sub><b>helei1030</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=helei1030" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MonsterChenzhuo"><img src="https://avatars.githubusercontent.com/u/60029759?v=4?s=100" width="100px;" alt="monster"/><br /><sub><b>monster</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MonsterChenzhuo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wanhao23"><img src="https://avatars.githubusercontent.com/u/29560961?v=4?s=100" width="100px;" alt="wanhao"/><br /><sub><b>wanhao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jonasHanhan"><img src="https://avatars.githubusercontent.com/u/130035609?v=4?s=100" width="100px;" alt="jonasHanhan"/><br /><sub><b>jonasHanhan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jonasHanhan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NikhilMurugesan"><img src="https://avatars.githubusercontent.com/u/49281792?v=4?s=100" width="100px;" alt="NikhilMurugesan"/><br /><sub><b>NikhilMurugesan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=NikhilMurugesan" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
</tr>
</tbody>
</table>
+21
View File
@@ -486,6 +486,27 @@ Thanks these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/doveLin0818"><img src="https://avatars.githubusercontent.com/u/190927907?v=4?s=100" width="100px;" alt="doveLin"/><br /><sub><b>doveLin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=doveLin0818" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://zzrl.cc/"><img src="https://avatars.githubusercontent.com/u/91836599?v=4?s=100" width="100px;" alt="yunfan24"/><br /><sub><b>yunfan24</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lctking"><img src="https://avatars.githubusercontent.com/u/168249998?v=4?s=100" width="100px;" alt="nullwli"/><br /><sub><b>nullwli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lctking" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://simonsigre.com/"><img src="https://avatars.githubusercontent.com/u/14932913?v=4?s=100" width="100px;" alt="Simon Sigré"/><br /><sub><b>Simon Sigré</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.ponfee.cn/"><img src="https://avatars.githubusercontent.com/u/46117331?v=4?s=100" width="100px;" alt="ponfee"/><br /><sub><b>ponfee</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ponfee" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Vedant7789"><img src="https://avatars.githubusercontent.com/u/147625492?v=4?s=100" width="100px;" alt="Vedant7789"/><br /><sub><b>Vedant7789</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Vedant7789" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Craaaaazy77"><img src="https://avatars.githubusercontent.com/u/23025522?v=4?s=100" width="100px;" alt="Craaaaazy77"/><br /><sub><b>Craaaaazy77</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Craaaaazy77" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Suvrat1629"><img src="https://avatars.githubusercontent.com/u/140749446?v=4?s=100" width="100px;" alt="Suvrat1629"/><br /><sub><b>Suvrat1629</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Suvrat1629" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://ghyghoo8.github.io/"><img src="https://avatars.githubusercontent.com/u/363129?v=4?s=100" width="100px;" alt="ghy"/><br /><sub><b>ghy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ghyghoo8" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/helei1030"><img src="https://avatars.githubusercontent.com/u/11839080?v=4?s=100" width="100px;" alt="helei1030"/><br /><sub><b>helei1030</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=helei1030" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MonsterChenzhuo"><img src="https://avatars.githubusercontent.com/u/60029759?v=4?s=100" width="100px;" alt="monster"/><br /><sub><b>monster</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MonsterChenzhuo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wanhao23"><img src="https://avatars.githubusercontent.com/u/29560961?v=4?s=100" width="100px;" alt="wanhao"/><br /><sub><b>wanhao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jonasHanhan"><img src="https://avatars.githubusercontent.com/u/130035609?v=4?s=100" width="100px;" alt="jonasHanhan"/><br /><sub><b>jonasHanhan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jonasHanhan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NikhilMurugesan"><img src="https://avatars.githubusercontent.com/u/49281792?v=4?s=100" width="100px;" alt="NikhilMurugesan"/><br /><sub><b>NikhilMurugesan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=NikhilMurugesan" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
</tr>
</tbody>
</table>
+5 -5
View File
@@ -302,13 +302,13 @@ items:
Authorization: Bearer {{.login.data.token}}
- name: deleteAlertDefine
request:
api: /api/alert/defines?ids={{(index .listAlertDefines.data.content 0).id | int64}}
api: /api/alert/defines?ids=1
method: DELETE
header:
Authorization: Bearer {{.login.data.token}}
- name: listAlertConverges
- name: listAlertGroupConverges
request:
api: /api/alert/converges
api: /api/alert/groups
header:
Authorization: Bearer {{.login.data.token}}
- name: listAlertSilences
@@ -316,9 +316,9 @@ items:
api: /api/alert/silences
header:
Authorization: Bearer {{.login.data.token}}
- name: listAlerts
- name: listGroupAlerts
request:
api: /api/alerts
api: /api/alerts/group
header:
Authorization: Bearer {{.login.data.token}}
- name: listReceivers
+56 -1
View File
@@ -32,9 +32,19 @@
<!-- common -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-common</artifactId>
<artifactId>hertzbeat-base</artifactId>
<scope>provided</scope>
</dependency>
<!-- plugin -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-plugin</artifactId>
</dependency>
<!-- warehouse -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-warehouse</artifactId>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -50,6 +60,21 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- email -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>${nekohtml.version}</version>
</dependency>
<!-- kafka -->
<dependency>
<groupId>org.apache.kafka</groupId>
@@ -73,6 +98,36 @@
<version>${easy-poi.version}</version>
<scope>compile</scope>
</dependency>
<!-- sms -->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-sms</artifactId>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp</groupId>
<artifactId>logging-interceptor</artifactId>
</exclusion>
<exclusion>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
<exclusion>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.huaweicloud.sdk</groupId>
<artifactId>huaweicloud-sdk-smn</artifactId>
<version>${huawei.sdk.version}</version>
<exclusions>
<exclusion>
<groupId>org.openeuler</groupId>
<artifactId>bgmprovider</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.arrow</groupId>
<artifactId>arrow-vector</artifactId>
@@ -79,6 +79,11 @@ public class AlerterProperties {
*/
private EntranceProperties entrance;
/**
* Inhibit configuration properties
*/
private InhibitProperties inhibit;
/**
* Data entry configuration properties
*/
@@ -105,4 +110,17 @@ public class AlerterProperties {
}
}
/**
* Inhibit configuration properties
*/
@Getter
@Setter
public static class InhibitProperties {
/**
* inhibit rule cache ttl, default 4h
*/
private long ttl = 4 * 60 * 60 * 1000L;
}
}
@@ -44,7 +44,7 @@ public class AlerterWorkerPool {
private void initWorkExecutor() {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("workerExecutor has uncaughtException.");
log.error("Alerter workerExecutor has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
@@ -62,7 +62,7 @@ public class AlerterWorkerPool {
private void initNotifyExecutor() {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("notifyExecutor has uncaughtException.");
log.error("Alerter notifyExecutor has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
@@ -1,492 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.calculate;
import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_STATUS_CODE_NOT_REACH;
import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_STATUS_CODE_PENDING;
import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_STATUS_CODE_SOLVED;
import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_CODE;
import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_METRIC;
import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_METRICS;
import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_MONITOR_APP;
import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_MONITOR_ID;
import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_MONITOR_NAME;
import com.google.common.collect.Maps;
import jakarta.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.alert.AlerterWorkerPool;
import org.apache.hertzbeat.alert.dao.AlertMonitorDao;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.AlertDefineService;
import org.apache.hertzbeat.alert.service.AlertService;
import org.apache.hertzbeat.alert.util.AlertTemplateUtil;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.constants.MetricDataConstants;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.entity.arrow.RowWrapper;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.TagItem;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.queue.CommonDataQueue;
import org.apache.hertzbeat.common.support.event.MonitorDeletedEvent;
import org.apache.hertzbeat.common.support.event.SystemConfigChangeEvent;
import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.hertzbeat.common.util.JexlExpressionRunner;
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
import org.springframework.context.event.EventListener;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
/**
* Calculate alarms based on the alarm definition rules and collected data
*/
@Component
@Slf4j
public class CalculateAlarm {
private static final String SYSTEM_VALUE_ROW_COUNT = "system_value_row_count";
private static final int CALCULATE_THREADS = 3;
/**
* The alarm in the process is triggered
* key - monitorId+alertDefineId+tags The alarm is a common threshold alarm
* key - monitorId Indicates the monitoring status availability reachability alarm
*/
private final Map<String, Alert> triggeredAlertMap;
/**
* The not recover alert
* key - monitorId + alertDefineId + tags
*/
private final Map<String, Alert> notRecoveredAlertMap;
private final AlerterWorkerPool workerPool;
private final CommonDataQueue dataQueue;
private final AlertDefineService alertDefineService;
private final AlarmCommonReduce alarmCommonReduce;
private ResourceBundle bundle;
private final AlertService alertService;
public CalculateAlarm(AlerterWorkerPool workerPool, CommonDataQueue dataQueue,
AlertDefineService alertDefineService, AlertMonitorDao monitorDao,
AlarmCommonReduce alarmCommonReduce, AlertService alertService) {
this.workerPool = workerPool;
this.dataQueue = dataQueue;
this.alarmCommonReduce = alarmCommonReduce;
this.alertDefineService = alertDefineService;
this.alertService = alertService;
this.bundle = ResourceBundleUtil.getBundle("alerter");
this.triggeredAlertMap = new ConcurrentHashMap<>(16);
this.notRecoveredAlertMap = new ConcurrentHashMap<>(16);
// Initialize stateAlertMap
List<Monitor> monitors = monitorDao.findMonitorsByStatus(CommonConstants.MONITOR_DOWN_CODE);
if (monitors != null) {
for (Monitor monitor : monitors) {
HashMap<String, String> tags = new HashMap<>(8);
tags.put(TAG_MONITOR_ID, String.valueOf(monitor.getId()));
tags.put(TAG_MONITOR_NAME, monitor.getName());
tags.put(TAG_MONITOR_APP, monitor.getApp());
this.notRecoveredAlertMap.put(monitor.getId() + CommonConstants.AVAILABILITY,
Alert.builder().tags(tags).target(CommonConstants.AVAILABILITY).status(ALERT_STATUS_CODE_PENDING).build());
}
}
startCalculate();
}
private void startCalculate() {
Runnable runnable = () -> {
while (!Thread.currentThread().isInterrupted()) {
try {
CollectRep.MetricsData metricsData = dataQueue.pollMetricsDataToAlerter();
if (metricsData != null) {
calculate(metricsData);
}
dataQueue.sendMetricsDataToStorage(metricsData);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
} catch (Exception e) {
log.error("calculate alarm error: {}.", e.getMessage(), e);
}
}
};
for (int i = 0; i < CALCULATE_THREADS; i++) {
workerPool.executeJob(runnable);
}
}
private void calculate(CollectRep.MetricsData metricsData) {
long currentTimeMilli = System.currentTimeMillis();
long monitorId = metricsData.getId();
String app = metricsData.getApp();
if (app.startsWith(CommonConstants.PROMETHEUS_APP_PREFIX)) {
app = CommonConstants.PROMETHEUS;
}
String metrics = metricsData.getMetrics();
// If the metrics whose scheduling priority is 0 has the status of collecting response data UN_REACHABLE/UN_CONNECTABLE,
// the highest severity alarm is generated to monitor the status change
if (metricsData.getPriority() == 0) {
handlerAvailableMetrics(monitorId, app, metricsData);
}
// Query the alarm definitions associated with the metrics of the monitoring type
// field - define[]
Map<String, List<AlertDefine>> defineMap = alertDefineService.getMonitorBindAlertDefines(monitorId, app, metrics);
if (defineMap.isEmpty()) {
return;
}
Map<String, Object> fieldValueMap = Maps.newHashMapWithExpectedSize(8);
try {
for (Map.Entry<String, List<AlertDefine>> entry : defineMap.entrySet()) {
List<AlertDefine> defines = entry.getValue();
for (AlertDefine define : defines) {
final String expr = define.getExpr();
if (StringUtils.isBlank(expr)) {
continue;
}
if (expr.contains(SYSTEM_VALUE_ROW_COUNT) && metricsData.getValues().size() == 0L) {
fieldValueMap.put(SYSTEM_VALUE_ROW_COUNT, 0);
try {
boolean match = execAlertExpression(fieldValueMap, expr);
try {
if (match) {
// If the threshold rule matches, the number of times the threshold has been triggered is determined and an alarm is triggered
afterThresholdRuleMatch(currentTimeMilli, monitorId, app, metrics, "", fieldValueMap, define);
// if the threshold is triggered, ignore other data rows
continue;
} else {
String alarmKey = String.valueOf(monitorId) + define.getId();
triggeredAlertMap.remove(alarmKey);
if (define.isRecoverNotice()) {
handleRecoveredAlert(currentTimeMilli, define, expr, alarmKey);
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
} catch (Exception ignored) {}
}
RowWrapper rowWrapper = metricsData.readRow();
while (rowWrapper.hasNextRow()) {
rowWrapper = rowWrapper.nextRow();
StringBuilder tagBuilder = new StringBuilder();
fieldValueMap.clear();
fieldValueMap.put(SYSTEM_VALUE_ROW_COUNT, metricsData.getValues().size());
rowWrapper.cellStream().forEach(cell -> {
String valueStr = cell.getValue();
if (CommonConstants.NULL_VALUE.equals(valueStr)) {
return;
}
final String fieldName = cell.getField().getName();
final int fieldType = cell.getMetadataAsInteger(MetricDataConstants.TYPE);
if (fieldType == CommonConstants.TYPE_NUMBER) {
final Double doubleValue;
if ((doubleValue = CommonUtil.parseStrDouble(valueStr)) != null) {
fieldValueMap.put(fieldName, doubleValue);
}
} else if (fieldType == CommonConstants.TYPE_TIME) {
final Integer integerValue;
if ((integerValue = CommonUtil.parseStrInteger(valueStr)) != null) {
fieldValueMap.put(fieldName, integerValue);
}
} else {
if (StringUtils.isNotEmpty(valueStr)) {
fieldValueMap.put(fieldName, valueStr);
}
}
if (cell.getMetadataAsBoolean(MetricDataConstants.LABEL)) {
tagBuilder.append("-").append(valueStr);
}
});
try {
boolean match = execAlertExpression(fieldValueMap, expr);
try {
if (match) {
// If the threshold rule matches, the number of times the threshold has been triggered is determined and an alarm is triggered
afterThresholdRuleMatch(currentTimeMilli, monitorId, app, metrics, tagBuilder.toString(), fieldValueMap, define);
} else {
String alarmKey = String.valueOf(monitorId) + define.getId() + tagBuilder;
triggeredAlertMap.remove(alarmKey);
if (define.isRecoverNotice()) {
handleRecoveredAlert(currentTimeMilli, define, expr, alarmKey);
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
} catch (Exception ignored) {}
}
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
private void handleRecoveredAlert(long currentTimeMilli, AlertDefine define, String expr, String alarmKey) {
Alert notResolvedAlert = notRecoveredAlertMap.remove(alarmKey);
if (notResolvedAlert != null) {
// Sending an alarm Restore
Map<String, String> tags = notResolvedAlert.getTags();
String content = this.bundle.getString("alerter.alarm.recover") + " : " + expr;
Alert resumeAlert = Alert.builder()
.tags(tags)
.target(define.getApp() + "." + define.getMetric() + "." + define.getField())
.content(content)
.priority(CommonConstants.ALERT_PRIORITY_CODE_WARNING)
.status(CommonConstants.ALERT_STATUS_CODE_RESTORED)
.firstAlarmTime(currentTimeMilli)
.lastAlarmTime(notResolvedAlert.getLastAlarmTime())
.triggerTimes(1)
.build();
alarmCommonReduce.reduceAndSendAlarm(resumeAlert);
}
}
private void afterThresholdRuleMatch(long currentTimeMilli, long monitorId, String app, String metrics, String tagStr,
Map<String, Object> fieldValueMap, AlertDefine define) {
String alarmKey = String.valueOf(monitorId) + define.getId() + tagStr;
Alert triggeredAlert = triggeredAlertMap.get(alarmKey);
if (triggeredAlert != null) {
int times = triggeredAlert.getTriggerTimes() + 1;
triggeredAlert.setTriggerTimes(times);
triggeredAlert.setFirstAlarmTime(currentTimeMilli);
triggeredAlert.setLastAlarmTime(currentTimeMilli);
int defineTimes = define.getTimes() == null ? 1 : define.getTimes();
if (times >= defineTimes) {
triggeredAlert.setStatus(ALERT_STATUS_CODE_PENDING);
triggeredAlertMap.remove(alarmKey);
notRecoveredAlertMap.put(alarmKey, triggeredAlert);
alarmCommonReduce.reduceAndSendAlarm(triggeredAlert.clone());
}
} else {
fieldValueMap.put(TAG_MONITOR_APP, app);
fieldValueMap.put(TAG_METRICS, metrics);
fieldValueMap.put(TAG_METRIC, define.getField());
Map<String, String> tags = new HashMap<>(8);
tags.put(TAG_MONITOR_ID, String.valueOf(monitorId));
tags.put(TAG_MONITOR_APP, app);
tags.put(CommonConstants.TAG_THRESHOLD_ID, String.valueOf(define.getId()));
if (!CollectionUtils.isEmpty(define.getTags())) {
for (TagItem tagItem : define.getTags()) {
fieldValueMap.put(tagItem.getName(), tagItem.getValue());
tags.put(tagItem.getName(), tagItem.getValue());
}
}
Alert alert = Alert.builder()
.tags(tags)
.priority(define.getPriority())
.status(ALERT_STATUS_CODE_NOT_REACH)
.target(app + "." + metrics + "." + define.getField())
.triggerTimes(1)
.firstAlarmTime(currentTimeMilli)
.lastAlarmTime(currentTimeMilli)
// Keyword matching and substitution in the template
.content(AlertTemplateUtil.render(define.getTemplate(), fieldValueMap))
.build();
int defineTimes = define.getTimes() == null ? 1 : define.getTimes();
if (1 >= defineTimes) {
alert.setStatus(ALERT_STATUS_CODE_PENDING);
notRecoveredAlertMap.put(alarmKey, alert);
alarmCommonReduce.reduceAndSendAlarm(alert.clone());
} else {
triggeredAlertMap.put(alarmKey, alert);
}
}
}
private boolean execAlertExpression(Map<String, Object> fieldValueMap, String expr) {
Boolean match;
JexlExpression expression;
try {
expression = JexlExpressionRunner.compile(expr);
} catch (JexlException jexlException) {
log.error("Alarm Rule: {} Compile Error: {}.", expr, jexlException.getMessage());
throw jexlException;
} catch (Exception e) {
log.error("Alarm Rule: {} Unknown Error: {}.", expr, e.getMessage());
throw e;
}
try {
match = (Boolean) JexlExpressionRunner.evaluate(expression, fieldValueMap);
} catch (JexlException jexlException) {
log.error("Alarm Rule: {} Run Error: {}.", expr, jexlException.getMessage());
throw jexlException;
} catch (Exception e) {
log.error("Alarm Rule: {} Unknown Error: {}.", expr, e.getMessage());
throw e;
}
return match != null && match;
}
private void handlerAvailableMetrics(long monitorId, String app, CollectRep.MetricsData metricsData) {
if (metricsData.getCode() == CollectRep.Code.TIMEOUT) {
return;
}
// TODO CACHE getMonitorBindAlertAvaDefine
AlertDefine avaAlertDefine = alertDefineService.getMonitorBindAlertAvaDefine(monitorId, app, CommonConstants.AVAILABILITY);
if (avaAlertDefine == null) {
return;
}
long currentTimeMill = System.currentTimeMillis();
if (metricsData.getCode() != CollectRep.Code.SUCCESS) {
Alert preAlert = triggeredAlertMap.get(String.valueOf(monitorId));
Map<String, String> tags = new HashMap<>(6);
tags.put(TAG_MONITOR_ID, String.valueOf(monitorId));
tags.put(TAG_MONITOR_APP, app);
tags.put(CommonConstants.TAG_THRESHOLD_ID, String.valueOf(avaAlertDefine.getId()));
tags.put(TAG_METRICS, CommonConstants.AVAILABILITY);
tags.put(TAG_CODE, metricsData.getCode().name());
Map<String, Object> valueMap = tags.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (!CollectionUtils.isEmpty(avaAlertDefine.getTags())) {
for (TagItem tagItem : avaAlertDefine.getTags()) {
valueMap.put(tagItem.getName(), tagItem.getValue());
tags.put(tagItem.getName(), tagItem.getValue());
}
}
if (preAlert == null) {
Alert.AlertBuilder alertBuilder = Alert.builder()
.tags(tags)
.priority(avaAlertDefine.getPriority())
.status(ALERT_STATUS_CODE_NOT_REACH)
.target(CommonConstants.AVAILABILITY)
.content(AlertTemplateUtil.render(avaAlertDefine.getTemplate(), valueMap))
.firstAlarmTime(currentTimeMill)
.lastAlarmTime(currentTimeMill)
.triggerTimes(1);
if (avaAlertDefine.getTimes() == null || avaAlertDefine.getTimes() <= 1) {
String notResolvedAlertKey = monitorId + CommonConstants.AVAILABILITY;
alertBuilder.status(ALERT_STATUS_CODE_PENDING);
notRecoveredAlertMap.put(notResolvedAlertKey, alertBuilder.build());
alarmCommonReduce.reduceAndSendAlarm(alertBuilder.build());
} else {
triggeredAlertMap.put(String.valueOf(monitorId), alertBuilder.build());
}
} else {
int times = preAlert.getTriggerTimes() + 1;
preAlert.setTriggerTimes(times);
preAlert.setFirstAlarmTime(currentTimeMill);
preAlert.setLastAlarmTime(currentTimeMill);
int defineTimes = avaAlertDefine.getTimes() == null ? 1 : avaAlertDefine.getTimes();
if (times >= defineTimes) {
preAlert.setStatus(ALERT_STATUS_CODE_PENDING);
String notResolvedAlertKey = monitorId + CommonConstants.AVAILABILITY;
notRecoveredAlertMap.put(notResolvedAlertKey, preAlert.clone());
alarmCommonReduce.reduceAndSendAlarm(preAlert.clone());
triggeredAlertMap.remove(String.valueOf(monitorId));
}
}
} else {
// Check whether an availability or unreachable alarm is generated before the association monitoring
// and send a clear alarm to clear the monitoring status
triggeredAlertMap.remove(String.valueOf(monitorId));
String notResolvedAlertKey = monitorId + CommonConstants.AVAILABILITY;
Alert notResolvedAlert = notRecoveredAlertMap.remove(notResolvedAlertKey);
if (notResolvedAlert != null) {
// Sending an alarm Restore
Map<String, String> tags = notResolvedAlert.getTags();
if (!avaAlertDefine.isRecoverNotice()) {
tags.put(CommonConstants.IGNORE, CommonConstants.IGNORE);
}
String content = this.bundle.getString("alerter.availability.recover");
Alert resumeAlert = Alert.builder()
.tags(tags)
.target(CommonConstants.AVAILABILITY)
.content(content)
.priority(CommonConstants.ALERT_PRIORITY_CODE_WARNING)
.status(CommonConstants.ALERT_STATUS_CODE_RESTORED)
.firstAlarmTime(currentTimeMill)
.lastAlarmTime(notResolvedAlert.getLastAlarmTime())
.triggerTimes(1)
.build();
alarmCommonReduce.reduceAndSendAlarm(resumeAlert);
Runnable updateStatusJob = () -> {
// todo update pre all type alarm status
updateAvailabilityAlertStatus(monitorId, resumeAlert);
};
workerPool.executeJob(updateStatusJob);
}
}
}
private void updateAvailabilityAlertStatus(long monitorId, Alert restoreAlert) {
List<Alert> availabilityAlerts = queryAvailabilityAlerts(monitorId, restoreAlert);
availabilityAlerts.stream().parallel().forEach(alert -> {
log.info("updating alert status solved id: {}", alert.getId());
alertService.editAlertStatus(ALERT_STATUS_CODE_SOLVED, List.of(alert.getId()));
});
}
private List<Alert> queryAvailabilityAlerts(long monitorId, Alert restoreAlert) {
//create query condition
Specification<Alert> specification = (root, query, criteriaBuilder) -> {
List<Predicate> andList = new ArrayList<>();
Predicate predicateTags = criteriaBuilder.like(root.get("tags").as(String.class), "%" + monitorId + "%");
andList.add(predicateTags);
Predicate predicatePriority = criteriaBuilder.equal(root.get("priority"), CommonConstants.ALERT_PRIORITY_CODE_EMERGENCY);
andList.add(predicatePriority);
Predicate predicateStatus = criteriaBuilder.equal(root.get("status"), ALERT_STATUS_CODE_PENDING);
andList.add(predicateStatus);
Predicate predicateAlertTime = criteriaBuilder.lessThanOrEqualTo(root.get("lastAlarmTime"), restoreAlert.getLastAlarmTime());
andList.add(predicateAlertTime);
Predicate[] predicates = new Predicate[andList.size()];
return criteriaBuilder.and(andList.toArray(predicates));
};
//query results
return alertService.getAlerts(specification);
}
@EventListener(SystemConfigChangeEvent.class)
public void onSystemConfigChangeEvent(SystemConfigChangeEvent event) {
log.info("calculate alarm receive system config change event: {}.", event.getSource());
this.bundle = ResourceBundleUtil.getBundle("alerter");
}
@EventListener(MonitorDeletedEvent.class)
public void onMonitorDeletedEvent(MonitorDeletedEvent event) {
log.info("calculate alarm receive monitor {} has been deleted.", event.getMonitorId());
this.triggeredAlertMap.remove(String.valueOf(event.getMonitorId()));
}
}
@@ -0,0 +1,188 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.calculate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.DataSourceService;
import org.apache.hertzbeat.alert.util.AlertTemplateUtil;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
/**
* Periodic Alert Calculator
*/
@Slf4j
@Component
public class PeriodicAlertCalculator {
private static final String VALUE = "__value__";
private static final String TIMESTAMP = "__timestamp__";
private final DataSourceService dataSourceService;
private final AlarmCommonReduce alarmCommonReduce;
/**
* The alarm in the process is triggered
* key - labels fingerprint
*/
private final Map<String, SingleAlert> pendingAlertMap;
/**
* The not recover alert
* key - labels fingerprint
*/
private final Map<String, SingleAlert> firingAlertMap;
public PeriodicAlertCalculator(DataSourceService dataSourceService, AlarmCommonReduce alarmCommonReduce) {
this.dataSourceService = dataSourceService;
this.alarmCommonReduce = alarmCommonReduce;
this.pendingAlertMap = new ConcurrentHashMap<>(8);
this.firingAlertMap = new ConcurrentHashMap<>(8);
}
public void calculate(AlertDefine rule) {
if (!rule.isEnable() || StringUtils.isEmpty(rule.getExpr())) {
log.error("Periodic rule {} is disabled or expression is empty", rule.getName());
return;
}
long currentTimeMilli = System.currentTimeMillis();
try {
// for prometheus is instant promql query, for db is sql query
// result: [{'value': 100, 'timestamp': 1343554, 'instance': 'node1'},{'value': 200, 'timestamp': 1343555, 'instance': 'node2'}]
// the return result should be matched with threshold
try {
List<Map<String, Object>> results = dataSourceService.calculate(
rule.getDatasource(),
rule.getExpr()
);
// if no match the expr threshold, the results item map {'value': null} should be null and others field keep
// if results has multi list, should trigger multi alert
if (CollectionUtils.isEmpty(results)) {
return;
}
for (Map<String, Object> result : results) {
Map<String, String> fingerPrints = new HashMap<>(8);
// here use the alert name as finger, not care the alert name may be changed
fingerPrints.put(CommonConstants.LABEL_ALERT_NAME, rule.getName());
fingerPrints.putAll(rule.getLabels());
for (Map.Entry<String, Object> entry : result.entrySet()) {
if (entry.getValue() != null && !VALUE.equals(entry.getKey())
&& !TIMESTAMP.equals(entry.getKey())) {
fingerPrints.put(entry.getKey(), entry.getValue().toString());
}
}
if (result.get(VALUE) == null) {
// recovery the alert
handleRecoveredAlert(fingerPrints);
continue;
}
Map<String, Object> fieldValueMap = new HashMap<>(8);
fieldValueMap.putAll(rule.getLabels());
fieldValueMap.put(CommonConstants.LABEL_ALERT_NAME, rule.getName());
for (Map.Entry<String, Object> entry : result.entrySet()) {
if (entry.getValue() != null) {
fieldValueMap.put(entry.getKey(), entry.getValue());
}
}
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, rule);
}
} catch (Exception ignored) {
// ignore the query exception eg: no result, timeout, etc
return;
}
} catch (Exception e) {
log.error("Calculate periodic rule {} failed: {}", rule.getName(), e.getMessage());
}
}
private void afterThresholdRuleMatch(long currentTimeMilli, Map<String, String> fingerPrints,
Map<String, Object> fieldValueMap, AlertDefine define) {
String fingerprint = calculateFingerprint(fingerPrints);
SingleAlert existingAlert = pendingAlertMap.get(fingerprint);
Map<String, String> labels = new HashMap<>(8);
fieldValueMap.putAll(define.getLabels());
labels.putAll(fingerPrints);
int requiredTimes = define.getTimes() == null ? 1 : define.getTimes();
if (existingAlert == null) {
// First time triggering alert, create new alert and set to pending status
SingleAlert newAlert = SingleAlert.builder()
.labels(labels)
// todo render var content in annotations
.annotations(define.getAnnotations())
.content(AlertTemplateUtil.render(define.getTemplate(), fieldValueMap))
.status(CommonConstants.ALERT_STATUS_PENDING)
.triggerTimes(1)
.startAt(currentTimeMilli)
.activeAt(currentTimeMilli)
.build();
// If required trigger times is 1, set to firing status directly
if (requiredTimes <= 1) {
newAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
firingAlertMap.put(fingerprint, newAlert);
alarmCommonReduce.reduceAndSendAlarm(newAlert.clone());
} else {
// Otherwise put into pending queue first
pendingAlertMap.put(fingerprint, newAlert);
}
} else {
// Update existing alert
existingAlert.setTriggerTimes(existingAlert.getTriggerTimes() + 1);
existingAlert.setActiveAt(currentTimeMilli);
// Check if required trigger times reached
if (existingAlert.getStatus().equals(CommonConstants.ALERT_STATUS_PENDING) && existingAlert.getTriggerTimes() >= requiredTimes) {
// Reached trigger times threshold, change to firing status
pendingAlertMap.remove(fingerprint);
existingAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
firingAlertMap.put(fingerprint, existingAlert);
alarmCommonReduce.reduceAndSendAlarm(existingAlert.clone());
}
}
}
private void handleRecoveredAlert(Map<String, String> fingerprints) {
String fingerprint = calculateFingerprint(fingerprints);
SingleAlert firingAlert = firingAlertMap.remove(fingerprint);
if (firingAlert != null) {
// todo consider multi times to tig for resolved alert
firingAlert.setTriggerTimes(1);
firingAlert.setEndAt(System.currentTimeMillis());
firingAlert.setStatus(CommonConstants.ALERT_STATUS_RESOLVED);
alarmCommonReduce.reduceAndSendAlarm(firingAlert.clone());
}
pendingAlertMap.remove(fingerprint);
}
private String calculateFingerprint(Map<String, String> fingerPrints) {
List<String> keyList = fingerPrints.keySet().stream().filter(Objects::nonNull).sorted().toList();
List<String> valueList = fingerPrints.values().stream().filter(Objects::nonNull).sorted().toList();
return Arrays.hashCode(keyList.toArray(new String[0])) + "-"
+ Arrays.hashCode(valueList.toArray(new String[0]));
}
}
@@ -0,0 +1,95 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.calculate;
import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_THRESHOLD_TYPE_PERIODIC;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
/**
* Periodic Alert Rule Scheduler
*/
@Slf4j
@Component
public class PeriodicAlertRuleScheduler implements CommandLineRunner {
private final PeriodicAlertCalculator calculator;
private final AlertDefineDao alertDefineDao;
private final ScheduledExecutorService scheduledExecutor;
private final Map<Long, ScheduledFuture<?>> scheduledFutures;
public PeriodicAlertRuleScheduler(PeriodicAlertCalculator calculator, AlertDefineDao alertDefineDao) {
this.calculator = calculator;
this.alertDefineDao = alertDefineDao;
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("Scheduled periodic alert threshold has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
.setNameFormat("periodic-alert-threshold-worker-%d")
.build();
this.scheduledExecutor = Executors.newScheduledThreadPool(10, threadFactory);
this.scheduledFutures = new ConcurrentHashMap<>();
}
public void cancelSchedule(Long ruleId) {
if (ruleId == null) {
return;
}
ScheduledFuture<?> future = scheduledFutures.get(ruleId);
if (future != null) {
future.cancel(true);
scheduledFutures.remove(ruleId);
}
}
public void updateSchedule(AlertDefine rule) {
if (rule == null || rule.getId() == null) {
log.error("Alert rule is null or rule id is null.");
return;
}
cancelSchedule(rule.getId());
if (rule.getType().equals(ALERT_THRESHOLD_TYPE_PERIODIC)) {
ScheduledFuture<?> future = scheduledExecutor.scheduleAtFixedRate(() -> {
calculator.calculate(rule);
}, 0, rule.getPeriod(), java.util.concurrent.TimeUnit.SECONDS);
scheduledFutures.put(rule.getId(), future);
}
}
@Override
public void run(String... args) throws Exception {
log.info("Starting periodic alert rule scheduler...");
List<AlertDefine> periodicRules = alertDefineDao.findAlertDefinesByTypeAndEnableTrue(ALERT_THRESHOLD_TYPE_PERIODIC);
for (AlertDefine rule : periodicRules) {
updateSchedule(rule);
}
}
}
@@ -0,0 +1,395 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.calculate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.alert.AlerterWorkerPool;
import org.apache.hertzbeat.alert.dao.SingleAlertDao;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.AlertDefineService;
import org.apache.hertzbeat.alert.util.AlertTemplateUtil;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.queue.CommonDataQueue;
import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.hertzbeat.common.util.JexlExpressionRunner;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
/**
* Calculate alarms based on the alarm definition rules and collected data
*/
@Component
@Slf4j
public class RealTimeAlertCalculator {
private static final int CALCULATE_THREADS = 3;
private static final String KEY_INSTANCE = "__instance__";
private static final String KEY_APP = "__app__";
private static final String KEY_METRICS = "__metrics__";
private static final String KEY_PRIORITY = "__priority__";
private static final String KEY_CODE = "__code__";
private static final String KEY_AVAILABLE = "__available__";
private static final String UP = "up";
private static final String DOWN = "down";
private static final String KEY_ROW = "__row__";
private static final Pattern APP_PATTERN = Pattern.compile("equals\\(__app__,\"([^\"]+)\"\\)");
private static final Pattern AVAILABLE_PATTERN = Pattern.compile("equals\\(__available__,\"([^\"]+)\"\\)");
private static final Pattern INSTANCE_PATTERN = Pattern.compile("equals\\(__instance__,\"(\\d+)\"\\)");
private static final Pattern METRICS_PATTERN = Pattern.compile("equals\\(__metrics__,\"([^\"]+)\"\\)");
/**
* The alarm in the process is triggered
* key - labels fingerprint
*/
private final Map<String, SingleAlert> pendingAlertMap;
/**
* The not recover alert
* key - labels fingerprint
*/
private final Map<String, SingleAlert> firingAlertMap;
private final AlerterWorkerPool workerPool;
private final CommonDataQueue dataQueue;
private final AlertDefineService alertDefineService;
private final AlarmCommonReduce alarmCommonReduce;
public RealTimeAlertCalculator(AlerterWorkerPool workerPool, CommonDataQueue dataQueue,
AlertDefineService alertDefineService, SingleAlertDao singleAlertDao,
AlarmCommonReduce alarmCommonReduce) {
this.workerPool = workerPool;
this.dataQueue = dataQueue;
this.alarmCommonReduce = alarmCommonReduce;
this.alertDefineService = alertDefineService;
this.pendingAlertMap = new ConcurrentHashMap<>(8);
this.firingAlertMap = new ConcurrentHashMap<>(8);
// Initialize firing stateAlertMap
List<SingleAlert> singleAlerts = singleAlertDao.querySingleAlertsByStatus(CommonConstants.ALERT_STATUS_FIRING);
for (SingleAlert singleAlert : singleAlerts) {
String fingerprint = calculateFingerprint(singleAlert.getLabels());
singleAlert.setId(null);
firingAlertMap.put(fingerprint, singleAlert);
}
startCalculate();
}
private void startCalculate() {
Runnable runnable = () -> {
while (!Thread.currentThread().isInterrupted()) {
try {
CollectRep.MetricsData metricsData = dataQueue.pollMetricsDataToAlerter();
calculate(metricsData);
dataQueue.sendMetricsDataToStorage(metricsData);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
} catch (Exception e) {
log.error("calculate alarm error: {}.", e.getMessage(), e);
}
}
};
for (int i = 0; i < CALCULATE_THREADS; i++) {
workerPool.executeJob(runnable);
}
}
private void calculate(CollectRep.MetricsData metricsData) {
long currentTimeMilli = System.currentTimeMillis();
String instance = String.valueOf(metricsData.getId());
String app = metricsData.getApp();
String metrics = metricsData.getMetrics();
int priority = metricsData.getPriority();
int code = metricsData.getCode().getNumber();
List<AlertDefine> thresholds = this.alertDefineService.getRealTimeAlertDefines();
// Filter thresholds by app, metrics and instance
thresholds = filterThresholdsByAppAndMetrics(thresholds, app, metrics, instance, priority);
if (thresholds.isEmpty()) {
return;
}
Map<String, Object> commonContext = new HashMap<>(8);
commonContext.put(KEY_INSTANCE, instance);
commonContext.put(KEY_APP, app);
commonContext.put(KEY_PRIORITY, priority);
commonContext.put(KEY_CODE, code);
commonContext.put(KEY_METRICS, metrics);
if (priority == 0) {
commonContext.put(KEY_AVAILABLE, metricsData.getCode() == CollectRep.Code.SUCCESS ? UP : DOWN);
}
List<CollectRep.Field> fields = metricsData.getFields();
Map<String, Object> fieldValueMap = new HashMap<>(8);
int valueRowCount = metricsData.getValuesCount();
for (AlertDefine define : thresholds) {
if (define.getLabels() == null) {
define.setLabels(new HashMap<>(8));
}
if (define.getAnnotations() == null) {
define.setAnnotations(new HashMap<>(8));
}
fieldValueMap.clear();
fieldValueMap.putAll(commonContext);
final String expr = define.getExpr();
if (StringUtils.isBlank(expr)) {
continue;
}
{
// trigger the expr before the metrics data, due the available up down or others
try {
boolean match = execAlertExpression(fieldValueMap, expr, true);
try {
Map<String, String> fingerPrints = new HashMap<>(8);
fingerPrints.put(CommonConstants.LABEL_INSTANCE, instance);
// here use the alert name as finger, not care the alert name may be changed
fingerPrints.put(CommonConstants.LABEL_ALERT_NAME, define.getName());
fingerPrints.putAll(define.getLabels());
if (match) {
// If the threshold rule matches, the number of times the threshold has been triggered is determined and an alarm is triggered
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, define);
} else {
handleRecoveredAlert(fingerPrints);
}
// if this threshold pre compile success, ignore blew
continue;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
} catch (Exception ignored) {}
}
Map<String, String> fingerPrints = new HashMap<>(8);
for (CollectRep.ValueRow valueRow : metricsData.getValues()) {
if (CollectionUtils.isEmpty(valueRow.getColumnsList())) {
continue;
}
fieldValueMap.clear();
fieldValueMap.put(KEY_ROW, valueRowCount);
fieldValueMap.putAll(commonContext);
fingerPrints.clear();
fingerPrints.put(CommonConstants.LABEL_INSTANCE, instance);
fingerPrints.put(CommonConstants.LABEL_ALERT_NAME, define.getName());
fingerPrints.putAll(define.getLabels());
for (int index = 0; index < valueRow.getColumnsList().size(); index++) {
String valueStr = valueRow.getColumns(index);
if (CommonConstants.NULL_VALUE.equals(valueStr)) {
continue;
}
final CollectRep.Field field = fields.get(index);
final int fieldType = field.getType();
if (fieldType == CommonConstants.TYPE_NUMBER) {
final Double doubleValue;
if ((doubleValue = CommonUtil.parseStrDouble(valueStr)) != null) {
fieldValueMap.put(field.getName(), doubleValue);
}
} else if (fieldType == CommonConstants.TYPE_TIME) {
final Integer integerValue;
if ((integerValue = CommonUtil.parseStrInteger(valueStr)) != null) {
fieldValueMap.put(field.getName(), integerValue);
}
} else {
if (StringUtils.isNotEmpty(valueStr)) {
fieldValueMap.put(field.getName(), valueStr);
}
}
if (field.getLabel()) {
fingerPrints.put(field.getName(), valueStr);
}
}
try {
boolean match = execAlertExpression(fieldValueMap, expr, false);
try {
if (match) {
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, define);
} else {
handleRecoveredAlert(fingerPrints);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
} catch (Exception ignored) {}
}
}
}
/**
* Filter alert definitions by app, metrics and instance
*
* @param thresholds Alert definitions to filter
* @param app Current app name
* @param metrics Current metrics name
* @param instance Current instance id
* @param priority Current priority
* @return Filtered alert definitions
*/
private List<AlertDefine> filterThresholdsByAppAndMetrics(List<AlertDefine> thresholds, String app, String metrics, String instance, int priority) {
return thresholds.stream()
.filter(define -> {
if (StringUtils.isBlank(define.getExpr())) {
return false;
}
String expr = define.getExpr();
// Extract and check app - required
Matcher appMatcher = APP_PATTERN.matcher(expr);
if (!appMatcher.find() || !app.equals(appMatcher.group(1))) {
return false;
}
// Extract and check available - required
if (priority != 0) {
Matcher availableMatcher = AVAILABLE_PATTERN.matcher(expr);
if (availableMatcher.find()) {
return false;
}
}
// Extract and check metrics - optional
Matcher metricsMatcher = METRICS_PATTERN.matcher(expr);
if (metricsMatcher.find() && !metrics.equals(metricsMatcher.group(1))) {
return false;
}
// Extract and check instance - optional with multiple values
Matcher instanceMatcher = INSTANCE_PATTERN.matcher(expr);
// If no instance specified in expr, accept all instances
if (!instanceMatcher.find()) {
return true;
}
// Reset matcher to check all instances
instanceMatcher.reset();
// If instances specified, current instance must match one of them
while (instanceMatcher.find()) {
if (Objects.equals(instance, instanceMatcher.group(1))) {
return true;
}
}
return false;
})
.collect(Collectors.toList());
}
private void handleRecoveredAlert(Map<String, String> fingerprints) {
String fingerprint = calculateFingerprint(fingerprints);
SingleAlert firingAlert = firingAlertMap.remove(fingerprint);
if (firingAlert != null) {
// todo consider multi times to tig for resolved alert
firingAlert.setTriggerTimes(1);
firingAlert.setEndAt(System.currentTimeMillis());
firingAlert.setStatus(CommonConstants.ALERT_STATUS_RESOLVED);
alarmCommonReduce.reduceAndSendAlarm(firingAlert.clone());
}
pendingAlertMap.remove(fingerprint);
}
private void afterThresholdRuleMatch(long currentTimeMilli, Map<String, String> fingerPrints,
Map<String, Object> fieldValueMap, AlertDefine define) {
String fingerprint = calculateFingerprint(fingerPrints);
SingleAlert existingAlert = pendingAlertMap.get(fingerprint);
Map<String, String> labels = new HashMap<>(8);
fieldValueMap.putAll(define.getLabels());
labels.putAll(fingerPrints);
int requiredTimes = define.getTimes() == null ? 1 : define.getTimes();
if (existingAlert == null) {
// First time triggering alert, create new alert and set to pending status
SingleAlert newAlert = SingleAlert.builder()
.labels(labels)
// todo render var content in annotations
.annotations(define.getAnnotations())
.content(AlertTemplateUtil.render(define.getTemplate(), fieldValueMap))
.status(CommonConstants.ALERT_STATUS_PENDING)
.triggerTimes(1)
.startAt(currentTimeMilli)
.activeAt(currentTimeMilli)
.build();
// If required trigger times is 1, set to firing status directly
if (requiredTimes <= 1) {
newAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
firingAlertMap.put(fingerprint, newAlert);
alarmCommonReduce.reduceAndSendAlarm(newAlert.clone());
} else {
// Otherwise put into pending queue first
pendingAlertMap.put(fingerprint, newAlert);
}
} else {
// Update existing alert
existingAlert.setTriggerTimes(existingAlert.getTriggerTimes() + 1);
existingAlert.setActiveAt(currentTimeMilli);
// Check if required trigger times reached
if (existingAlert.getStatus().equals(CommonConstants.ALERT_STATUS_PENDING) && existingAlert.getTriggerTimes() >= requiredTimes) {
// Reached trigger times threshold, change to firing status
pendingAlertMap.remove(fingerprint);
existingAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
firingAlertMap.put(fingerprint, existingAlert);
alarmCommonReduce.reduceAndSendAlarm(existingAlert.clone());
}
}
}
private boolean execAlertExpression(Map<String, Object> fieldValueMap, String expr, boolean ignoreJexlException) {
Boolean match;
JexlExpression expression;
try {
expression = JexlExpressionRunner.compile(expr);
} catch (JexlException jexlException) {
log.warn("Alarm Rule: {} Compile Error: {}.", expr, jexlException.getMessage());
throw jexlException;
} catch (Exception e) {
log.error("Alarm Rule: {} Unknown Error: {}.", expr, e.getMessage());
throw e;
}
try {
match = (Boolean) JexlExpressionRunner.evaluate(expression, fieldValueMap);
} catch (JexlException jexlException) {
if (ignoreJexlException) {
log.debug("Alarm Rule: {} Run Error: {}.", expr, jexlException.getMessage());
} else {
log.error("Alarm Rule: {} Run Error: {}.", expr, jexlException.getMessage());
}
throw jexlException;
} catch (Exception e) {
log.error("Alarm Rule: {} Unknown Error: {}.", expr, e.getMessage());
throw e;
}
return match != null && match;
}
private String calculateFingerprint(Map<String, String> fingerPrints) {
List<String> keyList = fingerPrints.keySet().stream().filter(Objects::nonNull).sorted().toList();
List<String> valueList = fingerPrints.values().stream().filter(Objects::nonNull).sorted().toList();
return Arrays.hashCode(keyList.toArray(new String[0])) + "-"
+ Arrays.hashCode(valueList.toArray(new String[0]));
}
}
@@ -0,0 +1,63 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.alert.config;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* SSE manager for alert
*/
@Component
public class AlertSseManager {
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
public SseEmitter createEmitter(Long clientId) {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
emitter.onCompletion(() -> removeEmitter(clientId));
emitter.onTimeout(() -> removeEmitter(clientId));
emitters.put(clientId, emitter);
return emitter;
}
@Async
public void broadcast(String data) {
emitters.forEach((clientId, emitter) -> {
try {
emitter.send(SseEmitter.event()
.id(String.valueOf(System.currentTimeMillis()))
.name("ALERT_EVENT")
.data(data));
} catch (IOException e) {
emitter.complete();
removeEmitter(clientId);
}
});
}
private void removeEmitter(Long clientId) {
emitters.remove(clientId);
}
}
@@ -23,12 +23,9 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.hertzbeat.alert.service.AlertDefineService;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
@@ -93,24 +90,4 @@ public class AlertDefineController {
return ResponseEntity.ok(Message.success("Delete success"));
}
@PostMapping(path = "/{alertDefineId}/monitors")
@Operation(summary = "Application alarm definition is associated with monitoring",
description = "Applies the association between specified alarm definitions and monitoring")
public ResponseEntity<Message<Void>> applyAlertDefineMonitorsBind(
@Parameter(description = "Alarm Definition ID", example = "6565463543") @PathVariable("alertDefineId") long alertDefineId,
@RequestBody List<AlertDefineMonitorBind> alertDefineMonitorBinds) {
alertDefineService.applyBindAlertDefineMonitors(alertDefineId, alertDefineMonitorBinds);
return ResponseEntity.ok(Message.success("Apply success"));
}
@GetMapping(path = "/{alertDefineId}/monitors")
@Operation(summary = "Application alarm definition is associated with monitoring",
description = "Applies the association between specified alarm definitions and monitoring")
public ResponseEntity<Message<List<AlertDefineMonitorBind>>> getAlertDefineMonitorsBind(
@Parameter(description = "Alarm Definition ID", example = "6565463543") @PathVariable("alertDefineId") long alertDefineId) {
List<AlertDefineMonitorBind> defineBinds = alertDefineService.getBindAlertDefineMonitors(alertDefineId);
defineBinds = defineBinds.stream().filter(item -> item.getMonitor() != null).collect(Collectors.toList());
return ResponseEntity.ok(Message.success(defineBinds));
}
}
@@ -55,12 +55,11 @@ public class AlertDefinesController {
public ResponseEntity<Message<Page<AlertDefine>>> getAlertDefines(
@Parameter(description = "Alarm Definition ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
@Parameter(description = "Search-Target Expr Template", example = "x") @RequestParam(required = false) String search,
@Parameter(description = "Alarm Definition Severity", example = "6565463543") @RequestParam(required = false) Byte priority,
@Parameter(description = "Sort field, default id", example = "id") @RequestParam(defaultValue = "id") String sort,
@Parameter(description = "Sort mode: asc: ascending, desc: descending", example = "desc") @RequestParam(defaultValue = "desc") String order,
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
@Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
Page<AlertDefine> alertDefinePage = alertDefineService.getAlertDefines(ids, search, priority, sort, order, pageIndex, pageSize);
Page<AlertDefine> alertDefinePage = alertDefineService.getAlertDefines(ids, search, sort, order, pageIndex, pageSize);
return ResponseEntity.ok(Message.success(alertDefinePage));
}
@@ -24,8 +24,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.Objects;
import org.apache.hertzbeat.alert.service.AlertConvergeService;
import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
import org.apache.hertzbeat.alert.service.AlertGroupConvergeService;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
@@ -42,38 +42,38 @@ import org.springframework.web.bind.annotation.RestController;
*/
@Tag(name = "Alert Converge API")
@RestController
@RequestMapping(path = "/api/alert/converge", produces = {APPLICATION_JSON_VALUE})
public class AlertConvergeController {
@RequestMapping(path = "/api/alert/group", produces = {APPLICATION_JSON_VALUE})
public class AlertGroupConvergeController {
@Autowired
private AlertConvergeService alertConvergeService;
private AlertGroupConvergeService alertGroupConvergeService;
@PostMapping
@Operation(summary = "New Alarm Converge", description = "Added an alarm Converge")
public ResponseEntity<Message<Void>> addNewAlertConverge(@Valid @RequestBody AlertConverge alertConverge) {
alertConvergeService.validate(alertConverge, false);
alertConvergeService.addAlertConverge(alertConverge);
@Operation(summary = "New Alarm Group Converge", description = "Added an alarm Group Converge")
public ResponseEntity<Message<Void>> addNewAlertGroupConverge(@Valid @RequestBody AlertGroupConverge alertGroupConverge) {
alertGroupConvergeService.validate(alertGroupConverge, false);
alertGroupConvergeService.addAlertGroupConverge(alertGroupConverge);
return ResponseEntity.ok(Message.success("Add success"));
}
@PutMapping
@Operation(summary = "Modifying an Alarm Converge", description = "Modify an existing alarm Converge")
public ResponseEntity<Message<Void>> modifyAlertConverge(@Valid @RequestBody AlertConverge alertConverge) {
alertConvergeService.validate(alertConverge, true);
alertConvergeService.modifyAlertConverge(alertConverge);
@Operation(summary = "Modifying an Alarm Group Converge", description = "Modify an existing alarm Group Converge")
public ResponseEntity<Message<Void>> modifyAlertGroupConverge(@Valid @RequestBody AlertGroupConverge alertGroupConverge) {
alertGroupConvergeService.validate(alertGroupConverge, true);
alertGroupConvergeService.modifyAlertGroupConverge(alertGroupConverge);
return ResponseEntity.ok(Message.success("Modify success"));
}
@GetMapping(path = "/{id}")
@Operation(summary = "Querying Alarm Converge",
description = "You can obtain alarm Converge information based on the alarm Converge ID")
public ResponseEntity<Message<AlertConverge>> getAlertConverge(
@Operation(summary = "Querying Alarm Group Converge",
description = "You can obtain alarm Group Converge information based on the alarm Converge ID")
public ResponseEntity<Message<AlertGroupConverge>> getAlertGroupConverge(
@Parameter(description = "Alarm Converge ID", example = "6565463543") @PathVariable("id") long id) {
AlertConverge alertConverge = alertConvergeService.getAlertConverge(id);
AlertGroupConverge alertGroupConverge = alertGroupConvergeService.getAlertGroupConverge(id);
return Objects.isNull(alertConverge)
? ResponseEntity.ok(Message.fail(MONITOR_NOT_EXIST_CODE, "AlertConverge not exist."))
: ResponseEntity.ok(Message.success(alertConverge));
return Objects.isNull(alertGroupConverge)
? ResponseEntity.ok(Message.fail(MONITOR_NOT_EXIST_CODE, "Alert Group Converge not exist."))
: ResponseEntity.ok(Message.success(alertGroupConverge));
}
}
@@ -23,8 +23,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.HashSet;
import java.util.List;
import org.apache.hertzbeat.alert.service.AlertConvergeService;
import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
import org.apache.hertzbeat.alert.service.AlertGroupConvergeService;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
@@ -40,34 +40,34 @@ import org.springframework.web.bind.annotation.RestController;
*/
@Tag(name = "Alert Converge Batch API")
@RestController
@RequestMapping(path = "/api/alert/converges", produces = {APPLICATION_JSON_VALUE})
public class AlertConvergesController {
@RequestMapping(path = "/api/alert/groups", produces = {APPLICATION_JSON_VALUE})
public class AlertGroupConvergesController {
@Autowired
private AlertConvergeService alertConvergeService;
private AlertGroupConvergeService alertGroupConvergeService;
@GetMapping
@Operation(summary = "Query the alarm converge list",
description = "You can obtain the list of alarm converge by querying filter items")
public ResponseEntity<Message<Page<AlertConverge>>> getAlertConverges(
@Operation(summary = "Query the alarm group converge list",
description = "You can obtain the list of alarm group group converge by querying filter items")
public ResponseEntity<Message<Page<AlertGroupConverge>>> getAlertGroupConverges(
@Parameter(description = "Alarm Converge ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
@Parameter(description = "Search Name", example = "x") @RequestParam(required = false) String search,
@Parameter(description = "Sort field, default id", example = "id") @RequestParam(defaultValue = "id") String sort,
@Parameter(description = "Sort mode: asc: ascending, desc: descending", example = "desc") @RequestParam(defaultValue = "desc") String order,
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
@Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
Page<AlertConverge> alertConvergePage = alertConvergeService.getAlertConverges(ids, search, sort, order, pageIndex, pageSize);
return ResponseEntity.ok(Message.success(alertConvergePage));
Page<AlertGroupConverge> alertGroupConvergePage = alertGroupConvergeService.getAlertGroupConverges(ids, search, sort, order, pageIndex, pageSize);
return ResponseEntity.ok(Message.success(alertGroupConvergePage));
}
@DeleteMapping
@Operation(summary = "Delete alarm converge in batches",
description = "Delete alarm converge in batches based on the alarm converge ID list")
@Operation(summary = "Delete alarm group converge in batches",
description = "Delete alarm group converge in batches based on the alarm group converge ID list")
public ResponseEntity<Message<Void>> deleteAlertDefines(
@Parameter(description = "Alarm Converge IDs", example = "6565463543") @RequestParam(required = false) List<Long> ids
) {
if (ids != null && !ids.isEmpty()) {
alertConvergeService.deleteAlertConverges(new HashSet<>(ids));
alertGroupConvergeService.deleteAlertGroupConverges(new HashSet<>(ids));
}
return ResponseEntity.ok(Message.success());
}
@@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.controller;
import static org.apache.hertzbeat.common.constants.CommonConstants.MONITOR_NOT_EXIST_CODE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.Objects;
import org.apache.hertzbeat.alert.service.AlertInhibitService;
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Alarm Silence management API
*/
@Tag(name = "Alert Silence API")
@RestController
@RequestMapping(path = "/api/alert/inhibit", produces = {APPLICATION_JSON_VALUE})
public class AlertInhibitController {
@Autowired
private AlertInhibitService alertInhibitService;
@PostMapping
@Operation(summary = "New Alarm Silence", description = "Added an alarm Silence")
public ResponseEntity<Message<Void>> addNewAlertInhibit(@Valid @RequestBody AlertInhibit alertInhibit) {
alertInhibitService.validate(alertInhibit, false);
alertInhibitService.addAlertInhibit(alertInhibit);
return ResponseEntity.ok(Message.success("Add success"));
}
@PutMapping
@Operation(summary = "Modifying an Alarm Silence", description = "Modify an existing alarm Silence")
public ResponseEntity<Message<Void>> modifyAlertInhibit(@Valid @RequestBody AlertInhibit alertInhibit) {
alertInhibitService.validate(alertInhibit, true);
alertInhibitService.modifyAlertInhibit(alertInhibit);
return ResponseEntity.ok(Message.success("Modify success"));
}
@GetMapping(path = "/{id}")
@Operation(summary = "Querying Alarm Silence",
description = "You can obtain alarm Silence information based on the alarm Silence ID")
public ResponseEntity<Message<AlertInhibit>> getAlertInhibit(
@Parameter(description = "Alarm Silence ID", example = "6565463543") @PathVariable("id") long id) {
AlertInhibit alertInhibit = alertInhibitService.getAlertInhibit(id);
return Objects.isNull(alertInhibit)
? ResponseEntity.ok(Message.fail(MONITOR_NOT_EXIST_CODE, "AlertInhibit not exist."))
: ResponseEntity.ok(Message.success(alertInhibit));
}
}
@@ -0,0 +1,76 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.controller;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.HashSet;
import java.util.List;
import org.apache.hertzbeat.alert.service.AlertInhibitService;
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Silence the batch API for alarms
*/
@Tag(name = "Alert Silence Batch API")
@RestController
@RequestMapping(path = "/api/alert/inhibits", produces = {APPLICATION_JSON_VALUE})
public class AlertInhibitsController {
@Autowired
private AlertInhibitService alertInhibitService;
@GetMapping
@Operation(summary = "Query the alarm inhibit list",
description = "You can obtain the list of alarm inhibit by querying filter items")
public ResponseEntity<Message<Page<AlertInhibit>>> getAlertInhibits(
@Parameter(description = "Alarm Silence ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
@Parameter(description = "Search Name", example = "x") @RequestParam(required = false) String search,
@Parameter(description = "Sort field, default id", example = "id") @RequestParam(defaultValue = "id") String sort,
@Parameter(description = "Sort mode: asc: ascending, desc: descending", example = "desc") @RequestParam(defaultValue = "desc") String order,
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
@Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
Page<AlertInhibit> alertInhibitPage = alertInhibitService.getAlertInhibits(ids, search, sort, order, pageIndex, pageSize);
return ResponseEntity.ok(Message.success(alertInhibitPage));
}
@DeleteMapping
@Operation(summary = "Delete alarm inhibit in batches",
description = "Delete alarm inhibit in batches based on the alarm inhibit ID list")
public ResponseEntity<Message<Void>> deleteAlertDefines(
@Parameter(description = "Alarm Silence IDs", example = "6565463543") @RequestParam(required = false) List<Long> ids
) {
if (ids != null && !ids.isEmpty()) {
alertInhibitService.deleteAlertInhibits(new HashSet<>(ids));
}
Message<Void> message = Message.success();
return ResponseEntity.ok(message);
}
}
@@ -17,19 +17,19 @@
package org.apache.hertzbeat.alert.controller;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dto.GeneralCloudAlertReport;
import org.apache.hertzbeat.alert.service.AlertService;
import org.apache.hertzbeat.alert.service.ExternAlertService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
@@ -37,27 +37,79 @@ import org.springframework.web.bind.annotation.RestController;
*/
@Tag(name = "Extern Alarm Manage API")
@RestController
@RequestMapping(path = "/api/alerts/report", produces = {APPLICATION_JSON_VALUE})
@Slf4j
public class AlertReportController {
@Autowired
private AlertService alertService;
@PostMapping("/{cloud}")
@Operation(summary = "Interface for reporting external alarm information of cloud service")
public ResponseEntity<Message<Void>> addNewAlertReportFromCloud(@PathVariable("cloud") String cloudServiceName,
@RequestBody String alertReport) {
alertService.addNewAlertReportFromCloud(cloudServiceName, alertReport);
return ResponseEntity.ok(Message.success("Add report success"));
}
@PostMapping
@Operation(summary = "Interface for reporting external and general alarm information",
description = "The interface is used to report external and general alarm information")
public ResponseEntity<Message<Void>> addNewAlertReport(@RequestBody GeneralCloudAlertReport alertReport) {
alertReport.refreshAlertTime();
alertService.addNewAlertReport(alertReport);
return ResponseEntity.ok(Message.success("Add report success"));
private final List<ExternAlertService> externAlertServiceList;
public AlertReportController(List<ExternAlertService> externAlertServiceList) {
this.externAlertServiceList = externAlertServiceList;
}
@PostMapping("/api/alerts/report/{source}")
@Operation(summary = "Api for receive external alarm information")
public ResponseEntity<Message<Void>> receiveExternAlert(@PathVariable(value = "source") String source,
@RequestBody String content) {
log.info("Receive extern alert from source: {}, content: {}", source, content);
if (!StringUtils.hasText(source)) {
source = "default";
}
for (ExternAlertService externAlertService : externAlertServiceList) {
if (externAlertService.supportSource().equals(source)) {
try {
externAlertService.addExternAlert(content);
return ResponseEntity.ok(Message.success("Add extern alert success"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Message.fail(CommonConstants.FAIL_CODE,
"Add extern alert failed: " + e.getMessage()));
}
}
}
log.warn("Not support extern alert from source: {}", source);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Message.fail(CommonConstants.FAIL_CODE, "Not support the " + source + " source alert"));
}
@PostMapping("/api/alerts/report")
@Operation(summary = "Api for receive default external alarm information")
public ResponseEntity<Message<Void>> receiveDefaultExternAlert(@RequestBody String content) {
log.info("Receive default extern alert content: {}", content);
ExternAlertService externAlertService = externAlertServiceList.stream()
.filter(item -> "default".equals(item.supportSource())).findFirst().orElse(null);
if (externAlertService != null) {
try {
externAlertService.addExternAlert(content);
return ResponseEntity.ok(Message.success("Add extern alert success"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Message.fail(CommonConstants.FAIL_CODE,
"Add extern alert failed: " + e.getMessage()));
}
}
log.error("Not support default extern alert");
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Message.success("Not support the default source alert"));
}
@PostMapping("/api/v2/alerts")
@Operation(summary = "Api for receive external alarm information")
public ResponseEntity<Message<Void>> receivePrometheusServerAlert(@RequestBody String content) {
log.info("Receive prometheus server alert, content: {}", content);
ExternAlertService externAlertService = externAlertServiceList.stream()
.filter(item -> "prometheus".equals(item.supportSource())).findFirst().orElse(null);
if (externAlertService != null) {
try {
externAlertService.addExternAlert(content);
return ResponseEntity.ok(Message.success("Add extern alert success"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Message.fail(CommonConstants.FAIL_CODE,
"Add extern alert failed: " + e.getMessage()));
}
}
log.error("Not support prometheus extern alert");
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Message.success("Not support the prometheus source alert"));
}
}
@@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.alert.controller;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;
import org.apache.hertzbeat.alert.config.AlertSseManager;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* SSE controller for alert
*/
@RestController
@RequestMapping(path = "/api/alert/sse", produces = {TEXT_EVENT_STREAM_VALUE})
public class AlertSseController {
private final AlertSseManager emitterManager;
public AlertSseController(AlertSseManager emitterManager) {
this.emitterManager = emitterManager;
}
@GetMapping(path = "/subscribe")
public SseEmitter subscribe() {
Long clientId = SnowFlakeIdGenerator.generateId();
return emitterManager.createEmitter(clientId);
}
}
@@ -25,7 +25,8 @@ import java.util.HashSet;
import java.util.List;
import org.apache.hertzbeat.alert.dto.AlertSummary;
import org.apache.hertzbeat.alert.service.AlertService;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
@@ -50,47 +51,49 @@ public class AlertsController {
private AlertService alertService;
@GetMapping
@Operation(summary = "Get a list of alarm information based on query filter items", description = "according to the query filter items to obtain a list of alarm information")
public ResponseEntity<Message<Page<Alert>>> getAlerts(
@Parameter(description = "Alarm ID List", example = "6565466456") @RequestParam(required = false) List<Long> ids,
@Parameter(description = "Alarm monitor object ID", example = "6565463543") @RequestParam(required = false) Long monitorId,
@Parameter(description = "Alarm level", example = "6565463543") @RequestParam(required = false) Byte priority,
@Parameter(description = "Alarm Status", example = "6565463543") @RequestParam(required = false) Byte status,
@Parameter(description = "Alarm content fuzzy query", example = "linux") @RequestParam(required = false) String content,
@Parameter(description = "Sort field, default id", example = "name") @RequestParam(defaultValue = "id") String sort,
@Operation(summary = "Query Alarms")
public ResponseEntity<Message<Page<SingleAlert>>> getAlerts(
@Parameter(description = "Alarm Status", example = "resolved") @RequestParam(required = false) String status,
@Parameter(description = "Alarm content fuzzy query", example = "linux") @RequestParam(required = false) String search,
@Parameter(description = "Sort field, default id", example = "name") @RequestParam(defaultValue = "gmtUpdate") String sort,
@Parameter(description = "Sort Type", example = "desc") @RequestParam(defaultValue = "desc") String order,
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
@Parameter(description = "Number of list pagination", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
Page<Alert> alertPage = alertService.getAlerts(ids, monitorId, priority, status, content, sort, order, pageIndex, pageSize);
Page<SingleAlert> alertPage = alertService.getSingleAlerts(status, search, sort, order, pageIndex, pageSize);
return ResponseEntity.ok(Message.success(alertPage));
}
@DeleteMapping
@Operation(summary = "Delete alarms in batches", description = "according to the alarm ID list to delete the alarm information in batches")
@GetMapping("/group")
@Operation(summary = "Query Group Alarms")
public ResponseEntity<Message<Page<GroupAlert>>> getGroupAlerts(
@Parameter(description = "Alarm Status", example = "resolved") @RequestParam(required = false) String status,
@Parameter(description = "Alarm content fuzzy query", example = "linux") @RequestParam(required = false) String search,
@Parameter(description = "Sort field, default id", example = "name") @RequestParam(defaultValue = "gmtUpdate") String sort,
@Parameter(description = "Sort Type", example = "desc") @RequestParam(defaultValue = "desc") String order,
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
@Parameter(description = "Number of list pagination", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
Page<GroupAlert> alertPage = alertService.getGroupAlerts(status, search, sort, order, pageIndex, pageSize);
return ResponseEntity.ok(Message.success(alertPage));
}
@DeleteMapping("/group")
@Operation(summary = "Delete group alarms in batches", description = "according to the alarm ID list to delete the alarm information in batches")
public ResponseEntity<Message<Void>> deleteAlerts(
@Parameter(description = "Alarm List ID", example = "6565463543") @RequestParam(required = false) List<Long> ids) {
if (ids != null && !ids.isEmpty()) {
alertService.deleteAlerts(new HashSet<>(ids));
alertService.deleteGroupAlerts(new HashSet<>(ids));
}
Message<Void> message = Message.success();
return ResponseEntity.ok(message);
}
@DeleteMapping("/clear")
@Operation(summary = "Delete alarms in batches", description = "delete all alarm information")
public ResponseEntity<Message<Void>> clearAllAlerts() {
alertService.clearAlerts();
Message<Void> message = Message.success();
return ResponseEntity.ok(message);
}
@PutMapping(path = "/status/{status}")
@Operation(summary = "Batch modify alarm status, set read and unread", description = "Batch modify alarm status, set read and unread")
@PutMapping(path = "/group/status/{status}")
@Operation(summary = "Batch modify alarm status, set firing or resolved", description = "Batch modify alarm status, set firing or resolved")
public ResponseEntity<Message<Void>> applyAlertDefinesStatus(
@Parameter(description = "Alarm status value", example = "0") @PathVariable Byte status,
@Parameter(description = "Alarm status value", example = "resolved") @PathVariable String status,
@Parameter(description = "Alarm List IDS", example = "6565463543") @RequestParam(required = false) List<Long> ids) {
if (ids != null && status != null && !ids.isEmpty()) {
alertService.editAlertStatus(status, ids);
alertService.editGroupAlertStatus(status, ids);
}
Message<Void> message = Message.success();
return ResponseEntity.ok(message);
@@ -15,22 +15,23 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.controller;
package org.apache.hertzbeat.alert.controller;
import static org.apache.hertzbeat.common.constants.CommonConstants.FAIL_CODE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Optional;
import javax.validation.Valid;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeRule;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.manager.service.NoticeConfigService;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.alert.service.NoticeConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -82,9 +83,18 @@ public class NoticeConfigController {
@GetMapping(path = "/receivers")
@Operation(summary = "Get a list of message notification recipients based on query filter items",
description = "Get a list of message notification recipients based on query filter items")
public ResponseEntity<Message<List<NoticeReceiver>>> getReceivers(
@Parameter(description = "en: Recipient name,support fuzzy query", example = "tom") @RequestParam(required = false) final String name) {
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeReceivers(name)));
public ResponseEntity<Message<Page<NoticeReceiver>>> getReceivers(
@Parameter(description = "en: Recipient name,support fuzzy query", example = "tom") @RequestParam(required = false) final String name,
@Parameter(description = "en: List current page", example = "0") @RequestParam(defaultValue = "0") final int pageIndex,
@Parameter(description = "en: Number of list pages", example = "8") @RequestParam(defaultValue = "8") final int pageSize) {
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeReceivers(name, pageIndex, pageSize)));
}
@GetMapping(path = "/receivers/all")
@Operation(summary = "Get a list of all message notification recipients",
description = "Get a list of all message notification recipients")
public ResponseEntity<Message<List<NoticeReceiver>>> getAllReceivers() {
return ResponseEntity.ok(Message.success(noticeConfigService.getAllNoticeReceivers()));
}
@GetMapping(path = "/receiver/{id}")
@@ -129,9 +139,11 @@ public class NoticeConfigController {
@GetMapping(path = "/rules")
@Operation(summary = "Get a list of message notification policies based on query filter items",
description = "Get a list of message notification policies based on query filter items")
public ResponseEntity<Message<List<NoticeRule>>> getRules(
@Parameter(description = "en: Recipient name", example = "rule1") @RequestParam(required = false) final String name) {
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeRules(name)));
public ResponseEntity<Message<Page<NoticeRule>>> getRules(
@Parameter(description = "en: Recipient name", example = "rule1") @RequestParam(required = false) final String name,
@Parameter(description = "en: List current page", example = "0") @RequestParam(defaultValue = "0") final int pageIndex,
@Parameter(description = "en: Number of list pages", example = "8") @RequestParam(defaultValue = "8") final int pageSize) {
return ResponseEntity.ok(Message.success(noticeConfigService.getNoticeRules(name, pageIndex, pageSize)));
}
@GetMapping(path = "/rule/{id}")
@@ -176,12 +188,22 @@ public class NoticeConfigController {
@GetMapping(path = "/templates")
@Operation(summary = "Get a list of message notification templates based on query filter items",
description = "Get a list of message notification templates based on query filter items")
public ResponseEntity<Message<List<NoticeTemplate>>> getTemplates(
@Parameter(description = "Template name,support fuzzy query", example = "rule1") @RequestParam(required = false) final String name) {
List<NoticeTemplate> templatePage = noticeConfigService.getNoticeTemplates(name);
public ResponseEntity<Message<Page<NoticeTemplate>>> getTemplates(
@Parameter(description = "Template name,support fuzzy query", example = "rule1") @RequestParam(required = false) final String name,
@Parameter(description = "Whether it is a preset template", example = "true") @RequestParam(defaultValue = "true") final boolean preset,
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") final int pageIndex,
@Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") final int pageSize) {
Page<NoticeTemplate> templatePage = noticeConfigService.getNoticeTemplates(name, preset, pageIndex, pageSize);
return ResponseEntity.ok(Message.success(templatePage));
}
@GetMapping(path = "/templates/all")
@Operation(summary = "Get a list of all message notification templates",
description = "Get a list of all message notification templates")
public ResponseEntity<Message<List<NoticeTemplate>>> getAllTemplates() {
return ResponseEntity.ok(Message.success(noticeConfigService.getAllNoticeTemplates()));
}
@GetMapping(path = "/template/{id}")
@Operation(summary = "Get the notification template information based on the template ID",
description = "Get the notification template information based on the template ID")
@@ -18,12 +18,11 @@
package org.apache.hertzbeat.alert.dao;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
/**
* AlertDefine Dao
@@ -37,31 +36,16 @@ public interface AlertDefineDao extends JpaRepository<AlertDefine, Long>, JpaSpe
void deleteAlertDefinesByIdIn(Set<Long> alertDefineIds);
/**
* Query the default alarm thresholds based on the monitoring metrics type
* @param app monitoring type
* @param metric metrics
* Query the alarm define by type
* @param type alarm type
* @return alarm defines
*/
List<AlertDefine> queryAlertDefinesByAppAndMetricAndPresetTrueAndEnableTrue(String app, String metric);
List<AlertDefine> findAlertDefinesByTypeAndEnableTrue(String type);
/**
* Query app metric alert define
* @param app app
* @param metric metric
* @return alert define
* Query the alarm define by name
* @param name alarm name
* @return alarm define
*/
List<AlertDefine> queryAlertDefineByAppAndMetric(String app, String metric);
/**
* Query the alarm definition list associated with the monitoring ID
* @param monitorId monitor id
* @param app monitor type
* @param metrics metrics
* @return Alarm Definition List
*/
@Query("select define from AlertDefine define join AlertDefineMonitorBind bind on bind.alertDefineId = define.id "
+ "where bind.monitorId = :monitorId and define.app = :app and define.metric = :metrics and define.enable = true and define.preset = false")
List<AlertDefine> queryAlertDefinesByMonitor(@Param(value = "monitorId") Long monitorId,
@Param(value = "app") String app,
@Param(value = "metrics") String metrics);
Optional<AlertDefine> findAlertDefineByName(String name);
}
@@ -15,36 +15,31 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.dao;
package org.apache.hertzbeat.alert.dao;
import java.util.List;
import java.util.Set;
import org.apache.hertzbeat.common.entity.manager.TagMonitorBind;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
/**
* TagMonitorBindDao repository
* AlertConverge Dao
*/
public interface TagMonitorBindDao extends JpaRepository<TagMonitorBind, Long>, JpaSpecificationExecutor<TagMonitorBind> {
public interface AlertGroupConvergeDao extends JpaRepository<AlertGroupConverge, Long>, JpaSpecificationExecutor<AlertGroupConverge> {
/**
* delete tags bind by monitor id
* @param monitorId monitorId
* Delete group alarm converge based on the ID list
* @param ids alert converge id list
*/
void deleteTagMonitorBindsByMonitorId(Long monitorId);
@Modifying
void deleteAlertGroupConvergesByIdIn(Set<Long> ids);
/**
* delete tags bind by monitor id
* @param monitorIds monitor list
* Query the enable true group alarm converge list
* @return group alarm converge list
*/
void deleteTagMonitorBindsByMonitorIdIn(Set<Long> monitorIds);
/**
* count tags bind relation by tag id
* @param tagIds list of tagId
* @return count
*/
long countByTagIdIn(Set<Long> tagIds);
List<AlertGroupConverge> findAlertGroupConvergesByEnableIsTrue();
}
@@ -17,21 +17,29 @@
package org.apache.hertzbeat.alert.dao;
import java.util.List;
import java.util.Set;
import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
/**
* AlertConverge Dao
* AlertInhibit Dao
*/
public interface AlertConvergeDao extends JpaRepository<AlertConverge, Long>, JpaSpecificationExecutor<AlertConverge> {
public interface AlertInhibitDao extends JpaRepository<AlertInhibit, Long>, JpaSpecificationExecutor<AlertInhibit> {
/**
* Delete alarm converge based on the ID list
* @param convergeIds alert converge id list
* Delete alarm inhibit based on the ID list
*
* @param ids alert inhibit id list
*/
@Modifying
void deleteAlertConvergesByIdIn(Set<Long> convergeIds);
void deleteAlertInhibitsByIdIn(Set<Long> ids);
/**
* Query the enable true alarm inhibit list
* @return alarm inhibit list
*/
List<AlertInhibit> findAlertInhibitsByEnableIsTrue();
}
@@ -18,8 +18,8 @@
package org.apache.hertzbeat.alert.dao;
import java.util.List;
import java.util.Optional;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.Tag;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
@@ -36,12 +36,13 @@ public interface AlertMonitorDao extends JpaRepository<Monitor, Long>, JpaSpecif
* @return Monitor the list
*/
List<Monitor> findMonitorsByStatus(Byte status);
/**
* find monitor bind tags by monitorId
* @param monitorId monitorId
* @return bind tags
* Query the monitoring
* @param id id
* @return monitor
*/
@Query("select tag from Tag tag join TagMonitorBind bind on bind.tagId = tag.id where bind.monitorId = :monitorId")
List<Tag> findMonitorIdBindTags(@Param(value = "monitorId") Long monitorId);
@Query("select monitor from Monitor monitor where monitor.id = :id")
Optional<Monitor> findMonitorById(@Param(value = "id") Long id);
}
@@ -17,6 +17,7 @@
package org.apache.hertzbeat.alert.dao;
import java.util.List;
import java.util.Set;
import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -35,4 +36,10 @@ public interface AlertSilenceDao extends JpaRepository<AlertSilence, Long>, JpaS
*/
@Modifying
void deleteAlertSilencesByIdIn(Set<Long> silenceIds);
/**
* find enabled alert silence
* @return enabled alert silence list
*/
List<AlertSilence> findAlertSilencesByEnableTrue();
}
@@ -17,10 +17,9 @@
package org.apache.hertzbeat.alert.dao;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.hertzbeat.alert.dto.AlertPriorityNum;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
@@ -28,29 +27,37 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
/**
* Alert Database Operations
* Group Alert Database Operations
*/
public interface AlertDao extends JpaRepository<Alert, Long>, JpaSpecificationExecutor<Alert> {
public interface GroupAlertDao extends JpaRepository<GroupAlert, Long>, JpaSpecificationExecutor<GroupAlert> {
/**
* Query alert group by groupKey
* @param groupKey group key identifier
* @return alert group
*/
GroupAlert findByGroupKey(String groupKey);
/**
* Delete alerts based on ID list
* @param alertIds Alert ID List
* @param ids Alert ID List
*/
void deleteAlertsByIdIn(Set<Long> alertIds);
@Modifying
void deleteGroupAlertsByIdIn(HashSet<Long> ids);
/**
* Updates the alarm status based on the alarm ID-status value
* @param status status value
* @param ids alarm ids
* @param ids alarm ids
*/
@Modifying
@Query("update Alert set status = :status where id in :ids")
void updateAlertsStatus(@Param(value = "status") Byte status, @Param(value = "ids") List<Long> ids);
@Query("update GroupAlert set status = :status where id in :ids")
void updateGroupAlertsStatus(@Param(value = "status") String status, @Param(value = "ids") List<Long> ids);
/**
* Query the number of unhandled alarms of each alarm severity
* @return List of alerts num
* find group alerts by id list
* @param ids ids
* @return group alerts
*/
@Query("select new org.apache.hertzbeat.alert.dto.AlertPriorityNum(mo.priority, count(mo.id)) from Alert mo where mo.status = 0 group by mo.priority")
List<AlertPriorityNum> findAlertPriorityNum();
List<GroupAlert> findGroupAlertsByIdIn(HashSet<Long> ids);
}
@@ -15,9 +15,9 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.dao;
package org.apache.hertzbeat.alert.dao;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -15,10 +15,10 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.dao;
package org.apache.hertzbeat.alert.dao;
import java.util.List;
import org.apache.hertzbeat.common.entity.manager.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -15,9 +15,9 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.dao;
package org.apache.hertzbeat.alert.dao;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.dao;
import java.util.HashSet;
import java.util.List;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
/**
* Single Alert Database Operations
*/
public interface SingleAlertDao extends JpaRepository<SingleAlert, Long>, JpaSpecificationExecutor<SingleAlert> {
/**
* Query alert by fingerprint
* @param fingerprint alert fingerprint
* @return alert
*/
SingleAlert findByFingerprint(String fingerprint);
/**
* Query alerts by fingerprint list
* @param fingerprints alert fingerprint list
* @return alerts
*/
List<SingleAlert> findSingleAlertsByFingerprintIn(List<String> fingerprints);
/**
* Query alerts by status
* @param status status firing or resolved
* @return alerts
*/
List<SingleAlert> querySingleAlertsByStatus(String status);
/**
* Delete alerts based on ID list
* @param ids Alert ID List
*/
@Modifying
void deleteSingleAlertsByIdIn(HashSet<Long> ids);
/**
* Updates the alarm status based on the alarm ID-status value
* @param status status value
* @param ids alarm ids
*/
@Modifying
@Query("update SingleAlert set status = :status where id in :ids")
void updateSingleAlertsStatus(@Param(value = "status") String status, @Param(value = "ids") List<Long> ids);
/**
* delete alerts by fingerprint list
* @param firingAlerts fingerprint list
*/
@Modifying
void deleteSingleAlertsByFingerprintIn(List<String> firingAlerts);
}
@@ -21,9 +21,8 @@ import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.List;
import java.util.Map;
import lombok.Data;
import org.apache.hertzbeat.common.entity.manager.TagItem;
/**
* Data transfer object for alert configuration
@@ -34,26 +33,22 @@ import org.apache.hertzbeat.common.entity.manager.TagItem;
@JsonIgnoreProperties(ignoreUnknown = true)
@ExcelTarget(value = "AlertDefineDTO")
public class AlertDefineDTO {
@Excel(name = "App")
private String app;
@Excel(name = "Metric")
private String metric;
@Excel(name = "Field")
private String field;
@Excel(name = "Preset")
private Boolean preset;
@Excel(name = "Name")
private String name;
@Excel(name = "Type")
private String type;
@Excel(name = "Expr")
private String expr;
@Excel(name = "Priority")
private Byte priority;
@Excel(name = "Period")
private Integer period;
@Excel(name = "Times")
private Integer times;
@Excel(name = "Tags")
private List<TagItem> tags;
@Excel(name = "Enable")
private Boolean enable;
@Excel(name = "RecoverNotice")
private Boolean recoverNotice;
@Excel(name = "Labels")
private Map<String, String> labels;
@Excel(name = "Annotations")
private Map<String, String> annotations;
@Excel(name = "Template")
private String template;
@Excel(name = "Enable")
private Boolean enable;
}
@@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.dto;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* AlertManagerExternAlert
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AlertManagerExternAlert {
private String groupKey;
private String externalURL;
private String version;
private String status;
private Map<String, String> groupLabels;
private Map<String, String> commonLabels;
private Map<String, String> commonAnnotations;
private List<PrometheusExternAlert> alerts;
}
@@ -1,78 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Map;
import org.apache.hertzbeat.common.entity.dto.AlertReport;
/**
* Abstract parent of cloud service alarm information
* {@link AlertReport} - The method corresponds to the parameter of this class one by one. For details about the method, see this class
*/
public abstract class CloudAlertReportAbstract {
/**
* This parameter specifies the alarm name when a cloud alarm is converted to an internal alarm
*/
@JsonIgnore
public abstract String getAlertName();
/**
* Interval when a cloud alarm is converted to an internal alarm
*/
@JsonIgnore
public abstract Integer getAlertDuration();
/**
* Specifies the alarm time when a cloud alarm is converted to an internal alarm
*/
@JsonIgnore
public abstract long getAlertTime();
/**
* Severity of an alarm when a cloud alarm is converted to an internal alarm
*/
@JsonIgnore
public abstract Integer getPriority();
/**
* This parameter specifies the alarm type when a cloud alarm is converted to an internal alarm
*/
@JsonIgnore
public abstract Integer getReportType();
/**
* Alarm label information about a cloud alarm that is converted to an internal alarm
*/
@JsonIgnore
public abstract Map<String, String> getLabels();
/**
* Alarm labels when a cloud alarm is converted to an internal alarm
*/
@JsonIgnore
public abstract Map<String, String> getAnnotations();
/**
* This topic describes the alarm content when a cloud alarm is converted to an internal alarm
*/
@JsonIgnore
public abstract String getContent();
}
@@ -1,79 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.dto;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.alert.util.DateUtil;
import org.apache.hertzbeat.common.entity.dto.AlertReport;
/**
* Generic cloud alarm entity class
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class GeneralCloudAlertReport extends AlertReport {
/**
* Alarm date and time
*/
private String alertDateTime;
/**
* DATE TIME FORMAT
*/
private String dateTimeFormat;
/**
* You can refresh the timestamp of the alarm time with enhanced properties
*/
public void refreshAlertTime() {
if (getAlertTime() != 0L) {
return;
}
if (StringUtils.isNotBlank(alertDateTime)) {
Long timeStamp = null;
if (StringUtils.isNotBlank(dateTimeFormat)) {
Optional<Long> tsf = DateUtil.getTimeStampFromFormat(alertDateTime, dateTimeFormat);
boolean present = tsf.isPresent();
if (present) {
timeStamp = tsf.get();
}
}
if (timeStamp == null) {
Optional<Long> tsf = DateUtil.getTimeStampFromSomeFormats(alertDateTime);
boolean present = tsf.isPresent();
if (present) {
timeStamp = tsf.get();
}
}
if (timeStamp != null) {
setAlertTime(timeStamp);
return;
}
}
throw new RuntimeException("parse alarm time error");
}
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.pojo.dto;
package org.apache.hertzbeat.alert.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
@@ -32,7 +32,7 @@ import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmailNoticeSender {
public class MailServerConfig {
@NotNull(message = "Type cannot be empty")
private Integer type;
@@ -15,27 +15,34 @@
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.manager;
package org.apache.hertzbeat.alert.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import java.time.Instant;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* tag item
* Prometheus Alert Content Entity
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class TagItem {
public class PrometheusExternAlert {
private Map<String, String> labels;
private Map<String, String> annotations;
@Schema(title = "Tag Name")
@NotBlank
private String name;
private String status;
@Schema(title = "Tag Value")
private String value;
private Instant startsAt;
private Instant endsAt;
private String generatorURL;
}
@@ -1,227 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.hertzbeat.alert.util.DateUtil;
/**
*
* Tencent cloud alarm entity class
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Builder
public class TenCloudAlertReport extends CloudAlertReportAbstract implements Serializable {
@JsonProperty("sessionID")
private String sessionId;
private String alarmStatus;
private String alarmType;
private AlarmObjInfo alarmObjInfo;
private AlarmPolicyInfo alarmPolicyInfo;
private String firstOccurTime;
private int durationTime;
private String recoverTime;
/**
* Alarm Object information
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class AlarmObjInfo {
private String region;
private String namespace;
@JsonProperty("appID")
private String appId;
private String uin;
private Dimensions dimensions;
}
/**
* Uniform Resource ID information
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Dimensions {
@JsonProperty("unInstanceID")
private String unInstanceId;
@JsonProperty("objID")
private String objId;
}
/**
* Alarm policy information
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class AlarmPolicyInfo {
@JsonProperty("policyID")
private String policyId;
private String policyType;
private String policyName;
@JsonProperty("policyTypeCName")
private String policyTypeCname;
private Conditions conditions;
}
/**
* Parameters of indicator alarms
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Conditions {
// alarm metrics parameters
private String metricName;
private String metricShowName;
private String calcType;
private String calcValue;
private String calcUnit;
private String currentValue;
private String historyValue;
private String unit;
private String period;
private String periodNum;
private String alarmNotifyType;
private long alarmNotifyPeriod;
// alarm event parameters
private String productName;
private String productShowName;
private String eventName;
private String eventShowName;
}
@Override
public String getAlertName() {
return "TenCloud|腾讯云";
}
@Override
public Integer getAlertDuration() {
return this.durationTime;
}
@Override
public long getAlertTime() {
return DateUtil.getTimeStampFromFormat(getFirstOccurTime(), "yyyy-MM-dd HH:mm:ss")
.orElse(0L);
}
@Override
public Integer getPriority() {
return 1;
}
@Override
public Integer getReportType() {
return 1;
}
@Override
public Map<String, String> getLabels() {
return Map.of("app", "TenCloud");
}
@Override
public Map<String, String> getAnnotations() {
return Map.of("app", "TenCloud");
}
/**
* Transaction alarm
*/
private static final String EVENT = "event";
/**
* Indicator alarm
*/
private static final String METRIC = "metric";
/**
* If the following alarm types increase, the entity class can be divided into a parent class and multiple subclasses, and then the method can be implemented in the subclass
* Since there are only two, it will not be split for the time being
*/
@Override
public String getContent() {
StringBuilder contentBuilder = new StringBuilder();
// Check Type.
if (EVENT.equals(getAlarmType())) {
contentBuilder
.append("[")
.append("告警状态 | ")
.append("0".equals(alarmStatus) ? "恢复" : "告警")
.append("]\n")
.append("[")
.append("告警对象信息 | ")
.append(getAlarmObjInfo().getRegion() == null ? "" : "region:" + getAlarmObjInfo().getRegion())
.append(";").append("appId:").append(getAlarmObjInfo().getAppId())
.append(";").append("uni:").append(getAlarmObjInfo().getUin())
.append(";").append("unInstanceId:").append(getAlarmObjInfo().getDimensions().getUnInstanceId())
.append("]\n")
.append("[")
.append("告警策略组信息 | ")
.append("名称:").append(getAlarmPolicyInfo().getPolicyName())
.append(";")
.append("策略类型展示名称:").append(getAlarmPolicyInfo().getConditions().getProductName())
.append(",").append(getAlarmPolicyInfo().getConditions().getProductShowName())
.append(";")
.append("事件告警名称:").append(getAlarmPolicyInfo().getConditions().getEventName())
.append(",").append(getAlarmPolicyInfo().getConditions().getEventShowName())
.append("]");
} else if (METRIC.equals(getAlarmType())) {
contentBuilder
.append("[")
.append("告警对象:")
.append(getAlarmObjInfo().getRegion() == null ? "" : getAlarmObjInfo().getRegion())
.append(getAlarmObjInfo().getRegion() == null ? "" : "|")
.append(getAlarmObjInfo().getNamespace())
.append("]")
.append("[")
.append("告警内容:")
.append(getAlarmPolicyInfo().getPolicyTypeCname()).append("|")
.append(getAlarmPolicyInfo().getConditions().getMetricShowName()).append("|")
.append(getAlarmPolicyInfo().getConditions().getMetricName())
.append(getAlarmPolicyInfo().getConditions().getCalcType())
.append(getAlarmPolicyInfo().getConditions().getCalcValue())
.append(getAlarmPolicyInfo().getConditions().getCalcUnit())
.append("]")
.append("[")
.append("当前数据")
.append(getAlarmPolicyInfo().getConditions().getCurrentValue())
.append(getAlarmPolicyInfo().getConditions().getCalcUnit())
.append("]");
}
return contentBuilder.toString();
}
}
@@ -0,0 +1,128 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*
* Tencent cloud alarm entity class
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TencentCloudExternAlert {
@JsonProperty("sessionID")
private String sessionId;
private String alarmStatus;
private String alarmType;
private AlarmObjInfo alarmObjInfo;
private AlarmPolicyInfo alarmPolicyInfo;
private String firstOccurTime;
private int durationTime;
private String recoverTime;
/**
* Alarm Object information
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class AlarmObjInfo {
private String region;
private String namespace;
@JsonProperty("appID")
private String appId;
private String uin;
private Dimensions dimensions;
}
/**
* Uniform Resource ID information
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Dimensions {
@JsonProperty("unInstanceID")
private String unInstanceId;
@JsonProperty("objID")
private String objId;
}
/**
* Alarm policy information
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class AlarmPolicyInfo {
@JsonProperty("policyID")
private String policyId;
private String policyType;
private String policyName;
@JsonProperty("policyTypeCName")
private String policyTypeCname;
private Conditions conditions;
}
/**
* Parameters of indicator alarms
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Conditions {
// alarm metrics parameters
private String metricName;
private String metricShowName;
private String calcType;
private String calcValue;
private String calcUnit;
private String currentValue;
private String historyValue;
private String unit;
private String period;
private String periodNum;
private String alarmNotifyType;
private long alarmNotifyPeriod;
// alarm event parameters
private String productName;
private String productShowName;
private String eventName;
private String eventShowName;
}
/**
* Transaction alarm
*/
private static final String EVENT = "event";
/**
* Indicator alarm
*/
private static final String METRIC = "metric";
}
@@ -1,52 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.enums;
import java.util.Arrays;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.hertzbeat.alert.dto.CloudAlertReportAbstract;
import org.apache.hertzbeat.alert.dto.TenCloudAlertReport;
/**
* Cloud server alarm enum
*/
@AllArgsConstructor
@Getter
public enum CloudServiceAlarmInformationEnum {
TencentCloud("tencloud", TenCloudAlertReport.class);
/**
* cloud service name
*/
private final String cloudServiceName;
/**
* cloud service body
*/
private final Class<? extends CloudAlertReportAbstract> cloudServiceAlarmInformationEntity;
public static CloudServiceAlarmInformationEnum getEnumFromCloudServiceName(String name) {
return Arrays.stream(CloudServiceAlarmInformationEnum.values())
.filter(cloudService -> cloudService.cloudServiceName.equals(name))
.findFirst()
.orElse(null);
}
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter;
package org.apache.hertzbeat.alert.notice;
import com.google.common.collect.Maps;
import java.util.List;
@@ -23,18 +23,16 @@ import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.AlerterWorkerPool;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeRule;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.common.queue.CommonDataQueue;
import org.apache.hertzbeat.manager.service.NoticeConfigService;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.apache.hertzbeat.manager.support.exception.IgnoreException;
import org.apache.hertzbeat.alert.config.AlertSseManager;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.alert.service.NoticeConfigService;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.plugin.PostAlertPlugin;
import org.apache.hertzbeat.plugin.Plugin;
import org.apache.hertzbeat.plugin.runner.PluginRunner;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
/**
@@ -42,40 +40,28 @@ import org.springframework.stereotype.Component;
*/
@Component
@Slf4j
public class DispatcherAlarm implements InitializingBean {
private static final int DISPATCH_THREADS = 3;
public class AlertNoticeDispatch {
private final AlerterWorkerPool workerPool;
private final CommonDataQueue dataQueue;
private final NoticeConfigService noticeConfigService;
private final AlertStoreHandler alertStoreHandler;
private final Map<Byte, AlertNotifyHandler> alertNotifyHandlerMap;
private final PluginRunner pluginRunner;
private final AlertSseManager emitterManager;
public DispatcherAlarm(AlerterWorkerPool workerPool,
CommonDataQueue dataQueue,
NoticeConfigService noticeConfigService,
AlertStoreHandler alertStoreHandler,
List<AlertNotifyHandler> alertNotifyHandlerList, PluginRunner pluginRunner) {
public AlertNoticeDispatch(AlerterWorkerPool workerPool,
NoticeConfigService noticeConfigService,
AlertStoreHandler alertStoreHandler,
List<AlertNotifyHandler> alertNotifyHandlerList, PluginRunner pluginRunner, AlertSseManager emitterManager) {
this.workerPool = workerPool;
this.dataQueue = dataQueue;
this.noticeConfigService = noticeConfigService;
this.alertStoreHandler = alertStoreHandler;
this.pluginRunner = pluginRunner;
alertNotifyHandlerMap = Maps.newHashMapWithExpectedSize(alertNotifyHandlerList.size());
this.emitterManager = emitterManager;
alertNotifyHandlerList.forEach(r -> alertNotifyHandlerMap.put(r.type(), r));
}
@Override
public void afterPropertiesSet() {
// Start alarm distribution
DispatchTask dispatchTask = new DispatchTask();
for (int i = 0; i < DISPATCH_THREADS; i++) {
workerPool.executeJob(dispatchTask);
}
}
/**
* send alert msg to receiver
*
@@ -83,7 +69,7 @@ public class DispatcherAlarm implements InitializingBean {
* @param alert alert msg
* @return send success or failed
*/
public boolean sendNoticeMsg(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) {
public boolean sendNoticeMsg(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
if (receiver == null || receiver.getType() == null) {
log.warn("DispatcherAlarm-sendNoticeMsg params is empty alert:[{}], receiver:[{}]", alert, receiver);
return false;
@@ -115,53 +101,34 @@ public class DispatcherAlarm implements InitializingBean {
return noticeConfigService.getOneTemplateById(id);
}
private Optional<List<NoticeRule>> matchNoticeRulesByAlert(Alert alert) {
private Optional<List<NoticeRule>> matchNoticeRulesByAlert(GroupAlert alert) {
return Optional.ofNullable(noticeConfigService.getReceiverFilterRule(alert));
}
private class DispatchTask implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Alert alert = dataQueue.pollAlertsData();
if (alert != null) {
// Determining alarm type storage
alertStoreHandler.store(alert);
// Notice distribution
sendNotify(alert);
// Execute the plugin if enable (Compatible with old version plugins, will be removed in later versions)
pluginRunner.pluginExecute(Plugin.class, plugin -> plugin.alert(alert));
// Execute the plugin if enable with params
pluginRunner.pluginExecute(PostAlertPlugin.class, (afterAlertPlugin, pluginContext) -> afterAlertPlugin.execute(alert, pluginContext));
}
} catch (IgnoreException ignored) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error(e.getMessage());
} catch (Exception exception) {
log.error(exception.getMessage(), exception);
}
}
}
private void sendNotify(Alert alert) {
matchNoticeRulesByAlert(alert).ifPresent(noticeRules -> noticeRules.forEach(rule -> {
workerPool.executeNotify(() -> {
rule.getReceiverId()
.forEach(receiverId -> {
try {
sendNoticeMsg(getOneReceiverById(receiverId),
getOneTemplateById(rule.getTemplateId()), alert);
} catch (AlertNoticeException e) {
log.warn("DispatchTask sendNoticeMsg error, message: {}", e.getMessage());
}
});
});
}));
public void dispatchAlarm(GroupAlert groupAlert) {
if (groupAlert != null) {
// Determining alarm type storage
GroupAlert storedGroupAlert = alertStoreHandler.store(groupAlert);
// Notice distribution
sendNotify(storedGroupAlert);
// Execute the plugin if enable (Compatible with old version plugins, will be removed in later versions)
pluginRunner.pluginExecute(Plugin.class, plugin -> plugin.alert(storedGroupAlert));
// Execute the plugin if enable with params
pluginRunner.pluginExecute(PostAlertPlugin.class, (afterAlertPlugin, pluginContext) -> afterAlertPlugin.execute(storedGroupAlert, pluginContext));
// Send alert to the sse client
emitterManager.broadcast(JsonUtil.toJson(storedGroupAlert));
}
}
private void sendNotify(GroupAlert alert) {
matchNoticeRulesByAlert(alert).ifPresent(noticeRules -> noticeRules.forEach(rule -> workerPool.executeNotify(() -> rule.getReceiverId()
.forEach(receiverId -> {
try {
sendNoticeMsg(getOneReceiverById(receiverId),
getOneTemplateById(rule.getTemplateId()), alert);
} catch (AlertNoticeException e) {
log.warn("DispatchTask sendNoticeMsg error, message: {}", e.getMessage());
}
}))));
}
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.support.exception;
package org.apache.hertzbeat.alert.notice;
/**
* alert notice send failed
@@ -15,12 +15,11 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter;
package org.apache.hertzbeat.alert.notice;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
/**
* AlertNotifyHandler interface
@@ -34,7 +33,7 @@ public interface AlertNotifyHandler {
* @param alert Alarm information
* @throws AlertNoticeException when send receiver error
*/
void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) throws AlertNoticeException;
void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException;
/**
* notification type
@@ -15,9 +15,9 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter;
package org.apache.hertzbeat.alert.notice;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
/**
* Alarm persistence
@@ -28,7 +28,9 @@ public interface AlertStoreHandler {
* Persistent alarm records
* It is necessary to associate and assign values
* to the alert tag information tags while persisting.
*
* @param alert alarm information
* @return groupAlert
*/
void store(Alert alert);
GroupAlert store(GroupAlert alert);
}
@@ -15,29 +15,25 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import freemarker.cache.StringTemplateLoader;
import freemarker.core.TemplateClassResolver;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.AlerterProperties;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.support.event.SystemConfigChangeEvent;
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
import org.apache.hertzbeat.manager.component.alerter.AlertNotifyHandler;
import org.apache.hertzbeat.alert.notice.AlertNotifyHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.client.RestTemplate;
@@ -49,15 +45,14 @@ import org.springframework.web.client.RestTemplate;
abstract class AbstractAlertNotifyHandlerImpl implements AlertNotifyHandler {
private static final String NUMBER_FORMAT = "0";
protected static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
protected ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
@Resource
@Autowired
protected RestTemplate restTemplate;
@Resource
@Autowired
protected AlerterProperties alerterProperties;
protected String renderContent(NoticeTemplate noticeTemplate, Alert alert) throws TemplateException, IOException {
protected String renderContent(NoticeTemplate noticeTemplate, GroupAlert alert) throws TemplateException, IOException {
StringTemplateLoader stringLoader = new StringTemplateLoader();
freemarker.template.Template templateRes;
Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
@@ -65,51 +60,14 @@ abstract class AbstractAlertNotifyHandlerImpl implements AlertNotifyHandler {
cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
Map<String, Object> model = new HashMap<>(16);
model.put("title", bundle.getString("alerter.notify.title"));
if (alert.getTags() != null) {
String monitorId = alert.getTags().get(CommonConstants.TAG_MONITOR_ID);
if (monitorId != null) {
model.put("monitorId", monitorId);
}
String monitorName = alert.getTags().get(CommonConstants.TAG_MONITOR_NAME);
if (monitorName != null) {
model.put("monitorName", monitorName);
}
String monitorHost = alert.getTags().get(CommonConstants.TAG_MONITOR_HOST);
if (monitorHost != null) {
model.put("monitorHost", monitorHost);
}
String thresholdId = alert.getTags().get(CommonConstants.TAG_THRESHOLD_ID);
if (thresholdId != null) {
model.put("thresholdId", thresholdId);
}
}
model.put("alarmId", alert.getId());
model.put("status", alert.getStatus());
model.put("monitorIdLabel", bundle.getString("alerter.notify.monitorId"));
model.put("monitorNameLabel", bundle.getString("alerter.notify.monitorName"));
model.put("monitorHostLabel", bundle.getString("alerter.notify.monitorHost"));
model.put("target", alert.getTarget());
model.put("targetLabel", bundle.getString("alerter.notify.target"));
model.put("priorityLabel", bundle.getString("alerter.notify.priority"));
model.put("priority", bundle.getString("alerter.priority." + alert.getPriority()));
model.put("priorityValue", alert.getPriority());
model.put("triggerTimeLabel", bundle.getString("alerter.notify.triggerTime"));
model.put("triggerTime", DTF.format(Instant.ofEpochMilli(alert.getLastAlarmTime()).atZone(ZoneId.systemDefault()).toLocalDateTime()));
if (CommonConstants.ALERT_STATUS_CODE_RESTORED == alert.getStatus()) {
model.put("restoreTimeLabel", bundle.getString("alerter.notify.restoreTime"));
model.put("restoreTime", DTF.format(Instant.ofEpochMilli(alert.getFirstAlarmTime()).atZone(ZoneId.systemDefault()).toLocalDateTime()));
}
model.put("timesLabel", bundle.getString("alerter.notify.times"));
model.put("times", alert.getTimes());
model.put("contentLabel", bundle.getString("alerter.notify.content"));
model.put("content", alert.getContent());
model.put("tagsLabel", bundle.getString("alerter.notify.tags"));
model.put("tags", alert.getTags());
model.put("groupLabels", alert.getGroupLabels());
model.put("commonLabels", alert.getCommonLabels());
model.put("commonAnnotations", alert.getCommonAnnotations());
model.put("alerts", alert.getAlerts());
if (alerterProperties != null) {
model.put("consoleUrl", alerterProperties.getConsoleUrl());
}
model.put("consoleLabel", bundle.getString("alerter.notify.console"));
// TODO Single instance reuse cache considers multiple-threading issues
String templateName = "freeMakerTemplate";
stringLoader.putTemplate(templateName, noticeTemplate.getContent());
@@ -15,17 +15,16 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import java.util.ResourceBundle;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
@@ -41,17 +40,9 @@ final class AliYunAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
private final ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
// SMS notification
try {
String monitorName = null;
if (alert.getTags() != null) {
monitorName = alert.getTags().get(CommonConstants.TAG_MONITOR_NAME);
}
String[] params = new String[3];
params[0] = monitorName == null ? alert.getTarget() : monitorName;
params[1] = bundle.getString("alerter.priority." + alert.getPriority());
params[2] = alert.getContent();
// todo send aliyun sms
} catch (Exception e) {
throw new AlertNoticeException("[Sms Notify Error] " + e.getMessage());
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
@@ -0,0 +1,137 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.notice.impl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.GroupAlertDao;
import org.apache.hertzbeat.alert.dao.SingleAlertDao;
import org.apache.hertzbeat.alert.notice.AlertStoreHandler;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.springframework.stereotype.Component;
/**
* Alarm data persistence - landing in the database
*/
@Component
@RequiredArgsConstructor
@Slf4j
final class DbAlertStoreHandlerImpl implements AlertStoreHandler {
private final GroupAlertDao groupAlertDao;
private final SingleAlertDao singleAlertDao;
@Override
public GroupAlert store(GroupAlert groupAlert) {
if (groupAlert == null || groupAlert.getAlerts() == null || groupAlert.getAlerts().isEmpty()) {
log.error("The Group Alerts is empty, ignore store");
return groupAlert;
}
// 1. Find existing alert group
GroupAlert existGroupAlert = groupAlertDao.findByGroupKey(groupAlert.getGroupKey());
// 2. Process individual alerts
Set<String> alertFingerprints = new HashSet<>(8);
List<SingleAlert> originalAlerts = groupAlert.getAlerts();
List<SingleAlert> newAlerts = new ArrayList<>();
for (SingleAlert singleAlert : originalAlerts) {
SingleAlert existAlert = singleAlertDao.findByFingerprint(singleAlert.getFingerprint());
if (existAlert != null) {
// Update the existing alert with the ID and creation time from the database
singleAlert.setId(existAlert.getId());
singleAlert.setGmtCreate(existAlert.getGmtCreate());
// Status transition logic
if (CommonConstants.ALERT_STATUS_FIRING.equals(singleAlert.getStatus())) {
// If the alert is firing and the existing alert is not resolved, update the start time and trigger times
if (!CommonConstants.ALERT_STATUS_RESOLVED.equals(existAlert.getStatus())) {
singleAlert.setStartAt(existAlert.getStartAt());
int triggerTimes = Optional.ofNullable(existAlert.getTriggerTimes()).orElse(1)
+ Optional.ofNullable(singleAlert.getTriggerTimes()).orElse(1);
singleAlert.setTriggerTimes(triggerTimes);
}
} else if (CommonConstants.ALERT_STATUS_RESOLVED.equals(singleAlert.getStatus())) {
// If the alert is resolved, set the end time (if not already set) and copy other fields from the existing alert
if (singleAlert.getEndAt() == null) {
singleAlert.setEndAt(System.currentTimeMillis());
}
singleAlert.setStartAt(existAlert.getStartAt());
singleAlert.setActiveAt(existAlert.getActiveAt());
singleAlert.setTriggerTimes(existAlert.getTriggerTimes());
}
}
SingleAlert savedSingleAlert = singleAlertDao.save(singleAlert);
newAlerts.add(savedSingleAlert);
alertFingerprints.add(savedSingleAlert.getFingerprint());
}
groupAlert.setAlerts(newAlerts);
// 3. Process resolved alerts
if (existGroupAlert != null) {
List<String> existFingerprints = existGroupAlert.getAlertFingerprints();
if (existFingerprints != null) {
alertFingerprints.addAll(existFingerprints);
}
// Merge group information
groupAlert.setId(existGroupAlert.getId());
groupAlert.setGmtCreate(existGroupAlert.getGmtCreate());
// Merge other historical information to preserve
Map<String, String> existCommonLabels = existGroupAlert.getCommonLabels();
if (existCommonLabels != null) {
Map<String, String> commonLabels = groupAlert.getCommonLabels();
if (commonLabels != null) {
// filter common label in commonLabels and existCommonLabels
commonLabels = commonLabels.entrySet().stream()
.filter(entry -> existCommonLabels.containsKey(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
groupAlert.setCommonLabels(commonLabels);
}
}
Map<String, String> existCommonAnnotations = existGroupAlert.getCommonAnnotations();
if (existCommonAnnotations != null) {
Map<String, String> commonAnnotations = groupAlert.getCommonAnnotations();
if (commonAnnotations != null) {
// filter common annotation in commonAnnotations and existCommonAnnotations
commonAnnotations = commonAnnotations.entrySet().stream()
.filter(entry -> existCommonAnnotations.containsKey(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
groupAlert.setCommonAnnotations(commonAnnotations);
}
}
}
// 4. Save alert group
groupAlert.setAlertFingerprints(alertFingerprints.stream().toList());
GroupAlert savedGroupAlert = groupAlertDao.save(groupAlert);
savedGroupAlert.setAlerts(groupAlert.getAlerts());
return savedGroupAlert;
}
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@@ -23,11 +23,11 @@ import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.util.StrUtil;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -44,7 +44,7 @@ import org.springframework.stereotype.Component;
final class DingTalkRobotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
try {
DingTalkWebHookDto dingTalkWebHookDto = new DingTalkWebHookDto();
DingTalkWebHookDto.MarkdownDTO markdownDTO = new DingTalkWebHookDto.MarkdownDTO();
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.List;
@@ -23,10 +23,10 @@ import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -42,7 +42,7 @@ import org.springframework.stereotype.Component;
final class DiscordBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) throws AlertNoticeException {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException {
try {
var notifyBody = DiscordNotifyDTO.builder()
.embeds(List.of(EmbedDTO.builder()
@@ -93,7 +93,7 @@ final class DiscordBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerI
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
private static class DiscordResponseDTO {
public static class DiscordResponseDTO {
private String id;
private Integer type;
private String content;
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.mail.internet.MimeMessage;
@@ -24,15 +24,15 @@ import java.util.Properties;
import java.util.ResourceBundle;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.alert.dto.MailServerConfig;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.support.event.SystemConfigChangeEvent;
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
import org.apache.hertzbeat.manager.dao.GeneralConfigDao;
import org.apache.hertzbeat.manager.pojo.dto.EmailNoticeSender;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.EventListener;
import org.springframework.mail.javamail.JavaMailSender;
@@ -77,7 +77,7 @@ public class EmailAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
private ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) throws AlertNoticeException {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException {
try {
// get sender
JavaMailSenderImpl sender = (JavaMailSenderImpl) javaMailSender;
@@ -88,7 +88,7 @@ public class EmailAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
if (emailConfig != null && emailConfig.getContent() != null) {
// enable database configuration
String content = emailConfig.getContent();
EmailNoticeSender emailNoticeSenderConfig = objectMapper.readValue(content, EmailNoticeSender.class);
MailServerConfig emailNoticeSenderConfig = objectMapper.readValue(content, MailServerConfig.class);
if (emailNoticeSenderConfig.isEnable()) {
sender.setHost(emailNoticeSenderConfig.getEmailHost());
sender.setPort(emailNoticeSenderConfig.getEmailPort());
@@ -15,18 +15,18 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import java.util.Arrays;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -49,10 +49,11 @@ final class FlyBookAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
try {
String notificationContent = JsonUtil.toJson(renderContent(noticeTemplate, alert));
String cardMessage = createLarkMessage(receiver.getUserId(), notificationContent, alert.getPriority());
// todo priority custom the color
String cardMessage = createLarkMessage(receiver.getUserId(), notificationContent, (byte) 1);
String webHookUrl = alerterProperties.getFlyBookWebhookUrl() + receiver.getAccessToken();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
@@ -15,16 +15,16 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -49,7 +49,7 @@ public class GotifyAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
* @throws AlertNoticeException when send receiver error
*/
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) throws AlertNoticeException {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException {
try {
GotifyWebHookDto gotifyWebHookDto = new GotifyWebHookDto();
gotifyWebHookDto.setTitle(bundle.getString("alerter.notify.title"));
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import com.huaweicloud.sdk.core.auth.BasicCredentials;
import com.huaweicloud.sdk.smn.v2.SmnClient;
@@ -27,11 +27,11 @@ import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.springframework.stereotype.Component;
/**
@@ -40,13 +40,15 @@ import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@Slf4j
@Deprecated
final class HuaweiCloudSmnAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
private final ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
private final Map<String, SmnClient> smnClientMap = new ConcurrentHashMap<>();
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
// todo use rest api not sdk
try {
var smnClient = getSmnClient(receiver);
var request = new PublishMessageRequest()
@@ -15,15 +15,15 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -46,7 +46,7 @@ public class ServerChanAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
* @throws AlertNoticeException when send receiver error
*/
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) throws AlertNoticeException {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException {
try {
ServerChanAlertNotifyHandlerImpl.ServerChanWebHookDto serverChanWebHookDto = new ServerChanAlertNotifyHandlerImpl.ServerChanWebHookDto();
serverChanWebHookDto.setTitle(bundle.getString("alerter.notify.title"));
@@ -15,17 +15,17 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import java.util.Objects;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -44,7 +44,7 @@ final class SlackAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
private final RestTemplate restTemplate;
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) throws AlertNoticeException {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException {
try {
var slackNotify = SlackNotifyDTO.builder()
.text(renderContent(noticeTemplate, alert))
@@ -15,18 +15,17 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import java.util.ResourceBundle;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.alert.service.TencentSmsClient;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
import org.apache.hertzbeat.manager.service.TencentSmsClient;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
@@ -37,6 +36,7 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Slf4j
@ConditionalOnProperty("common.sms.tencent.app-id")
@Deprecated
final class SmsAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
private final TencentSmsClient tencentSmsClient;
@@ -44,17 +44,25 @@ final class SmsAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
private final ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) {
// SMS notification
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
// SMS notification todo use the rest api not sdk
try {
String monitorName = null;
if (alert.getTags() != null) {
monitorName = alert.getTags().get(CommonConstants.TAG_MONITOR_NAME);
String instance = null;
String priority = null;
String content = null;
if (alert.getCommonLabels() != null) {
instance = alert.getCommonLabels().get("instance");
priority = alert.getCommonLabels().get("priority");
content = alert.getCommonAnnotations().get("summary");
content = content == null ? alert.getCommonAnnotations().get("description") : content;
if (content == null) {
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
}
}
String[] params = new String[3];
params[0] = monitorName == null ? alert.getTarget() : monitorName;
params[1] = bundle.getString("alerter.priority." + alert.getPriority());
params[2] = alert.getContent();
params[0] = instance == null ? alert.getGroupKey() : instance;
params[1] = priority == null ? "unknown" : priority;
params[2] = content;
tencentSmsClient.sendMessage(params, new String[]{receiver.getPhone()});
} catch (Exception e) {
throw new AlertNoticeException("[Sms Notify Error] " + e.getMessage());
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -23,10 +23,10 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -42,7 +42,7 @@ import org.springframework.stereotype.Component;
final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) throws AlertNoticeException {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException {
try {
String url = String.format(alerterProperties.getTelegramWebhookUrl(), receiver.getTgBotToken());
TelegramBotNotifyDTO notifyBody = TelegramBotNotifyDTO.builder()
@@ -89,7 +89,7 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
@NoArgsConstructor
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
private static class TelegramBotNotifyResponse {
public static class TelegramBotNotifyResponse {
private boolean ok;
@JsonProperty("error_code")
private Integer errorCode;
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@@ -23,9 +23,9 @@ import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.http.HttpHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,7 +44,7 @@ final class WeChatAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
private static final String ACCESS_TOKEN = "access_token";
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
try {
String accessToken = getAccessToken();
String messageContent = constructMessageContent(receiver, noticeTemplate, alert);
@@ -73,14 +73,15 @@ final class WeChatAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
return accessToken;
}
private String constructMessageContent(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) {
private String constructMessageContent(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
// example: construct a text message content
JsonObject messageContent = new JsonObject();
messageContent.addProperty("msgtype", "text");
JsonObject textContent = new JsonObject();
// Here you can construct the message content based on the NoticeTemplate and Alert information
String alertMessage = String.format("警告:%s\n详情:%s", alert.getAlertDefineId(), alert.getContent());
// String alertMessage = String.format("警告:%s\n详情:%s", alert.getAlertDefineId(), alert.getContent());
String alertMessage = "Alert message content";
textContent.addProperty("content", alertMessage);
messageContent.add("text", textContent);
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
@@ -25,10 +25,10 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -62,7 +62,7 @@ public class WeComAppAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerIm
private final RestTemplate restTemplate;
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) throws AlertNoticeException {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException {
String corpId = receiver.getCorpId();
Integer agentId = receiver.getAgentId();
String appSecret = receiver.getAppSecret();
@@ -120,6 +120,9 @@ public class WeComAppAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerIm
return 10;
}
/**
* WeChat app request
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@@ -135,6 +138,9 @@ public class WeComAppAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerIm
private String accessToken;
}
/**
* WeChat app message
*/
@Data
@Builder
@AllArgsConstructor
@@ -171,6 +177,9 @@ public class WeComAppAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerIm
*/
private MarkdownDTO markdown;
/**
* markdown message
*/
@Data
protected static class MarkdownDTO {
/**
@@ -179,6 +188,9 @@ public class WeComAppAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerIm
private String content;
}
/**
* text message
*/
@Data
protected static class TextDTO {
/**
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@@ -27,11 +27,11 @@ import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.util.StrUtil;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -48,7 +48,7 @@ import org.springframework.stereotype.Component;
final class WeComRobotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
try {
WeWorkWebHookDto weWorkWebHookDTO = new WeWorkWebHookDto();
WeWorkWebHookDto.MarkdownDTO markdownDTO = new WeWorkWebHookDto.MarkdownDTO();
@@ -82,7 +82,7 @@ final class WeComRobotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerI
}
}
private WeWorkWebHookDto checkNeedAtNominator(NoticeReceiver receiver, Alert alert) {
private WeWorkWebHookDto checkNeedAtNominator(NoticeReceiver receiver, GroupAlert alert) {
if (StringUtils.isBlank(receiver.getPhone()) && StringUtils.isBlank(receiver.getUserId())) {
return null;
}
@@ -15,16 +15,13 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.component.alerter.impl;
package org.apache.hertzbeat.alert.notice.impl;
import java.util.Iterator;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.manager.support.exception.AlertNoticeException;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -40,14 +37,11 @@ import org.springframework.stereotype.Component;
final class WebHookAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, Alert alert) {
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// fix null pointer exception
filterInvalidTags(alert);
alert.setContent(escapeJsonStr(alert.getContent()));
// alert.setContent(escapeJsonStr(alert.getContent()));
String webhookJson = renderContent(noticeTemplate, alert);
webhookJson = webhookJson.replace(",\n }", "\n }");
@@ -68,25 +62,4 @@ final class WebHookAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
public byte type() {
return 2;
}
private void filterInvalidTags(Alert alert) {
if (alert.getTags() == null) {
return;
}
Iterator<Map.Entry<String, String>> iterator = alert.getTags().entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
if (StringUtils.isNoneBlank(entry.getKey(), entry.getValue())) {
continue;
}
iterator.remove();
}
// In order to beautify Freemarker template
if (alert.getTags().entrySet().size() <= 0L) {
alert.setTags(null);
}
}
}
@@ -17,57 +17,85 @@
package org.apache.hertzbeat.alert.reduce;
import java.util.HashMap;
import java.util.List;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.AlertMonitorDao;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.Tag;
import org.apache.hertzbeat.common.queue.CommonDataQueue;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.springframework.stereotype.Service;
/**
* reduce alarm and send alert data
* common reduce alarm worker
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class AlarmCommonReduce {
private final AlarmGroupReduce alarmGroupReduce;
private ThreadPoolExecutor workerExecutor;
private final AlarmSilenceReduce alarmSilenceReduce;
private final AlarmConvergeReduce alarmConvergeReduce;
private final CommonDataQueue dataQueue;
private final AlertMonitorDao alertMonitorDao;
public void reduceAndSendAlarm(Alert alert) {
alert.setTimes(1);
Map<String, String> tags = alert.getTags();
if (tags == null) {
tags = new HashMap<>(8);
alert.setTags(tags);
}
String monitorIdStr = tags.get(CommonConstants.TAG_MONITOR_ID);
if (monitorIdStr == null) {
log.debug("receiver extern alarm message: {}", alert);
} else {
long monitorId = Long.parseLong(monitorIdStr);
List<Tag> tagList = alertMonitorDao.findMonitorIdBindTags(monitorId);
for (Tag tag : tagList) {
if (!tags.containsKey(tag.getName())) {
tags.put(tag.getName(), tag.getTagValue());
}
}
}
// converge -> silence
if (alarmConvergeReduce.filterConverge(alert) && alarmSilenceReduce.filterSilence(alert)) {
dataQueue.sendAlertsData(alert);
}
public AlarmCommonReduce(AlarmGroupReduce alarmGroupReduce) {
initWorkExecutor();
this.alarmGroupReduce = alarmGroupReduce;
}
private void initWorkExecutor() {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("alerter-reduce-worker has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
.setNameFormat("alerter-reduce-worker-%d")
.build();
workerExecutor = new ThreadPoolExecutor(2,
2,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
}
public void reduceAndSendAlarm(SingleAlert alert) {
workerExecutor.execute(reduceAlarmTask(alert));
}
Runnable reduceAlarmTask(SingleAlert alert) {
return () -> {
try {
// Generate alert fingerprint
String fingerprint = generateAlertFingerprint(alert.getLabels());
alert.setFingerprint(fingerprint);
alarmGroupReduce.processGroupAlert(alert);
} catch (Exception e) {
log.error("Reduce alarm failed: {}", e.getMessage());
}
};
}
/**
* Generate fingerprint for alert to identify duplicates
* Fingerprint is based on labels excluding timestamp related fields
*/
private String generateAlertFingerprint(Map<String, String> labels) {
// Remove timestamp related fields
labels.remove("timestamp");
labels.remove("start_at");
labels.remove("active_at");
return labels.entrySet().stream()
.filter(e -> !"timestamp".equals(e.getKey())
&& !"starts_at".equals(e.getKey()) && !"actives_at".equals(e.getKey())
&& !"end_at".equals(e.getKey()) && !"ends_at".equals(e.getKey())
&& !"start_at".equals(e.getKey()) && !"active_at".equals(e.getKey()))
.sorted(Map.Entry.comparingByKey())
.map(e -> e.getKey() + ":" + e.getValue())
.collect(Collectors.joining(","));
}
}
@@ -1,168 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.reduce;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.hertzbeat.alert.dao.AlertConvergeDao;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.cache.CommonCacheService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
import org.apache.hertzbeat.common.entity.manager.TagItem;
import org.springframework.stereotype.Service;
/**
* alarm converge
*/
@Service
public class AlarmConvergeReduce {
private final AlertConvergeDao alertConvergeDao;
private final Map<Integer, Alert> converageAlertMap;
public AlarmConvergeReduce(AlertConvergeDao alertConvergeDao) {
this.alertConvergeDao = alertConvergeDao;
this.converageAlertMap = new ConcurrentHashMap<>(16);
}
/**
* currentAlert converge filter data
*
* @param currentAlert currentAlert
* @return true when not filter
*/
@SuppressWarnings("unchecked")
public boolean filterConverge(Alert currentAlert) {
// ignore monitor status auto recover notice
if ((currentAlert.getTags() != null && currentAlert.getTags().containsKey(CommonConstants.IGNORE))
|| currentAlert.getStatus() == CommonConstants.ALERT_STATUS_CODE_RESTORED) {
// restored alert
boolean isHasIgnore = false;
Map<String, String> tags = currentAlert.getTags();
if (Objects.requireNonNull(tags).containsKey(CommonConstants.IGNORE)) {
isHasIgnore = true;
tags.remove(CommonConstants.IGNORE);
}
int alertHash = generateAlertHash(CommonConstants.ALERT_PRIORITY_CODE_CRITICAL, tags);
converageAlertMap.remove(alertHash);
alertHash = generateAlertHash(CommonConstants.ALERT_PRIORITY_CODE_EMERGENCY, tags);
converageAlertMap.remove(alertHash);
alertHash = generateAlertHash(CommonConstants.ALERT_PRIORITY_CODE_WARNING, tags);
converageAlertMap.remove(alertHash);
if (isHasIgnore) {
tags.put(CommonConstants.IGNORE, CommonConstants.IGNORE);
}
return true;
}
CommonCacheService<String, Object> convergeCache = CacheFactory.getAlertConvergeCache();
List<AlertConverge> alertConvergeList = (List<AlertConverge>) convergeCache.get(CommonConstants.CACHE_ALERT_CONVERGE);
if (alertConvergeList == null) {
alertConvergeList = alertConvergeDao.findAll();
// matchAll is in the last
alertConvergeList.sort((item1, item2) -> {
if (item1.isMatchAll()) {
return 1;
} else if (item2.isMatchAll()) {
return -1;
} else {
return 0;
}
});
convergeCache.put(CommonConstants.CACHE_ALERT_CONVERGE, alertConvergeList);
}
for (AlertConverge alertConverge : alertConvergeList) {
if (!alertConverge.isEnable()) {
continue;
}
boolean match = alertConverge.isMatchAll();
if (!match) {
List<TagItem> tags = alertConverge.getTags();
if (currentAlert.getTags() != null && !currentAlert.getTags().isEmpty()) {
Map<String, String> alertTagMap = currentAlert.getTags();
match = tags.stream().anyMatch(item -> {
if (alertTagMap.containsKey(item.getName())) {
String tagValue = alertTagMap.get(item.getName());
if (tagValue == null && item.getValue() == null) {
return true;
} else {
return tagValue != null && tagValue.equals(item.getValue());
}
} else {
return false;
}
});
} else {
match = true;
}
if (match && alertConverge.getPriorities() != null && !alertConverge.getPriorities().isEmpty()) {
match = alertConverge.getPriorities().stream().anyMatch(item -> item != null && item == currentAlert.getPriority());
}
}
if (match) {
long evalInterval = alertConverge.getEvalInterval() * 1000L;
long now = System.currentTimeMillis();
if (evalInterval <= 0) {
return true;
}
int alertHash = generateAlertHash(currentAlert.getPriority(), currentAlert.getTags());
Alert preAlert = converageAlertMap.get(alertHash);
if (preAlert == null) {
currentAlert.setTimes(1);
currentAlert.setFirstAlarmTime(now);
currentAlert.setLastAlarmTime(now);
converageAlertMap.put(alertHash, currentAlert.clone());
return true;
} else {
if (now - preAlert.getFirstAlarmTime() < evalInterval) {
preAlert.setTimes(preAlert.getTimes() + 1);
preAlert.setLastAlarmTime(now);
return false;
} else {
currentAlert.setTimes(preAlert.getTimes());
if (preAlert.getTimes() == 1) {
currentAlert.setFirstAlarmTime(now);
} else {
currentAlert.setFirstAlarmTime(preAlert.getFirstAlarmTime());
}
currentAlert.setLastAlarmTime(now);
preAlert.setFirstAlarmTime(now);
preAlert.setLastAlarmTime(now);
preAlert.setTimes(1);
return true;
}
}
}
}
return true;
}
private int generateAlertHash(byte priority, Map<String, String> tags) {
List<String> keyList = tags.keySet().stream().filter(Objects::nonNull).sorted().toList();
List<String> valueList = tags.values().stream().filter(Objects::nonNull).sorted().toList();
return Objects.hash(priority)
+ Arrays.hashCode(keyList.toArray(new String[0]))
+ Arrays.hashCode(valueList.toArray(new String[0]));
}
}
@@ -0,0 +1,329 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.reduce;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.stream.Collectors;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.AlertGroupConvergeDao;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.springframework.stereotype.Component;
/**
* Alarm group reduce
* refer from prometheus, code with @cursor
*/
@Component
@Slf4j
public class AlarmGroupReduce {
/**
* Default initial group wait time 30s
*/
private static final long DEFAULT_GROUP_WAIT = 30 * 1000L;
/**
* Default group send interval 5min
*/
private static final long DEFAULT_GROUP_INTERVAL = 5 * 60 * 1000L;
/**
* Default repeat interval 4h
*/
private static final long DEFAULT_REPEAT_INTERVAL = 4 * 60 * 60 * 1000L;
/**
* Milliseconds per second
*/
private static final long MS_PER_SECOND = 1000L;
/**
* Check interval for group send ms
*/
private static final long CHECK_INTERVAL = 1000L;
private final AlarmInhibitReduce alarmInhibitReduce;
/**
* Group define rules
* key: rule name
* value: group rule configuration
*/
private final Map<String, AlertGroupConverge> groupDefines;
/**
* Alert cache grouped by labels
* key: groupDefineKey:groupKey
* value: GroupAlertCache
*/
private final Map<String, GroupAlertCache> groupCacheMap;
public AlarmGroupReduce(AlarmInhibitReduce alarmInhibitReduce, AlertGroupConvergeDao alertGroupConvergeDao) {
this.alarmInhibitReduce = alarmInhibitReduce;
this.groupDefines = new ConcurrentHashMap<>(8);
this.groupCacheMap = new ConcurrentHashMap<>(8);
List<AlertGroupConverge> groupConverges = alertGroupConvergeDao.findAlertGroupConvergesByEnableIsTrue();
refreshGroupDefines(groupConverges);
startCheckAndSendGroups();
}
private void startCheckAndSendGroups() {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("Check alarm groups calculate has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
.setNameFormat("alarm-group-calculate-%d")
.build();
ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
scheduledExecutor.scheduleAtFixedRate(() -> {
try {
long now = System.currentTimeMillis();
groupCacheMap.forEach((groupKey, cache) -> {
if (shouldSendGroup(cache, now)) {
sendGroupAlert(cache);
cache.setLastSendTime(now);
cache.getAlertFingerprints().clear();
}
});
} catch (Exception e) {
log.error("Check alarm groups calculate has exception.: {}", e.getMessage(), e);
}
}, 10000, CHECK_INTERVAL, java.util.concurrent.TimeUnit.MILLISECONDS);
}
/**
* Configure group define rules
* @param groupDefines map of group rule configurations
*/
public void refreshGroupDefines(List<AlertGroupConverge> groupDefines) {
this.groupDefines.clear();
groupDefines.forEach(define -> this.groupDefines.put(define.getName(), define));
}
/**
* Process single alert and group by defined rules
*/
public void processGroupAlert(SingleAlert alert) {
Map<String, String> labels = alert.getLabels();
if (labels == null || labels.isEmpty() || groupDefines.isEmpty()) {
sendSingleAlert(alert);
return;
}
// Process each group define rule
boolean matched = false;
for (Map.Entry<String, AlertGroupConverge> define : groupDefines.entrySet()) {
String defineName = define.getKey();
AlertGroupConverge ruleConfig = define.getValue();
// Check if alert has all required group labels
if (hasRequiredLabels(labels, ruleConfig.getGroupLabels())) {
matched = true;
processAlertByGroupDefine(alert, defineName, ruleConfig);
}
}
if (!matched) {
sendSingleAlert(alert);
}
}
private boolean hasRequiredLabels(Map<String, String> labels, List<String> requiredLabels) {
return requiredLabels.stream().allMatch(labels::containsKey);
}
private void processAlertByGroupDefine(SingleAlert alert, String defineName, AlertGroupConverge ruleConfig) {
// Extract group labels based on define
Map<String, String> extractedLabels = new HashMap<>();
for (String labelKey : ruleConfig.getGroupLabels()) {
extractedLabels.put(labelKey, alert.getLabels().get(labelKey));
}
// Generate group key
String groupKey = generateGroupKey(extractedLabels);
// Get or create group cache
GroupAlertCache cache = groupCacheMap.computeIfAbsent(groupKey, k -> {
GroupAlertCache newCache = new GroupAlertCache();
newCache.setGroupKey(groupKey);
newCache.setGroupLabels(extractedLabels);
newCache.setGroupDefineName(defineName);
newCache.setCreateTime(System.currentTimeMillis());
newCache.setAlertFingerprints(new ConcurrentHashMap<>(8));
return newCache;
});
String fingerprint = alert.getFingerprint();
// Check if this is a duplicate alert
SingleAlert existingAlert = cache.getAlertFingerprints().get(fingerprint);
if (existingAlert != null) {
// Update existing alert timestamp
alert.setStartAt(existingAlert.getStartAt());
cache.getAlertFingerprints().put(fingerprint, alert);
return;
}
// Add new alert
cache.getAlertFingerprints().put(fingerprint, alert);
if (shouldSendGroupImmediately(cache)) {
sendGroupAlert(cache);
cache.setLastSendTime(System.currentTimeMillis());
cache.getAlertFingerprints().clear();
}
}
private void sendGroupAlert(GroupAlertCache cache) {
if (cache.getAlertFingerprints().isEmpty()) {
return;
}
long now = System.currentTimeMillis();
String status = determineGroupStatus(cache.getAlertFingerprints().values());
// For firing alerts, check repeat interval
if (CommonConstants.ALERT_STATUS_FIRING.equals(status)) {
AlertGroupConverge ruleConfig = groupDefines.get(cache.getGroupDefineName());
long repeatInterval = ruleConfig.getRepeatInterval() != null
? ruleConfig.getRepeatInterval() * MS_PER_SECOND : DEFAULT_REPEAT_INTERVAL;
// Skip if within repeat interval
if (cache.getLastRepeatTime() > 0
&& now - cache.getLastRepeatTime() < repeatInterval) {
return;
}
cache.setLastRepeatTime(now);
}
GroupAlert groupAlert = GroupAlert.builder()
.groupKey(cache.getGroupKey())
.groupLabels(cache.getGroupLabels())
.commonLabels(extractCommonLabels(cache.getAlertFingerprints().values()))
.commonAnnotations(extractCommonAnnotations(cache.getAlertFingerprints().values()))
.alerts(new ArrayList<>(cache.getAlertFingerprints().values()))
.status(status)
.build();
alarmInhibitReduce.inhibitAlarm(groupAlert);
}
private boolean shouldSendGroup(GroupAlertCache cache, long now) {
AlertGroupConverge ruleConfig = groupDefines.get(cache.getGroupDefineName());
long groupWait = ruleConfig != null ? ruleConfig.getGroupWait() * MS_PER_SECOND : DEFAULT_GROUP_WAIT;
long groupInterval = ruleConfig != null ? ruleConfig.getGroupInterval() * MS_PER_SECOND : DEFAULT_GROUP_INTERVAL;
// First wait time reached
if (cache.getLastSendTime() == 0
&& now - cache.getCreateTime() >= groupWait) {
return true;
}
// Group interval time reached
return cache.getLastSendTime() > 0
&& now - cache.getLastSendTime() >= groupInterval;
}
private boolean shouldSendGroupImmediately(GroupAlertCache cache) {
// Check if all alerts are resolved
return cache.getAlertFingerprints().values().stream()
.allMatch(alert -> CommonConstants.ALERT_STATUS_RESOLVED.equals(alert.getStatus()));
}
private void sendSingleAlert(SingleAlert alert) {
// Wrap single alert as group alert
String groupKey = generateGroupKey(alert.getLabels());
GroupAlert groupAlert = GroupAlert.builder()
.groupKey(groupKey)
.groupLabels(alert.getLabels())
.commonLabels(alert.getLabels())
.commonAnnotations(alert.getAnnotations())
.alerts(Collections.singletonList(alert))
.status(alert.getStatus())
.build();
alarmInhibitReduce.inhibitAlarm(groupAlert);
}
private String generateGroupKey(Map<String, String> labels) {
return labels.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(e -> e.getKey() + ":" + e.getValue())
.collect(Collectors.joining(","));
}
private Map<String, String> extractCommonLabels(Collection<SingleAlert> alerts) {
// Extract common labels from all alerts
if (alerts.isEmpty()) {
return new HashMap<>(8);
}
Map<String, String> common = new HashMap<>(alerts.stream().findFirst().get().getLabels());
alerts.forEach(alert -> {
common.keySet().removeIf(key ->
!alert.getLabels().containsKey(key)
|| !common.get(key).equals(alert.getLabels().get(key)));
});
return common;
}
private Map<String, String> extractCommonAnnotations(Collection<SingleAlert> alerts) {
// Extract common annotations from all alerts
if (alerts.isEmpty()) {
return new HashMap<>(8);
}
Map<String, String> common = new HashMap<>(alerts.stream().findFirst().get().getAnnotations());
alerts.forEach(alert -> {
common.keySet().removeIf(key ->
!alert.getAnnotations().containsKey(key)
|| !common.get(key).equals(alert.getAnnotations().get(key)));
});
return common;
}
private String determineGroupStatus(Collection<SingleAlert> alerts) {
// If any alert is firing, group is firing
return alerts.stream()
.anyMatch(alert -> CommonConstants.ALERT_STATUS_FIRING.equals(alert.getStatus()))
? CommonConstants.ALERT_STATUS_FIRING : CommonConstants.ALERT_STATUS_RESOLVED;
}
@Data
private static class GroupAlertCache {
private String groupDefineName;
private String groupKey;
private Map<String, String> groupLabels;
private Map<String, SingleAlert> alertFingerprints = new ConcurrentHashMap<>(8);
private long createTime;
private long lastSendTime;
private long lastRepeatTime;
}
}
@@ -0,0 +1,318 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.reduce;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.AlerterProperties;
import org.apache.hertzbeat.alert.dao.AlertInhibitDao;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Collections;
import java.util.stream.Collectors;
import lombok.Data;
import lombok.AllArgsConstructor;
/**
* inhibit alarm
* refer from prometheus, code with @cursor
*/
@Component
@Slf4j
public class AlarmInhibitReduce {
/**
* Interval for checking and cleaning up expired source alerts
*/
private static final long CHECK_INTERVAL = 60_000L;
/**
* alarm silence
*/
private final AlarmSilenceReduce alarmSilenceReduce;
/**
* rule cache
*/
private final Map<Long, AlertInhibit> inhibitRules;
/**
* Cache for source alerts
* key: ruleId
* value: map of source alerts with their fingerprints
*/
private final Map<Long, Map<String, SourceAlertEntry>> sourceAlertCache;
/**
* Default TTL for source alerts (4 hours)
*/
private static long SOURCE_ALERT_TTL = 4 * 60 * 60 * 1000L;
public AlarmInhibitReduce(AlarmSilenceReduce alarmSilenceReduce, AlertInhibitDao alertInhibitDao
, AlerterProperties alerterProperties) {
this.alarmSilenceReduce = alarmSilenceReduce;
if (alerterProperties.getInhibit() != null && alerterProperties.getInhibit().getTtl() > 0) {
SOURCE_ALERT_TTL = alerterProperties.getInhibit().getTtl();
}
inhibitRules = new ConcurrentHashMap<>(8);
sourceAlertCache = new ConcurrentHashMap<>(8);
List<AlertInhibit> inhibits = alertInhibitDao.findAlertInhibitsByEnableIsTrue();
refreshInhibitRules(inhibits);
startScheduledCleanupCache();
}
private void startScheduledCleanupCache() {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("Scheduled clean up inhibit cache has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
.setNameFormat("inhibit-clean-up-%d")
.build();
ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
// Scheduled cleanup of all expired source alerts
scheduledExecutor.scheduleAtFixedRate(() -> {
try {
sourceAlertCache.values().forEach(this::cleanupExpiredEntries);
// Remove empty rule caches
sourceAlertCache.entrySet().removeIf(entry -> entry.getValue().isEmpty());
} catch (Exception e) {
log.error("Error during scheduled cleanup", e);
}
}, CHECK_INTERVAL, CHECK_INTERVAL, TimeUnit.MILLISECONDS);
}
/**
* Configure inhibit rules
* @param rules Inhibit rule list
*/
public void refreshInhibitRules(List<AlertInhibit> rules) {
if (rules == null) {
log.warn("Attempted to refresh inhibit rules with null list.");
return;
}
this.inhibitRules.clear();
rules.forEach(rule -> this.inhibitRules.put(rule.getId(), rule));
}
/**
* Process alert with inhibit rules
* If alert is inhibited, it will not be forwarded
* @param groupAlert Grouped and pending alerts to be processed
*/
public void inhibitAlarm(GroupAlert groupAlert) {
if (groupAlert == null) {
log.warn("Received null GroupAlert. Skipping processing.");
return;
}
try {
if (inhibitRules.isEmpty()) {
alarmSilenceReduce.silenceAlarm(groupAlert);
return;
}
// Process each individual alert
for (var alert : groupAlert.getAlerts()) {
for (AlertInhibit rule : inhibitRules.values()) {
if (isSourceAlert(alert, rule)) {
cacheSourceAlert(alert, rule);
}
}
}
// Filter out inhibited alerts
groupAlert.getAlerts().removeIf(this::shouldInhibit);
// Continue processing if there are remaining alerts
if (!groupAlert.getAlerts().isEmpty()) {
alarmSilenceReduce.silenceAlarm(groupAlert);
}
} catch (Exception e) {
log.error("Error inhibiting alarm for {}", groupAlert, e);
}
}
/**
* Check if alert matches inhibit rule source labels
* @param alert Single alert to be processed
* @param rule The rule of inhibition
*/
private boolean isSourceAlert(SingleAlert alert, AlertInhibit rule) {
if (alert == null || rule == null) {
log.warn("Received null alert or rule in isSourceAlert");
return false;
}
if (!CommonConstants.ALERT_STATUS_FIRING.equals(alert.getStatus())) {
return false;
}
return matchLabels(alert.getLabels(), rule.getSourceLabels());
}
/**
* Check if alert should be inhibited by any active source alerts
* @param alert Single alert to be processed
*/
private boolean shouldInhibit(SingleAlert alert) {
if (alert == null) {
log.warn("Received null alert in shouldInhibit");
return false;
}
if (CommonConstants.ALERT_STATUS_RESOLVED.equals(alert.getStatus())) {
return false;
}
for (AlertInhibit rule : inhibitRules.values()) {
if (!matchLabels(alert.getLabels(), rule.getTargetLabels())) {
continue;
}
List<SingleAlert> sourceAlerts = getActiveSourceAlerts(rule);
if (sourceAlerts.isEmpty()) {
continue;
}
for (SingleAlert source : sourceAlerts) {
if (matchEqualLabels(source, alert, rule.getEqualLabels())) {
return true;
}
}
}
return false;
}
/**
* Check if all required labels match
* @param alertLabels The label of the alarm
* @param requiredLabels Labels to be matched
*/
private boolean matchLabels(Map<String, String> alertLabels, Map<String, String> requiredLabels) {
if (alertLabels == null || requiredLabels == null) {
log.warn("Received null alertLabels or requiredLabels in matchLabels");
return false;
}
return requiredLabels.entrySet().stream()
.allMatch(entry -> entry.getValue().equals(alertLabels.get(entry.getKey())));
}
/**
* Check if equal labels have same values in both alerts
* @param source Alert used to suppress other alerts
* @param target Alert that may be suppressed
* @param equalLabels Need to be equal labels
*/
private boolean matchEqualLabels(SingleAlert source, SingleAlert target, List<String> equalLabels) {
if (source == null || target == null) {
log.warn("Received null source or target in matchEqualLabels");
return false;
}
if (equalLabels == null || equalLabels.isEmpty()) {
return true;
}
Map<String, String> sourceLabels = source.getLabels();
Map<String, String> targetLabels = target.getLabels();
return equalLabels.stream().allMatch(label -> {
String sourceValue = sourceLabels.get(label);
String targetValue = targetLabels.get(label);
return sourceValue != null && sourceValue.equals(targetValue);
});
}
/**
* Cache source alert for inhibit rule
* @param alert Single alert to be processed
* @param rule The rule of inhibition
*/
private void cacheSourceAlert(SingleAlert alert, AlertInhibit rule) {
if (alert == null || rule == null) {
log.warn("Received null alert or rule in cacheSourceAlert");
return;
}
Map<String, SourceAlertEntry> ruleCache = sourceAlertCache.computeIfAbsent(
rule.getId(),
k -> new ConcurrentHashMap<>()
);
SourceAlertEntry entry = new SourceAlertEntry(
alert,
System.currentTimeMillis(),
System.currentTimeMillis() + SOURCE_ALERT_TTL
);
ruleCache.put(alert.getFingerprint(), entry);
cleanupExpiredEntries(ruleCache);
}
/**
* Get active source alerts for inhibit rule
* @param rule The rule of inhibition
* @return List of active source alerts
*/
private List<SingleAlert> getActiveSourceAlerts(AlertInhibit rule) {
if (rule == null) {
log.warn("Received null rule in getActiveSourceAlerts");
return Collections.emptyList();
}
Map<String, SourceAlertEntry> ruleCache = sourceAlertCache.get(rule.getId());
if (ruleCache == null || ruleCache.isEmpty()) {
return Collections.emptyList();
}
long now = System.currentTimeMillis();
return ruleCache.values().stream()
.filter(entry -> entry.getExpiryTime() > now)
.map(SourceAlertEntry::getAlert)
.collect(Collectors.toList());
}
/**
* Remove expired entries from cache
* @param cache Source alert cache entry map
*/
private void cleanupExpiredEntries(Map<String, SourceAlertEntry> cache) {
if (cache == null) {
log.warn("Received null cache in cleanupExpiredEntries");
return;
}
long now = System.currentTimeMillis();
cache.entrySet().removeIf(entry -> entry.getValue().getExpiryTime() <= now);
}
/**
* Source alert cache entry
*/
@Data
@AllArgsConstructor
private static class SourceAlertEntry {
private final SingleAlert alert;
private final long createTime;
private final long expiryTime;
}
}
@@ -23,103 +23,89 @@ import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.hertzbeat.alert.dao.AlertSilenceDao;
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.cache.CommonCacheService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
import org.apache.hertzbeat.common.entity.manager.TagItem;
import org.springframework.stereotype.Service;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.springframework.stereotype.Component;
/**
* silence alarm
* Silence alert handler
* Refer from prometheus alert silencing mechanism
*/
@Service
@Component
@RequiredArgsConstructor
public class AlarmSilenceReduce {
private final AlertSilenceDao alertSilenceDao;
private final AlertNoticeDispatch dispatcherAlarm;
/**
* alert silence filter data
* @param alert alert
* @return true when not filter
* Process alert with silence rules
* If alert matches any active silence rule, it will be silenced
* @param groupAlert The alert to be processed
*/
@SuppressWarnings("unchecked")
public boolean filterSilence(Alert alert) {
CommonCacheService<String, Object> silenceCache = CacheFactory.getAlertSilenceCache();
List<AlertSilence> alertSilenceList = (List<AlertSilence>) silenceCache.get(CommonConstants.CACHE_ALERT_SILENCE);
public void silenceAlarm(GroupAlert groupAlert) {
List<AlertSilence> alertSilenceList = CacheFactory.getAlertSilenceCache();
if (alertSilenceList == null) {
alertSilenceList = alertSilenceDao.findAll();
silenceCache.put(CommonConstants.CACHE_ALERT_SILENCE, alertSilenceList);
alertSilenceList = alertSilenceDao.findAlertSilencesByEnableTrue();
CacheFactory.setAlertSilenceCache(alertSilenceList);
}
// Check each silence rule
for (AlertSilence alertSilence : alertSilenceList) {
if (!alertSilence.isEnable()) {
continue;
}
// if match the silence rule, return
// Check if alert matches silence rule
boolean match = alertSilence.isMatchAll();
if (!match) {
List<TagItem> tags = alertSilence.getTags();
if (alert.getTags() != null && !alert.getTags().isEmpty()) {
Map<String, String> alertTagMap = alert.getTags();
match = tags.stream().anyMatch(item -> {
if (alertTagMap.containsKey(item.getName())) {
String tagValue = alertTagMap.get(item.getName());
if (tagValue == null && item.getValue() == null) {
return true;
} else {
return tagValue != null && tagValue.equals(item.getValue());
}
} else {
return false;
}
});
} else {
match = true;
}
if (match && alertSilence.getPriorities() != null && !alertSilence.getPriorities().isEmpty()) {
match = alertSilence.getPriorities().stream().anyMatch(item -> item != null && item == alert.getPriority());
}
if (!match && groupAlert.getGroupLabels() != null) {
Map<String, String> labels = alertSilence.getLabels();
Map<String, String> alertLabels = groupAlert.getGroupLabels();
match = labels.entrySet().stream().anyMatch(item ->
alertLabels.containsKey(item.getKey()) && item.getValue().equals(alertLabels.get(item.getKey())));
}
if (match) {
LocalDateTime now = LocalDateTime.now();
if (alertSilence.getType() == 0) {
// once time
return checkAndSave(LocalDateTime.now(), alertSilence);
// One-time silence rule
if (checkAndSave(now, alertSilence)) {
continue;
}
// Alert is silenced
return;
} else if (alertSilence.getType() == 1) {
// cyc time
int currentDayOfWeek = LocalDateTime.now().toLocalDate().getDayOfWeek().getValue();
if (alertSilence.getDays() != null && !alertSilence.getDays().isEmpty()) {
boolean dayMatch = alertSilence.getDays().stream().anyMatch(item -> item == currentDayOfWeek);
if (dayMatch) {
return checkAndSave(LocalDateTime.now(), alertSilence);
}
// Cyclic silence rule
int currentDayOfWeek = now.getDayOfWeek().getValue();
if (alertSilence.getDays() != null && alertSilence.getDays().contains((byte) currentDayOfWeek)
&& !checkAndSave(now, alertSilence)) {
// Alert is silenced
return;
}
}
}
}
return true;
// No matching silence rule, forward the alert
dispatcherAlarm.dispatchAlarm(groupAlert);
}
/**
* Check AlertSilence start and end match, to save alertSilence obj.
* @param times LocalDateTime.
* @param alertSilence {@link AlertSilence}
* @return boolean
* Check if alert time is within silence period and update silence rule counter
* @param now Current time
* @param alertSilence Silence rule to check
* @return true if alert should not be silenced, false if alert should be silenced
*/
private boolean checkAndSave(LocalDateTime times, AlertSilence alertSilence) {
boolean startMatch = alertSilence.getPeriodStart() == null || times.isAfter(alertSilence.getPeriodStart().toLocalDateTime());
boolean endMatch = alertSilence.getPeriodEnd() == null || times.isBefore(alertSilence.getPeriodEnd().toLocalDateTime());
private boolean checkAndSave(LocalDateTime now, AlertSilence alertSilence) {
boolean startMatch = alertSilence.getPeriodStart() == null
|| now.isAfter(alertSilence.getPeriodStart().toLocalDateTime());
boolean endMatch = alertSilence.getPeriodEnd() == null
|| now.isBefore(alertSilence.getPeriodEnd().toLocalDateTime());
if (startMatch && endMatch) {
int time = Optional.ofNullable(alertSilence.getTimes()).orElse(0);
alertSilence.setTimes(time + 1);
alertSilenceDao.save(alertSilence);
return false;
}
return true;
}
}
@@ -19,13 +19,9 @@ package org.apache.hertzbeat.alert.service;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.multipart.MultipartFile;
/**
@@ -77,59 +73,18 @@ public interface AlertDefineService {
* @throws RuntimeException Exception thrown during deletion
*/
void deleteAlertDefines(Set<Long> alertIds) throws RuntimeException;
/**
* Dynamic conditional query
* @param specification Query conditions
* @param pageRequest Paging parameters
* @return The query results
*/
Page<AlertDefine> getMonitorBindAlertDefines(Specification<AlertDefine> specification, PageRequest pageRequest);
/**
* Association between application alarm schedule and monitoring
* @param alertId Alarm Definition ID
* @param alertDefineBinds correlation
*/
void applyBindAlertDefineMonitors(Long alertId, List<AlertDefineMonitorBind> alertDefineBinds);
/**
* Query the alarm definitions that match the specified metrics associated with the monitoring ID
* @param monitorId Monitor the ID
* @param app Monitoring type
* @param metrics metrics
* @return field - define[]
*/
Map<String, List<AlertDefine>> getMonitorBindAlertDefines(long monitorId, String app, String metrics);
/**
* Query the alarm definitions that match the specified metrics associated with the monitoring ID
* @param monitorId Monitor the ID
* @param app Monitoring type
* @param metrics metrics
* @return field - define[]
*/
AlertDefine getMonitorBindAlertAvaDefine(long monitorId, String app, String metrics);
/**
* Dynamic conditional query
* @param defineIds Alarm Definition ID List
* @param search Search-Target Expr Template
* @param priority Alarm Definition Severity
* @param sort Sort field
* @param order Sort mode: asc: ascending, desc: descending
* @param pageIndex List current page
* @param pageSize Number of list pages
* @return The query results
*/
Page<AlertDefine> getAlertDefines(List<Long> defineIds, String search, Byte priority, String sort, String order, int pageIndex, int pageSize);
/**
* Query the associated monitoring list information based on the alarm definition ID
* @param alertDefineId Alarm Definition ID
* @return Associated information about the monitoring list
*/
List<AlertDefineMonitorBind> getBindAlertDefineMonitors(long alertDefineId);
Page<AlertDefine> getAlertDefines(List<Long> defineIds, String search, String sort, String order, int pageIndex, int pageSize);
/**
* Export file configuration of specified type based on ID list and export file type
@@ -146,4 +101,11 @@ public interface AlertDefineService {
* @throws Exception An exception was thrown during the importConfig
*/
void importConfig(MultipartFile file) throws Exception;
/**
* Get the real-time alarm definition list
* @return Real-time alarm definition list
*/
List<AlertDefine> getRealTimeAlertDefines();
}
@@ -19,50 +19,50 @@ package org.apache.hertzbeat.alert.service;
import java.util.List;
import java.util.Set;
import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
import org.springframework.data.domain.Page;
/**
* management interface service for alert converge
*/
public interface AlertConvergeService {
public interface AlertGroupConvergeService {
/**
* Verify the correctness of the request data parameters
* @param alertConverge AlertConverge
* @param alertGroupConverge AlertGroupConverge
* @param isModify whether modify
* @throws IllegalArgumentException A checksum parameter error is thrown
*/
void validate(AlertConverge alertConverge, boolean isModify) throws IllegalArgumentException;
void validate(AlertGroupConverge alertGroupConverge, boolean isModify) throws IllegalArgumentException;
/**
* New AlertConverge
* @param alertConverge AlertConverge Entity
* New AlertGroupConverge
* @param alertGroupConverge AlertGroupConverge Entity
* @throws RuntimeException Added procedure exception throwing
*/
void addAlertConverge(AlertConverge alertConverge) throws RuntimeException;
void addAlertGroupConverge(AlertGroupConverge alertGroupConverge) throws RuntimeException;
/**
* Modifying an AlertConverge
* @param alertConverge Alarm definition Entity
* Modifying an AlertGroupConverge
* @param alertGroupConverge Alarm definition Entity
* @throws RuntimeException Exception thrown during modification
*/
void modifyAlertConverge(AlertConverge alertConverge) throws RuntimeException;
void modifyAlertGroupConverge(AlertGroupConverge alertGroupConverge) throws RuntimeException;
/**
* Obtain AlertConverge information
* @param convergeId AlertConverge ID
* @return AlertConverge
* Obtain AlertGroupConverge information
* @param convergeId AlertGroupConverge ID
* @return AlertGroupConverge
* @throws RuntimeException An exception was thrown during the query
*/
AlertConverge getAlertConverge(long convergeId) throws RuntimeException;
AlertGroupConverge getAlertGroupConverge(long convergeId) throws RuntimeException;
/**
* Delete AlertConverge in batches
* @param convergeIds AlertConverge IDs
* Delete AlertGroupConverge in batches
* @param convergeIds AlertGroupConverge IDs
* @throws RuntimeException Exception thrown during deletion
*/
void deleteAlertConverges(Set<Long> convergeIds) throws RuntimeException;
void deleteAlertGroupConverges(Set<Long> convergeIds) throws RuntimeException;
/**
* Dynamic conditional query
@@ -74,5 +74,5 @@ public interface AlertConvergeService {
* @param pageSize Number of list pages
* @return The query results
*/
Page<AlertConverge> getAlertConverges(List<Long> convergeIds, String search, String sort, String order, int pageIndex, int pageSize);
Page<AlertGroupConverge> getAlertGroupConverges(List<Long> convergeIds, String search, String sort, String order, int pageIndex, int pageSize);
}
@@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service;
import java.util.List;
import java.util.Set;
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
import org.springframework.data.domain.Page;
/**
* management interface service for alert inhibit
*/
public interface AlertInhibitService {
/**
* Verify the correctness of the request data parameters
* @param alertInhibit AlertInhibit
* @param isModify whether modify
* @throws IllegalArgumentException A checksum parameter error is thrown
*/
void validate(AlertInhibit alertInhibit, boolean isModify) throws IllegalArgumentException;
/**
* New AlertInhibit
* @param alertInhibit AlertInhibit Entity
* @throws RuntimeException Added procedure exception throwing
*/
void addAlertInhibit(AlertInhibit alertInhibit) throws RuntimeException;
/**
* Modifying an AlertInhibit
* @param alertInhibit Alarm definition Entity
* @throws RuntimeException Exception thrown during modification
*/
void modifyAlertInhibit(AlertInhibit alertInhibit) throws RuntimeException;
/**
* Obtain AlertInhibit information
* @param inhibitId AlertInhibit ID
* @return AlertInhibit
* @throws RuntimeException An exception was thrown during the query
*/
AlertInhibit getAlertInhibit(long inhibitId) throws RuntimeException;
/**
* Delete AlertInhibit in batches
* @param inhibitIds AlertInhibit IDs
* @throws RuntimeException Exception thrown during deletion
*/
void deleteAlertInhibits(Set<Long> inhibitIds) throws RuntimeException;
/**
* Dynamic conditional query
* @param inhibitIds Alarm Silence ID
* @param search Search Name
* @param sort Sort field
* @param order Sort mode: asc: ascending, desc: descending
* @param pageIndex List current page
* @param pageSize Number of list pages
* @return The query results
*/
Page<AlertInhibit> getAlertInhibits(List<Long> inhibitIds, String search, String sort, String order, int pageIndex, int pageSize);
}
@@ -20,10 +20,9 @@ package org.apache.hertzbeat.alert.service;
import java.util.HashSet;
import java.util.List;
import org.apache.hertzbeat.alert.dto.AlertSummary;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.dto.AlertReport;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
/**
* Alarm information management interface
@@ -31,68 +30,57 @@ import org.springframework.data.jpa.domain.Specification;
public interface AlertService {
/**
* Add alarm record
* @param alert Alert entity
* @throws RuntimeException Add process exception throw
* get and query single alerts
* @param status status
* @param search search
* @param sort sort
* @param order order
* @param pageIndex pageIndex
* @param pageSize pageSize
* @return single alerts
*/
void addAlert(Alert alert) throws RuntimeException;
Page<SingleAlert> getSingleAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize);
/**
* Dynamic conditional query
* @param alarmIds Alarm ID List
* @param monitorId Monitor ID
* @param priority Alarm level
* @param status Alarm Status
* @param content Alarm content fuzzy query
* @param sort Sort field
* @param order Sort Type
* @param pageIndex List current page
* @param pageSize Number of list pagination
* @return search result
*/
Page<Alert> getAlerts(List<Long> alarmIds, Long monitorId, Byte priority, Byte status, String content, String sort, String order, int pageIndex, int pageSize);
Page<GroupAlert> getGroupAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize);
/**
* Delete alarms in batches according to the alarm ID list
* @param ids Alarm ID List
* delete the group alarm according to the alarm ID
* @param ids Alarm ID List
*/
void deleteAlerts(HashSet<Long> ids);
void deleteGroupAlerts(HashSet<Long> ids);
/**
* Clear all alerts
* delete the single alarm according to the alarm ID
* @param ids Alarm ID List
*/
void clearAlerts();
void deleteSingleAlerts(HashSet<Long> ids);
/**
* Update the alarm status according to the alarm ID-status value
* @param status Alarm status to be modified
* @param ids Alarm ID List to be modified
* @param status Alarm status to be modified
* @param ids Alarm ID List to be modified
*/
void editAlertStatus(Byte status, List<Long> ids);
void editGroupAlertStatus(String status, List<Long> ids);
/**
* Update the alarm status according to the alarm ID-status value
* @param status Alarm status to be modified
* @param ids Alarm ID List to be modified
*/
void editSingleAlertStatus(String status, List<Long> ids);
/**
* Get alarm statistics information
* @return Alarm statistics information
*/
AlertSummary getAlertsSummary();
/**
* A third party reports an alarm
* @param alertReport The alarm information
*/
void addNewAlertReport(AlertReport alertReport);
/**
* Save external alarms of cloud services
* @param cloudServiceName cloud service name,Such as tencloud
* @param alertReport alert report json string
*/
void addNewAlertReportFromCloud(String cloudServiceName, String alertReport);
/**
* Dynamic conditional query
* @param specification Query conditions
* @return search result
*/
List<Alert> getAlerts(Specification<Alert> specification);
}
@@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service;
import java.util.List;
import java.util.Map;
/**
* datasource service
*/
public interface DataSourceService {
/**
* execute query expr calculate
* @param datasource datasource
* @param expr query expr
* @return result
*/
List<Map<String, Object>> calculate(String datasource, String expr);
}
@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service;
/**
* Extern alert service for prometheus, cloud and so on
*/
public interface ExternAlertService {
/**
* Save external alarms of services
* @param content alert report json string
*/
void addExternAlert(String content);
/**
* Support source: prometheus, tencent ...
* @return source name
*/
String supportSource();
}
@@ -15,14 +15,15 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.service;
package org.apache.hertzbeat.alert.service;
import java.util.List;
import java.util.Optional;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeRule;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.springframework.data.domain.Page;
/**
* Message notification configuration interface
@@ -32,23 +33,32 @@ public interface NoticeConfigService {
/**
* Dynamic conditional query
* @param name Recipient name,support fuzzy query
* @param pageIndex Page number
* @param pageSize Number of records per page
* @return Search result
*/
List<NoticeReceiver> getNoticeReceivers(String name);
Page<NoticeReceiver> getNoticeReceivers(String name, int pageIndex, int pageSize);
/**
* Dynamic conditional query
* @param name Template name,support fuzzy query
* @param preset Whether it is a system preset template
* true: System preset template
* false: Custom template
* @param pageIndex Page number
* @param pageSize Number of records per page
* @return Search result
*/
List<NoticeTemplate> getNoticeTemplates(String name);
Page<NoticeTemplate> getNoticeTemplates(String name, boolean preset, int pageIndex, int pageSize);
/**
* Dynamic conditional query
* @param name Recipient name
* @param name Recipient name ,support fuzzy query
* @param pageIndex Page number
* @param pageSize Number of records per page
* @return Search result
*/
List<NoticeRule> getNoticeRules(String name);
Page<NoticeRule> getNoticeRules(String name, int pageIndex, int pageSize);
/**
* Add a notification recipient
@@ -91,7 +101,7 @@ public interface NoticeConfigService {
* @param alert Alarm information
* @return Receiver
*/
List<NoticeRule> getReceiverFilterRule(Alert alert);
List<NoticeRule> getReceiverFilterRule(GroupAlert alert);
/**
* Query the template information according to the template ID
@@ -154,4 +164,15 @@ public interface NoticeConfigService {
*/
boolean sendTestMsg(NoticeReceiver noticeReceiver);
/**
* Query all notification recipients
* @return Recipient List
*/
List<NoticeReceiver> getAllNoticeReceivers();
/**
* Query all notification policies
* @return Notification Policy List
*/
List<NoticeTemplate> getAllNoticeTemplates();
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.service;
package org.apache.hertzbeat.alert.service;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.sms.v20210111.SmsClient;
@@ -25,17 +25,17 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dto.AlertDefineDTO;
import org.apache.hertzbeat.alert.dto.ExportAlertDefineDTO;
import org.apache.hertzbeat.common.entity.manager.TagItem;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.util.export.ExcelExportUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
@@ -97,15 +97,6 @@ public class AlertDefineExcelImExportServiceImpl extends AlertDefineAbstractImEx
}
}
private List<TagItem> extractTagDataFromRow(Cell cell) {
String jsonStr = getCellValueAsString(cell);
if (StringUtils.hasText(jsonStr)) {
return JsonUtil.fromJson(jsonStr, new TypeReference<>() {
});
}
return null;
}
private String getCellValueAsString(Cell cell) {
if (cell == null) {
@@ -153,18 +144,17 @@ public class AlertDefineExcelImExportServiceImpl extends AlertDefineAbstractImEx
private AlertDefineDTO extractAlertDefineDataFromRow(Row row) {
TypeReference<Map<String, String>> typeReference = new TypeReference<>() {};
AlertDefineDTO alertDefineDTO = new AlertDefineDTO();
alertDefineDTO.setApp(getCellValueAsString(row.getCell(0)));
alertDefineDTO.setMetric(getCellValueAsString(row.getCell(1)));
alertDefineDTO.setField(getCellValueAsString(row.getCell(2)));
alertDefineDTO.setPreset(getCellValueAsBoolean(row.getCell(3)));
alertDefineDTO.setExpr(getCellValueAsString(row.getCell(4)));
alertDefineDTO.setPriority(getCellValueAsByte(row.getCell(5)));
alertDefineDTO.setTimes(getCellValueAsInteger(row.getCell(6)));
alertDefineDTO.setTags(extractTagDataFromRow(row.getCell(7)));
alertDefineDTO.setName(getCellValueAsString(row.getCell(0)));
alertDefineDTO.setType(getCellValueAsString(row.getCell(1)));
alertDefineDTO.setExpr(getCellValueAsString(row.getCell(2)));
alertDefineDTO.setPeriod(getCellValueAsInteger(row.getCell(3)));
alertDefineDTO.setTimes(getCellValueAsInteger(row.getCell(4)));
alertDefineDTO.setLabels(JsonUtil.fromJson(getCellValueAsString(row.getCell(5)), typeReference));
alertDefineDTO.setAnnotations(JsonUtil.fromJson(getCellValueAsString(row.getCell(6)), typeReference));
alertDefineDTO.setTemplate(getCellValueAsString(row.getCell(7)));
alertDefineDTO.setEnable(getCellValueAsBoolean(row.getCell(8)));
alertDefineDTO.setRecoverNotice(getCellValueAsBoolean(row.getCell(9)));
alertDefineDTO.setTemplate(getCellValueAsString(row.getCell(10)));
return alertDefineDTO;
}
@@ -178,11 +168,31 @@ public class AlertDefineExcelImExportServiceImpl extends AlertDefineAbstractImEx
public void writeOs(List<ExportAlertDefineDTO> exportAlertDefineList, OutputStream os) {
try {
Workbook workbook = new HSSFWorkbook();
Workbook workbook = WorkbookFactory.create(true);
String sheetName = "Export AlertDefine";
Sheet sheet = ExcelExportUtils.setSheet(sheetName, workbook, AlertDefineDTO.class);
Sheet sheet = workbook.createSheet(sheetName);
sheet.setDefaultColumnWidth(20);
sheet.setColumnWidth(2, 40 * 256);
sheet.setColumnWidth(5, 40 * 256);
sheet.setColumnWidth(6, 40 * 256);
sheet.setColumnWidth(7, 40 * 256);
// set header style
CellStyle headerCellStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerCellStyle.setFont(headerFont);
headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
// set cell style
CellStyle cellStyle = ExcelExportUtils.setCellStyle(workbook);
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.CENTER);
// set header
String[] headers = {"Name", "Type", "Expr", "Period", "Times", "Labels", "Annotations", "Template", "Enable"};
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerCellStyle);
}
// Traverse the threshold rule list, each threshold rule object corresponds to a row of data
int rowIndex = 1;
@@ -190,44 +200,33 @@ public class AlertDefineExcelImExportServiceImpl extends AlertDefineAbstractImEx
AlertDefineDTO alertDefineDTO = alertDefine.getAlertDefine();
Row row = sheet.createRow(rowIndex++);
// Threshold rule information only needs to be written once
Cell appCell = row.createCell(0);
appCell.setCellValue(alertDefineDTO.getApp());
appCell.setCellStyle(cellStyle);
Cell metricCell = row.createCell(1);
metricCell.setCellValue(alertDefineDTO.getMetric());
metricCell.setCellStyle(cellStyle);
Cell fieldCell = row.createCell(2);
fieldCell.setCellValue(alertDefineDTO.getField());
fieldCell.setCellStyle(cellStyle);
Cell presetCell = row.createCell(3);
presetCell.setCellValue(alertDefineDTO.getPreset() != null
&& alertDefineDTO.getPreset());
presetCell.setCellStyle(cellStyle);
Cell exprCell = row.createCell(4);
Cell nameCell = row.createCell(0);
nameCell.setCellValue(alertDefineDTO.getName());
nameCell.setCellStyle(cellStyle);
Cell typeCell = row.createCell(1);
typeCell.setCellValue(alertDefineDTO.getType());
typeCell.setCellStyle(cellStyle);
Cell exprCell = row.createCell(2);
exprCell.setCellValue(alertDefineDTO.getExpr());
exprCell.setCellStyle(cellStyle);
Cell priorityCell = row.createCell(5);
priorityCell.setCellValue(alertDefineDTO.getPriority());
priorityCell.setCellStyle(cellStyle);
Cell timesCell = row.createCell(6);
Cell periodCell = row.createCell(3);
periodCell.setCellValue(alertDefineDTO.getPeriod());
periodCell.setCellStyle(cellStyle);
Cell timesCell = row.createCell(4);
timesCell.setCellValue(alertDefineDTO.getTimes());
Cell tagCell = row.createCell(7);
// get tags
List<TagItem> tagList = alertDefineDTO.getTags();
String tagValue = tagList == null || tagList.isEmpty() ? "" : JsonUtil.toJson(tagList);
tagCell.setCellValue(tagValue);
tagCell.setCellStyle(cellStyle);
Cell enableCell = row.createCell(8);
enableCell.setCellValue(alertDefineDTO.getEnable() != null
&& alertDefineDTO.getEnable());
enableCell.setCellStyle(cellStyle);
Cell recoverNoticeCell = row.createCell(9);
recoverNoticeCell.setCellValue(alertDefineDTO.getRecoverNotice() != null
&& alertDefineDTO.getRecoverNotice());
recoverNoticeCell.setCellStyle(cellStyle);
Cell templateCell = row.createCell(10);
timesCell.setCellStyle(cellStyle);
Cell labelsCell = row.createCell(5);
labelsCell.setCellValue(JsonUtil.toJson(alertDefineDTO.getLabels()));
labelsCell.setCellStyle(cellStyle);
Cell annoCell = row.createCell(6);
annoCell.setCellValue(JsonUtil.toJson(alertDefineDTO.getAnnotations()));
annoCell.setCellStyle(cellStyle);
Cell templateCell = row.createCell(7);
templateCell.setCellValue(alertDefineDTO.getTemplate());
recoverNoticeCell.setCellStyle(cellStyle);
templateCell.setCellStyle(cellStyle);
Cell enableCell = row.createCell(8);
enableCell.setCellValue(alertDefineDTO.getEnable());
enableCell.setCellStyle(cellStyle);
}
workbook.write(os);
os.close();
@@ -17,6 +17,7 @@
package org.apache.hertzbeat.alert.service.impl;
import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_THRESHOLD_TYPE_REALTIME;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -24,16 +25,14 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.AlertDefineBindDao;
import org.apache.hertzbeat.alert.calculate.PeriodicAlertRuleScheduler;
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
import org.apache.hertzbeat.alert.dao.AlertMonitorDao;
import org.apache.hertzbeat.alert.service.AlertDefineImExportService;
import org.apache.hertzbeat.alert.service.AlertDefineService;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.constants.ExportFileConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.util.FileUtil;
import org.apache.hertzbeat.common.util.JexlExpressionRunner;
import org.springframework.beans.factory.annotation.Autowired;
@@ -53,14 +52,11 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Alarm definition management interface implementation
@@ -72,12 +68,9 @@ public class AlertDefineServiceImpl implements AlertDefineService {
@Autowired
private AlertDefineDao alertDefineDao;
@Autowired
private AlertDefineBindDao alertDefineBindDao;
@Autowired
private AlertMonitorDao alertMonitorDao;
private PeriodicAlertRuleScheduler periodicAlertRuleScheduler;
private final Map<String, AlertDefineImExportService> alertDefineImExportServiceMap = new HashMap<>();
@@ -89,29 +82,43 @@ public class AlertDefineServiceImpl implements AlertDefineService {
@Override
public void validate(AlertDefine alertDefine, boolean isModify) throws IllegalArgumentException {
// todo
if (StringUtils.hasText(alertDefine.getExpr())) {
try {
JexlExpressionRunner.compile(alertDefine.getExpr());
} catch (Exception e) {
throw new IllegalArgumentException("alert expr error: " + e.getMessage());
if (ALERT_THRESHOLD_TYPE_REALTIME.equals(alertDefine.getType())) {
try {
JexlExpressionRunner.compile(alertDefine.getExpr());
} catch (Exception e) {
throw new IllegalArgumentException("alert expr error: " + e.getMessage());
}
}
}
// the name of the alarm rule is unique
Optional<AlertDefine> optional = alertDefineDao.findAlertDefineByName(alertDefine.getName());
if (optional.isPresent()) {
if (!isModify || !optional.get().getId().equals(alertDefine.getId())) {
throw new IllegalArgumentException("alert name already exists");
}
}
}
@Override
public void addAlertDefine(AlertDefine alertDefine) throws RuntimeException {
alertDefineDao.save(alertDefine);
alertDefine = alertDefineDao.save(alertDefine);
periodicAlertRuleScheduler.updateSchedule(alertDefine);
CacheFactory.clearAlertDefineCache();
}
@Override
public void modifyAlertDefine(AlertDefine alertDefine) throws RuntimeException {
alertDefineDao.save(alertDefine);
periodicAlertRuleScheduler.updateSchedule(alertDefine);
CacheFactory.clearAlertDefineCache();
}
@Override
public void deleteAlertDefine(long alertId) throws RuntimeException {
alertDefineDao.deleteById(alertId);
periodicAlertRuleScheduler.cancelSchedule(alertId);
CacheFactory.clearAlertDefineCache();
}
@Override
@@ -123,47 +130,14 @@ public class AlertDefineServiceImpl implements AlertDefineService {
@Override
public void deleteAlertDefines(Set<Long> alertIds) throws RuntimeException {
alertDefineDao.deleteAlertDefinesByIdIn(alertIds);
}
@Override
public Page<AlertDefine> getMonitorBindAlertDefines(Specification<AlertDefine> specification, PageRequest pageRequest) {
return alertDefineDao.findAll(specification, pageRequest);
}
@Override
public void applyBindAlertDefineMonitors(Long alertId, List<AlertDefineMonitorBind> alertDefineBinds) {
// todo checks whether the alarm definition and monitoring exist
if (!alertDefineBindDao.existsById(alertId)){
alertDefineBindDao.deleteAlertDefineBindsByAlertDefineIdEquals(alertId);
for (Long alertId : alertIds) {
periodicAlertRuleScheduler.cancelSchedule(alertId);
}
// Delete all associations of this alarm
alertDefineBindDao.deleteAlertDefineBindsByAlertDefineIdEquals(alertId);
// Save the associated
alertDefineBindDao.saveAll(alertDefineBinds);
CacheFactory.clearAlertDefineCache();
}
@Override
public Map<String, List<AlertDefine>> getMonitorBindAlertDefines(long monitorId, String app, String metrics) {
List<AlertDefine> defines = alertDefineDao.queryAlertDefinesByMonitor(monitorId, app, metrics);
List<AlertDefine> defaultDefines = alertDefineDao.queryAlertDefinesByAppAndMetricAndPresetTrueAndEnableTrue(app, metrics);
defines.addAll(defaultDefines);
Set<AlertDefine> defineSet = defines.stream().filter(item -> item.getField() != null).collect(Collectors.toSet());
// The alarm thresholds are defined in ascending order of the alarm severity from 0 to 3.
// The lower the number, the higher the alarm is. That is, the alarm is calculated from the highest alarm threshold
return defineSet.stream().sorted(Comparator.comparing(AlertDefine::getPriority))
.collect(Collectors.groupingBy(AlertDefine::getField));
}
@Override
public AlertDefine getMonitorBindAlertAvaDefine(long monitorId, String app, String metrics) {
List<AlertDefine> defines = alertDefineDao.queryAlertDefinesByMonitor(monitorId, app, metrics);
List<AlertDefine> defaultDefines = alertDefineDao.queryAlertDefinesByAppAndMetricAndPresetTrueAndEnableTrue(app, metrics);
defines.addAll(defaultDefines);
return defines.stream().findFirst().orElse(null);
}
@Override
public Page<AlertDefine> getAlertDefines(List<Long> defineIds, String search, Byte priority, String sort, String order, int pageIndex, int pageSize) {
public Page<AlertDefine> getAlertDefines(List<Long> defineIds, String search, String sort, String order, int pageIndex, int pageSize) {
// parse translation content list
ObjectMapper objectMapper = new ObjectMapper();
List<String> searchList = Collections.emptyList();
@@ -190,10 +164,10 @@ public class AlertDefineServiceImpl implements AlertDefineService {
for (String searchContent : finalSearchList) {
searchContent = searchContent.toLowerCase();
Predicate predicate = criteriaBuilder.or(
criteriaBuilder.like(criteriaBuilder.lower(root.get("app")), "%" + searchContent + "%"),
criteriaBuilder.like(criteriaBuilder.lower(root.get("metric")), "%" + searchContent + "%"),
criteriaBuilder.like(criteriaBuilder.lower(root.get("field")), "%" + searchContent + "%"),
criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + searchContent + "%"),
criteriaBuilder.like(criteriaBuilder.lower(root.get("expr")), "%" + searchContent + "%"),
criteriaBuilder.like(criteriaBuilder.lower(root.get("labels")), "%" + searchContent + "%"),
criteriaBuilder.like(criteriaBuilder.lower(root.get("annotations")), "%" + searchContent + "%"),
criteriaBuilder.like(criteriaBuilder.lower(root.get("template")), "%" + searchContent + "%")
);
searchPredicates.add(predicate);
@@ -201,10 +175,6 @@ public class AlertDefineServiceImpl implements AlertDefineService {
// all search keywords are connected with or
andList.add(criteriaBuilder.or(searchPredicates.toArray(new Predicate[0])));
}
if (priority != null) {
Predicate predicate = criteriaBuilder.equal(root.get("priority"), priority);
andList.add(predicate);
}
Predicate[] predicates = new Predicate[andList.size()];
return criteriaBuilder.and(andList.toArray(predicates));
};
@@ -213,28 +183,6 @@ public class AlertDefineServiceImpl implements AlertDefineService {
return alertDefineDao.findAll(specification, pageRequest);
}
@Override
public List<AlertDefineMonitorBind> getBindAlertDefineMonitors(long alertDefineId) {
List<AlertDefineMonitorBind> defineMonitorBinds = alertDefineBindDao.getAlertDefineBindsByAlertDefineIdEquals(alertDefineId);
if (defineMonitorBinds == null || defineMonitorBinds.isEmpty()) {
return defineMonitorBinds;
}
List<Long> needLoadMonitorIds = defineMonitorBinds.stream()
.filter(bind -> bind.getMonitor() == null)
.map(AlertDefineMonitorBind::getMonitorId).toList();
if (needLoadMonitorIds.isEmpty()) {
return defineMonitorBinds;
}
Map<Long, Monitor> monitorMap = alertMonitorDao.findAllById(needLoadMonitorIds)
.stream().collect(Collectors.toMap(Monitor::getId, Function.identity()));
for (AlertDefineMonitorBind bind : defineMonitorBinds) {
if (bind.getMonitor() == null) {
bind.setMonitor(monitorMap.get(bind.getMonitorId()));
}
}
return defineMonitorBinds;
}
@Override
public void export(List<Long> ids, String type, HttpServletResponse res) throws Exception {
var imExportService = alertDefineImExportServiceMap.get(type);
@@ -260,4 +208,14 @@ public class AlertDefineServiceImpl implements AlertDefineService {
var imExportService = alertDefineImExportServiceMap.get(type);
imExportService.importConfig(file.getInputStream());
}
@Override
public List<AlertDefine> getRealTimeAlertDefines() {
List<AlertDefine> alertDefines = CacheFactory.getAlertDefineCache();
if (alertDefines == null) {
alertDefines = alertDefineDao.findAlertDefinesByTypeAndEnableTrue(ALERT_THRESHOLD_TYPE_REALTIME);
CacheFactory.setAlertDefineCache(alertDefines);
}
return alertDefines;
}
}
@@ -23,12 +23,10 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.AlertConvergeDao;
import org.apache.hertzbeat.alert.service.AlertConvergeService;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.cache.CommonCacheService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
import org.apache.hertzbeat.alert.dao.AlertGroupConvergeDao;
import org.apache.hertzbeat.alert.reduce.AlarmGroupReduce;
import org.apache.hertzbeat.alert.service.AlertGroupConvergeService;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@@ -44,42 +42,45 @@ import org.springframework.util.StringUtils;
@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class AlertConvergeServiceImpl implements AlertConvergeService {
public class AlertGroupConvergeServiceImpl implements AlertGroupConvergeService {
@Autowired
private AlertConvergeDao alertConvergeDao;
private AlertGroupConvergeDao alertGroupConvergeDao;
@Autowired
private AlarmGroupReduce alarmGroupReduce;
@Override
public void validate(AlertConverge alertConverge, boolean isModify) throws IllegalArgumentException {
public void validate(AlertGroupConverge alertGroupConverge, boolean isModify) throws IllegalArgumentException {
// todo
}
@Override
public void addAlertConverge(AlertConverge alertConverge) throws RuntimeException {
alertConvergeDao.save(alertConverge);
clearAlertConvergesCache();
public void addAlertGroupConverge(AlertGroupConverge alertGroupConverge) throws RuntimeException {
alertGroupConvergeDao.save(alertGroupConverge);
refreshAlertGroupConvergesCache();
}
@Override
public void modifyAlertConverge(AlertConverge alertConverge) throws RuntimeException {
alertConvergeDao.save(alertConverge);
clearAlertConvergesCache();
public void modifyAlertGroupConverge(AlertGroupConverge alertGroupConverge) throws RuntimeException {
alertGroupConvergeDao.save(alertGroupConverge);
refreshAlertGroupConvergesCache();
}
@Override
public AlertConverge getAlertConverge(long convergeId) throws RuntimeException {
return alertConvergeDao.findById(convergeId).orElse(null);
public AlertGroupConverge getAlertGroupConverge(long convergeId) throws RuntimeException {
return alertGroupConvergeDao.findById(convergeId).orElse(null);
}
@Override
public void deleteAlertConverges(Set<Long> convergeIds) throws RuntimeException {
alertConvergeDao.deleteAlertConvergesByIdIn(convergeIds);
clearAlertConvergesCache();
public void deleteAlertGroupConverges(Set<Long> convergeIds) throws RuntimeException {
alertGroupConvergeDao.deleteAlertGroupConvergesByIdIn(convergeIds);
refreshAlertGroupConvergesCache();
}
@Override
public Page<AlertConverge> getAlertConverges(List<Long> convergeIds, String search, String sort, String order, int pageIndex, int pageSize) {
Specification<AlertConverge> specification = (root, query, criteriaBuilder) -> {
public Page<AlertGroupConverge> getAlertGroupConverges(List<Long> convergeIds, String search, String sort, String order, int pageIndex, int pageSize) {
Specification<AlertGroupConverge> specification = (root, query, criteriaBuilder) -> {
List<Predicate> andList = new ArrayList<>();
if (convergeIds != null && !convergeIds.isEmpty()) {
CriteriaBuilder.In<Long> inPredicate = criteriaBuilder.in(root.get("id"));
@@ -102,11 +103,11 @@ public class AlertConvergeServiceImpl implements AlertConvergeService {
};
Sort sortExp = Sort.by(new Sort.Order(Sort.Direction.fromString(order), sort));
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sortExp);
return alertConvergeDao.findAll(specification, pageRequest);
return alertGroupConvergeDao.findAll(specification, pageRequest);
}
private void clearAlertConvergesCache() {
CommonCacheService<String, Object> convergeCache = CacheFactory.getAlertConvergeCache();
convergeCache.remove(CommonConstants.CACHE_ALERT_CONVERGE);
private void refreshAlertGroupConvergesCache() {
List<AlertGroupConverge> alertGroupConverges = alertGroupConvergeDao.findAlertGroupConvergesByEnableIsTrue();
alarmGroupReduce.refreshGroupDefines(alertGroupConverges);
}
}
@@ -0,0 +1,112 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service.impl;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.AlertInhibitDao;
import org.apache.hertzbeat.alert.reduce.AlarmInhibitReduce;
import org.apache.hertzbeat.alert.service.AlertInhibitService;
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
/**
* management interface service implement for alert inhibit
*/
@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class AlertInhibitServiceImpl implements AlertInhibitService {
@Autowired
private AlertInhibitDao alertInhibitDao;
@Autowired
private AlarmInhibitReduce alarmInhibitReduce;
@Override
public void validate(AlertInhibit alertInhibit, boolean isModify) throws IllegalArgumentException {
// todo
}
@Override
public void addAlertInhibit(AlertInhibit alertInhibit) throws RuntimeException {
alertInhibitDao.save(alertInhibit);
refreshAlertInhibitsCache();
}
@Override
public void modifyAlertInhibit(AlertInhibit alertInhibit) throws RuntimeException {
alertInhibitDao.save(alertInhibit);
refreshAlertInhibitsCache();
}
@Override
public AlertInhibit getAlertInhibit(long inhibitId) throws RuntimeException {
return alertInhibitDao.findById(inhibitId).orElse(null);
}
@Override
public void deleteAlertInhibits(Set<Long> inhibitIds) throws RuntimeException {
alertInhibitDao.deleteAlertInhibitsByIdIn(inhibitIds);
refreshAlertInhibitsCache();
}
@Override
public Page<AlertInhibit> getAlertInhibits(List<Long> inhibitIds, String search, String sort, String order, int pageIndex, int pageSize) {
Specification<AlertInhibit> specification = (root, query, criteriaBuilder) -> {
List<Predicate> andList = new ArrayList<>();
if (inhibitIds != null && !inhibitIds.isEmpty()) {
CriteriaBuilder.In<Long> inPredicate = criteriaBuilder.in(root.get("id"));
for (long id : inhibitIds) {
inPredicate.value(id);
}
andList.add(inPredicate);
}
if (StringUtils.hasText(search)) {
Predicate predicate = criteriaBuilder.or(
criteriaBuilder.like(
criteriaBuilder.lower(root.get("name")),
"%" + search.toLowerCase() + "%"
)
);
andList.add(predicate);
}
Predicate[] predicates = new Predicate[andList.size()];
return criteriaBuilder.and(andList.toArray(predicates));
};
Sort sortExp = Sort.by(new Sort.Order(Sort.Direction.fromString(order), sort));
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sortExp);
return alertInhibitDao.findAll(specification, pageRequest);
}
private void refreshAlertInhibitsCache() {
alarmInhibitReduce.refreshInhibitRules(alertInhibitDao.findAlertInhibitsByEnableIsTrue());
}
}
@@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service.impl;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dto.AlertManagerExternAlert;
import org.apache.hertzbeat.alert.dto.PrometheusExternAlert;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.ExternAlertService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* Alertmanager external alarm service impl
*/
@Slf4j
@Service
public class AlertManagerExternAlertService implements ExternAlertService {
@Autowired
private AlarmCommonReduce alarmCommonReduce;
@Override
public void addExternAlert(String content) {
AlertManagerExternAlert alert = JsonUtil.fromJson(content, AlertManagerExternAlert.class);
if (alert == null) {
log.warn("parse alertmanager extern alert content failed! content: {}", content);
return;
}
List<PrometheusExternAlert> alerts = alert.getAlerts();
if (alerts == null || alerts.isEmpty()) {
log.warn("receive alertmanager extern alert without alerts! content: {}", content);
return;
}
for (PrometheusExternAlert prometheusAlert : alerts) {
Map<String, String> annotations = prometheusAlert.getAnnotations();
if (annotations == null) {
annotations = new HashMap<>(8);
}
if (StringUtils.hasText(prometheusAlert.getGeneratorURL())) {
annotations.put("generatorURL", prometheusAlert.getGeneratorURL());
}
String description = annotations.get("description");
if (description == null) {
description = annotations.get("summary");
}
if (description == null) {
description = annotations.values().stream().findFirst().orElse("");
}
Map<String, String> labels = prometheusAlert.getLabels();
if (labels == null) {
labels = new HashMap<>(8);
}
labels.put("__source__", "alertmanager");
String status = CommonConstants.ALERT_STATUS_FIRING;
if (prometheusAlert.getEndsAt() != null && prometheusAlert.getEndsAt().isBefore(Instant.now())) {
status = CommonConstants.ALERT_STATUS_RESOLVED;
}
SingleAlert singleAlert = SingleAlert.builder()
.content(description)
.status(status)
.activeAt(CommonConstants.ALERT_STATUS_FIRING.equals(status) ? Instant.now().toEpochMilli() : null)
.startAt(prometheusAlert.getStartsAt() != null ? prometheusAlert.getStartsAt().toEpochMilli() : Instant.now().toEpochMilli())
.endAt(CommonConstants.ALERT_STATUS_RESOLVED.equals(status) ? prometheusAlert.getEndsAt().toEpochMilli() : null)
.labels(labels)
.annotations(prometheusAlert.getAnnotations())
.triggerTimes(1)
.build();
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
}
}
@Override
public String supportSource() {
return "alertmanager";
}
}
@@ -17,30 +17,21 @@
package org.apache.hertzbeat.alert.service.impl;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.AlertDao;
import org.apache.hertzbeat.alert.dto.AlertPriorityNum;
import org.apache.hertzbeat.alert.dao.GroupAlertDao;
import org.apache.hertzbeat.alert.dao.SingleAlertDao;
import org.apache.hertzbeat.alert.dto.AlertSummary;
import org.apache.hertzbeat.alert.dto.CloudAlertReportAbstract;
import org.apache.hertzbeat.alert.enums.CloudServiceAlarmInformationEnum;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.AlertService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.dto.AlertReport;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@@ -56,95 +47,153 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class AlertServiceImpl implements AlertService {
@Autowired
private AlertDao alertDao;
private GroupAlertDao groupAlertDao;
@Autowired
private SingleAlertDao singleAlertDao;
@Autowired
private AlarmCommonReduce alarmCommonReduce;
@Override
public void addAlert(Alert alert) throws RuntimeException {
alertDao.save(alert);
}
@Override
public Page<Alert> getAlerts(List<Long> alarmIds, Long monitorId, Byte priority, Byte status, String content, String sort, String order, int pageIndex, int pageSize) {
Specification<Alert> specification = (root, query, criteriaBuilder) -> {
public Page<SingleAlert> getSingleAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize) {
Specification<SingleAlert> specification = (root, query, criteriaBuilder) -> {
List<Predicate> andList = new ArrayList<>();
if (alarmIds != null && !alarmIds.isEmpty()) {
CriteriaBuilder.In<Long> inPredicate = criteriaBuilder.in(root.get("id"));
for (long id : alarmIds) {
inPredicate.value(id);
}
andList.add(inPredicate);
}
if (monitorId != null) {
Predicate predicate = criteriaBuilder.like(root.get("tags").as(String.class), "%" + monitorId + "%");
andList.add(predicate);
}
if (priority != null) {
Predicate predicate = criteriaBuilder.equal(root.get("priority"), priority);
andList.add(predicate);
}
if (status != null) {
Predicate predicate = criteriaBuilder.equal(root.get("status"), status);
andList.add(predicate);
}
if (content != null && !content.isEmpty()) {
Predicate predicateContent = criteriaBuilder.like(root.get("content"), "%" + content + "%");
andList.add(predicateContent);
Predicate[] andPredicates = new Predicate[andList.size()];
Predicate andPredicate = criteriaBuilder.and(andList.toArray(andPredicates));
List<Predicate> orList = new ArrayList<>();
if (search != null && !search.isEmpty()) {
Predicate predicateContent = criteriaBuilder.like(root.get("content"), "%" + search + "%");
orList.add(predicateContent);
Predicate predicateLabels = criteriaBuilder.like(root.get("labels"), "%" + search + "%");
orList.add(predicateLabels);
Predicate predicateAnnotation = criteriaBuilder.like(root.get("annotations"), "%" + search + "%");
orList.add(predicateAnnotation);
}
Predicate[] orPredicates = new Predicate[orList.size()];
Predicate orPredicate = criteriaBuilder.or(orList.toArray(orPredicates));
if (andPredicates.length == 0 && orPredicates.length == 0) {
return query.where().getRestriction();
} else if (andPredicates.length == 0) {
return orPredicate;
} else if (orPredicates.length == 0) {
return andPredicate;
} else {
return query.where(andPredicate, orPredicate).getRestriction();
}
Predicate[] predicates = new Predicate[andList.size()];
return criteriaBuilder.and(andList.toArray(predicates));
};
Sort sortExp = Sort.by(new Sort.Order(Sort.Direction.fromString(order), sort));
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sortExp);
return alertDao.findAll(specification, pageRequest);
return singleAlertDao.findAll(specification, pageRequest);
}
@Override
public void deleteAlerts(HashSet<Long> ids) {
alertDao.deleteAlertsByIdIn(ids);
public Page<GroupAlert> getGroupAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize) {
Specification<GroupAlert> specification = (root, query, criteriaBuilder) -> {
List<Predicate> andList = new ArrayList<>();
if (status != null) {
Predicate predicate = criteriaBuilder.equal(root.get("status"), status);
andList.add(predicate);
}
Predicate[] andPredicates = new Predicate[andList.size()];
Predicate andPredicate = criteriaBuilder.and(andList.toArray(andPredicates));
List<Predicate> orList = new ArrayList<>();
if (search != null && !search.isEmpty()) {
Predicate predicateContent = criteriaBuilder.like(root.get("groupLabels"), "%" + search + "%");
orList.add(predicateContent);
Predicate predicateLabels = criteriaBuilder.like(root.get("commonLabels"), "%" + search + "%");
orList.add(predicateLabels);
Predicate predicateAnnotation = criteriaBuilder.like(root.get("commonAnnotations"), "%" + search + "%");
orList.add(predicateAnnotation);
}
Predicate[] orPredicates = new Predicate[orList.size()];
Predicate orPredicate = criteriaBuilder.or(orList.toArray(orPredicates));
if (andPredicates.length == 0 && orPredicates.length == 0) {
return query.where().getRestriction();
} else if (andPredicates.length == 0) {
return orPredicate;
} else if (orPredicates.length == 0) {
return andPredicate;
} else {
return query.where(andPredicate, orPredicate).getRestriction();
}
};
Sort sortExp = Sort.by(new Sort.Order(Sort.Direction.fromString(order), sort));
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sortExp);
Page<GroupAlert> groupAlertPage = groupAlertDao.findAll(specification, pageRequest);
for (GroupAlert groupAlert : groupAlertPage.getContent()) {
List<String> firingAlerts = groupAlert.getAlertFingerprints();
List<SingleAlert> singleAlerts = singleAlertDao.findSingleAlertsByFingerprintIn(firingAlerts);
groupAlert.setAlerts(singleAlerts);
}
return groupAlertPage;
}
@Override
public void clearAlerts() {
alertDao.deleteAll();
public void deleteGroupAlerts(HashSet<Long> ids) {
List<GroupAlert> groupAlerts = groupAlertDao.findGroupAlertsByIdIn(ids);
for (GroupAlert groupAlert : groupAlerts) {
List<String> firingAlerts = groupAlert.getAlertFingerprints();
singleAlertDao.deleteSingleAlertsByFingerprintIn(firingAlerts);
}
groupAlertDao.deleteGroupAlertsByIdIn(ids);
}
@Override
public void editAlertStatus(Byte status, List<Long> ids) {
alertDao.updateAlertsStatus(status, ids);
public void deleteSingleAlerts(HashSet<Long> ids) {
singleAlertDao.deleteSingleAlertsByIdIn(ids);
}
@Override
public void editGroupAlertStatus(String status, List<Long> ids) {
groupAlertDao.updateGroupAlertsStatus(status, ids);
}
@Override
public void editSingleAlertStatus(String status, List<Long> ids) {
singleAlertDao.updateSingleAlertsStatus(status, ids);
}
@Override
public AlertSummary getAlertsSummary() {
AlertSummary alertSummary = new AlertSummary();
// Statistics on the alarm information in the alarm state
List<AlertPriorityNum> priorityNums = alertDao.findAlertPriorityNum();
if (priorityNums != null) {
for (AlertPriorityNum priorityNum : priorityNums) {
switch (priorityNum.getPriority()) {
case CommonConstants
.ALERT_PRIORITY_CODE_WARNING -> alertSummary.setPriorityWarningNum(priorityNum.getNum());
case CommonConstants.ALERT_PRIORITY_CODE_CRITICAL -> alertSummary.setPriorityCriticalNum(priorityNum.getNum());
case CommonConstants.ALERT_PRIORITY_CODE_EMERGENCY -> alertSummary.setPriorityEmergencyNum(priorityNum.getNum());
List<SingleAlert> firingAlerts = singleAlertDao.querySingleAlertsByStatus(CommonConstants.ALERT_STATUS_FIRING);
// severity - emergency critical warning info
int emergencyNum = 0;
int criticalNum = 0;
int warningNum = 0;
for (SingleAlert alert : firingAlerts) {
String severity = alert.getLabels().get(CommonConstants.LABEL_ALERT_SEVERITY);
if (severity != null) {
switch (severity) {
case CommonConstants.ALERT_SEVERITY_EMERGENCY -> emergencyNum++;
case CommonConstants.ALERT_SEVERITY_CRITICAL -> criticalNum++;
case CommonConstants.ALERT_SEVERITY_WARNING -> warningNum++;
default -> {}
}
}
alertSummary.setPriorityCriticalNum(criticalNum);
alertSummary.setPriorityEmergencyNum(emergencyNum);
alertSummary.setPriorityWarningNum(warningNum);
}
long total = alertDao.count();
long total = singleAlertDao.count();
alertSummary.setTotal(total);
long dealNum = total - alertSummary.getPriorityCriticalNum()
- alertSummary.getPriorityEmergencyNum() - alertSummary.getPriorityWarningNum();
alertSummary.setDealNum(dealNum);
long resolved = total - firingAlerts.size();
alertSummary.setDealNum(resolved);
try {
if (total == 0) {
alertSummary.setRate(100);
} else {
float rate = BigDecimal.valueOf(100 * (float) dealNum / total)
float rate = BigDecimal.valueOf(100 * (float) resolved / total)
.setScale(2, RoundingMode.HALF_UP)
.floatValue();
alertSummary.setRate(rate);
@@ -154,82 +203,4 @@ public class AlertServiceImpl implements AlertService {
}
return alertSummary;
}
@Override
public void addNewAlertReport(AlertReport alertReport) {
alarmCommonReduce.reduceAndSendAlarm(buildAlertData(alertReport));
}
@Override
public void addNewAlertReportFromCloud(String cloudServiceName, String alertReport) {
CloudServiceAlarmInformationEnum cloudService = CloudServiceAlarmInformationEnum
.getEnumFromCloudServiceName(cloudServiceName);
AlertReport alert = null;
if (cloudService != null) {
try {
CloudAlertReportAbstract cloudAlertReport = JsonUtil
.fromJson(alertReport, cloudService.getCloudServiceAlarmInformationEntity());
assert cloudAlertReport != null;
alert = AlertReport.builder()
.content(cloudAlertReport.getContent())
.alertName(cloudAlertReport.getAlertName())
.alertTime(cloudAlertReport.getAlertTime())
.alertDuration(cloudAlertReport.getAlertDuration())
.priority(cloudAlertReport.getPriority())
.reportType(cloudAlertReport.getReportType())
.labels(cloudAlertReport.getLabels())
.annotations(cloudAlertReport.getAnnotations())
.build();
} catch (Exception e) {
log.error("[alert report] parse cloud service alarm content failed! cloud service: {} conrent: {}",
cloudService.name(), alertReport);
}
} else {
alert = AlertReport.builder()
.content("error do not has cloud service api")
.alertName("/api/alerts/report/" + cloudServiceName)
.alertTime(Instant.now().getEpochSecond())
.priority(1)
.reportType(1)
.build();
}
Optional.ofNullable(alert).ifPresent(this::addNewAlertReport);
}
@Override
public List<Alert> getAlerts(Specification<Alert> specification) {
return alertDao.findAll(specification);
}
/**
* The external alarm information is converted to Alert
* @param alertReport alarm body
* @return Alert entity
*/
private Alert buildAlertData(AlertReport alertReport){
Map<String, String> annotations = alertReport.getAnnotations();
StringBuilder sb = new StringBuilder();
if (alertReport.getContent() == null || alertReport.getContent().length() <= 0){
StringBuilder finalSb = sb;
annotations.forEach((k, v) -> finalSb.append(k).append(":").append(v).append("\n"));
} else {
sb = new StringBuilder(alertReport.getContent());
}
LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(alertReport.getAlertTime()),
ZoneId.systemDefault());
return Alert.builder()
.content("Alert Center\n" + sb)
.priority(alertReport.getPriority().byteValue())
.status(CommonConstants.ALERT_STATUS_CODE_PENDING)
.tags(alertReport.getLabels())
.target(alertReport.getAlertName())
.triggerTimes(1)
.firstAlarmTime(alertReport.getAlertTime())
.lastAlarmTime(alertReport.getAlertTime())
.gmtCreate(dateTime)
.gmtUpdate(dateTime)
.build();
}
}
@@ -27,8 +27,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dao.AlertSilenceDao;
import org.apache.hertzbeat.alert.service.AlertSilenceService;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.cache.CommonCacheService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
@@ -111,7 +109,6 @@ public class AlertSilenceServiceImpl implements AlertSilenceService {
}
private void clearAlertSilencesCache() {
CommonCacheService<String, Object> silenceCache = CacheFactory.getAlertSilenceCache();
silenceCache.remove(CommonConstants.CACHE_ALERT_SILENCE);
CacheFactory.clearAlertSilenceCache();
}
}
@@ -0,0 +1,181 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.service.DataSourceService;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.apache.commons.lang3.tuple.Pair;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* datasource service
*/
@Service
@Slf4j
public class DataSourceServiceImpl implements DataSourceService {
@Autowired(required = false)
private List<QueryExecutor> executors;
private static final Pattern EXPR_PATTERN = Pattern.compile("^(.*?)([><]=?|==|!=)\\s*(\\d+(\\.\\d+)?)$");
@Override
public List<Map<String, Object>> calculate(String datasource, String expr) {
if (executors == null || executors.isEmpty()) {
throw new IllegalArgumentException("No query executor found");
}
QueryExecutor executor = executors.stream().filter(e -> e.support(datasource)).findFirst().orElse(null);
if (executor == null) {
throw new IllegalArgumentException("Unsupported datasource: " + datasource);
}
// todo support multiple expr: and or logic operation
Pair<String, Pair<String, Double>> parsedExpr = parseExpression(expr);
String query = parsedExpr.getLeft();
String operator = parsedExpr.getRight().getLeft();
Double threshold = parsedExpr.getRight().getRight();
try {
List<Map<String, Object>> results = executor.execute(query);
return evaluateResults(results, operator, threshold);
} catch (Exception e) {
log.error("Error executing query on datasource {}: {}", datasource, e.getMessage());
throw new RuntimeException("Query execution failed", e);
}
}
private Pair<String, Pair<String, Double>> parseExpression(String expr) {
Matcher matcher = EXPR_PATTERN.matcher(expr.trim());
if (matcher.find()) {
// promql, sql expr
String query = matcher.group(1).trim();
// operator
String operator = matcher.group(2).trim();
// value
Double threshold = Double.valueOf(matcher.group(3).trim());
return Pair.of(query, Pair.of(operator, threshold));
} else {
throw new IllegalArgumentException("Invalid expression format: " + expr);
}
}
private List<Map<String, Object>> evaluateResults(List<Map<String, Object>> results, String operator, Double threshold) {
List<Map<String, Object>> filteredResults = new ArrayList<>();
for (Map<String, Object> result : results) {
Object values = result.get("__value__");
if (values == null) {
// ignore the query result data is empty
continue;
}
// values may be a list of values, or a single value
Object matchValue = evaluateCondition(values, operator, threshold);
result.put("__value__", matchValue);
// if matchValue is null, mean not match the threshold
// if not null, mean match the threshold
filteredResults.add(result);
}
return filteredResults;
}
private Object evaluateCondition(Object value, String operator, Double threshold) {
// value may be a list of values, or a single value
switch (operator) {
case ">":
// if value is list, return the max value
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.max(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue > threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) > threshold ? value : null;
}
case ">=":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.max(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue >= threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) >= threshold ? value : null;
}
case "<":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.min(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue < threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) < threshold ? value : null;
}
case "<=":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.min(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue <= threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) <= threshold ? value : null;
}
case "==":
if (value instanceof List<?> values) {
for (Object v : values) {
if (v.equals(threshold)) {
return v;
}
}
return null;
} else {
return value.equals(threshold) ? value : null;
}
case "!=":
if (value instanceof List<?> values) {
for (Object v : values) {
if (v.equals(threshold)) {
return null;
}
}
return value;
} else {
return value.equals(threshold) ? null : value;
}
default:
// unsupported operator todo add more operator
return null;
}
}
}
@@ -0,0 +1,86 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service.impl;
import java.time.Instant;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.ExternAlertService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Default external alarm service impl
*/
@Slf4j
@Service
public class DefaultExternAlertService implements ExternAlertService {
@Autowired
private AlarmCommonReduce alarmCommonReduce;
@Override
public void addExternAlert(String content) {
SingleAlert alert = JsonUtil.fromJson(content, SingleAlert.class);
if (alert == null) {
log.warn("parse extern alert content failed! content: {}", content);
throw new IllegalArgumentException("parse extern alert content failed!");
}
alert.setId(null);
String status = alert.getStatus();
if (status == null) {
// can use the startAt and endAt to determine the status
if (alert.getStartAt() != null && alert.getEndAt() != null) {
if (alert.getEndAt() < Instant.now().toEpochMilli()) {
status = CommonConstants.ALERT_STATUS_RESOLVED;
} else {
status = CommonConstants.ALERT_STATUS_FIRING;
}
} else {
status = CommonConstants.ALERT_STATUS_FIRING;
}
}
alert.setStatus(status);
if (CommonConstants.ALERT_STATUS_FIRING.equals(status)) {
alert.setEndAt(null);
if (alert.getStartAt() == null) {
alert.setStartAt(Instant.now().toEpochMilli());
}
if (alert.getActiveAt() == null) {
alert.setActiveAt(Instant.now().toEpochMilli());
}
} else {
alert.setActiveAt(null);
if (alert.getStartAt() == null) {
alert.setStartAt(Instant.now().toEpochMilli());
}
if (alert.getEndAt() == null) {
alert.setEndAt(Instant.now().toEpochMilli());
}
}
alarmCommonReduce.reduceAndSendAlarm(alert);
}
@Override
public String supportSource() {
return "default";
}
}
@@ -15,36 +15,23 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.service.impl;
package org.apache.hertzbeat.alert.service.impl;
import jakarta.persistence.criteria.Predicate;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.cache.CacheFactory;
import org.apache.hertzbeat.common.cache.CommonCacheService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeRule;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.manager.component.alerter.DispatcherAlarm;
import org.apache.hertzbeat.manager.dao.NoticeReceiverDao;
import org.apache.hertzbeat.manager.dao.NoticeRuleDao;
import org.apache.hertzbeat.manager.dao.NoticeTemplateDao;
import org.apache.hertzbeat.manager.service.NoticeConfigService;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.alert.notice.AlertNoticeDispatch;
import org.apache.hertzbeat.alert.dao.NoticeReceiverDao;
import org.apache.hertzbeat.alert.dao.NoticeRuleDao;
import org.apache.hertzbeat.alert.dao.NoticeTemplateDao;
import org.apache.hertzbeat.alert.service.NoticeConfigService;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Lazy;
@@ -52,10 +39,28 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Message notification configuration implementation
*/
@@ -64,11 +69,7 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLineRunner {
private static final String ALERT_TEST_TARGET = "Test Target";
private static final String ALERT_TEST_CONTENT = "test send msg! \\n This is the test data. It is proved that it can be received successfully";
private static final Map<Byte, NoticeTemplate> PRESET_TEMPLATE = new HashMap<>(16);
@Autowired
@@ -82,47 +83,90 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
@Autowired
@Lazy
private DispatcherAlarm dispatcherAlarm;
private AlertNoticeDispatch dispatcherAlarm;
@Override
public List<NoticeReceiver> getNoticeReceivers(String name) {
public Page<NoticeReceiver> getNoticeReceivers(String name, int pageIndex, int pageSize) {
Specification<NoticeReceiver> specification = (root, query, criteriaBuilder) -> {
Predicate predicate = criteriaBuilder.conjunction();
if (StringUtils.isNotBlank(name)) {
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
Predicate predicateName = criteriaBuilder.like(
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
);
predicate = criteriaBuilder.and(predicateName);
}
return predicate;
};
return noticeReceiverDao.findAll(specification);
return noticeReceiverDao.findAll(specification, PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id")));
}
@Override
public List<NoticeTemplate> getNoticeTemplates(String name) {
Specification<NoticeTemplate> specification = (root, query, criteriaBuilder) -> {
Predicate predicate = criteriaBuilder.conjunction();
if (StringUtils.isNotBlank(name)) {
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
predicate = criteriaBuilder.and(predicateName);
public List<NoticeReceiver> getAllNoticeReceivers() {
return noticeReceiverDao.findAll();
}
@Override
public Page<NoticeTemplate> getNoticeTemplates(String name, boolean preset, int pageIndex, int pageSize) {
if (preset) {
// Query preset templates
List<NoticeTemplate> defaultTemplates = new LinkedList<>(PRESET_TEMPLATE.values());
// Filter by name (case-insensitive)
List<NoticeTemplate> filteredDefaultTemplates = defaultTemplates.stream()
.filter(template -> StringUtils.isBlank(name)
|| template.getName().toLowerCase().contains(name.toLowerCase()))
.collect(Collectors.toList());
// Pagination logic
int totalItems = filteredDefaultTemplates.size();
int fromIndex = Math.min(pageIndex * pageSize, totalItems);
int toIndex = Math.min(fromIndex + pageSize, totalItems);
if (fromIndex >= totalItems) {
return new PageImpl<>(Collections.emptyList(), PageRequest.of(pageIndex, pageSize), totalItems);
}
return predicate;
};
List<NoticeTemplate> paginatedTemplates = filteredDefaultTemplates.subList(fromIndex, toIndex);
return new PageImpl<>(paginatedTemplates, PageRequest.of(pageIndex, pageSize), totalItems);
} else {
// Query custom templates
Specification<NoticeTemplate> specification = (root, query, criteriaBuilder) -> {
Predicate predicate = criteriaBuilder.conjunction();
if (StringUtils.isNotBlank(name)) {
Predicate predicateName = criteriaBuilder.like(
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
);
predicate = criteriaBuilder.and(predicateName);
}
return predicate;
};
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id"));
return noticeTemplateDao.findAll(specification, pageRequest);
}
}
@Override
public List<NoticeTemplate> getAllNoticeTemplates() {
List<NoticeTemplate> defaultTemplates = new LinkedList<>(PRESET_TEMPLATE.values());
defaultTemplates.addAll(noticeTemplateDao.findAll(specification));
defaultTemplates.addAll(noticeTemplateDao.findAll());
return defaultTemplates;
}
@Override
public List<NoticeRule> getNoticeRules(String name) {
public Page<NoticeRule> getNoticeRules(String name, int pageIndex, int pageSize) {
Specification<NoticeRule> specification = (root, query, criteriaBuilder) -> {
Predicate predicate = criteriaBuilder.conjunction();
if (StringUtils.isNotBlank(name)) {
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
Predicate predicateName = criteriaBuilder.like(
criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase() + "%"
);
predicate = criteriaBuilder.and(predicateName);
}
return predicate;
};
return noticeRuleDao.findAll(specification);
return noticeRuleDao.findAll(specification, PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id")));
}
@Override
@@ -159,37 +203,28 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
}
@Override
@SuppressWarnings("unchecked")
public List<NoticeRule> getReceiverFilterRule(Alert alert) {
public List<NoticeRule> getReceiverFilterRule(GroupAlert alert) {
// use cache
CommonCacheService<String, Object> noticeCache = CacheFactory.getNoticeCache();
List<NoticeRule> rules = (List<NoticeRule>) noticeCache.get(CommonConstants.CACHE_NOTICE_RULE);
if (CollectionUtils.isEmpty(rules)) {
List<NoticeRule> rules = CacheFactory.getNoticeCache();
if (rules == null) {
rules = noticeRuleDao.findNoticeRulesByEnableTrue();
noticeCache.put(CommonConstants.CACHE_NOTICE_RULE, rules);
CacheFactory.setNoticeCache(rules);
}
// The temporary rule is to forward all, and then implement more matching rules: alarm status selection, monitoring type selection, etc.
return rules.stream()
.filter(rule -> {
if (!rule.isFilterAll()) {
// filter priorities
if (rule.getPriorities() != null && !rule.getPriorities().isEmpty()) {
boolean priorityMatch = rule.getPriorities().stream().anyMatch(item -> item != null && item == alert.getPriority());
if (!priorityMatch) {
return false;
}
}
// filter tags
if (rule.getTags() != null && !rule.getTags().isEmpty()) {
boolean tagMatch = rule.getTags().stream().anyMatch(tagItem -> {
if (!alert.getTags().containsKey(tagItem.getName())) {
// filter labels
if (rule.getLabels() != null && !rule.getLabels().isEmpty()) {
boolean labelMatch = rule.getLabels().entrySet().stream().allMatch(labelItem -> {
if (!alert.getCommonLabels().containsKey(labelItem.getKey())) {
return false;
}
String alertTagValue = alert.getTags().get(tagItem.getName());
return Objects.equals(tagItem.getValue(), alertTagValue);
String alertLabelValue = alert.getCommonLabels().get(labelItem.getKey());
return Objects.equals(labelItem.getValue(), alertLabelValue);
});
if (!tagMatch) {
if (!labelMatch) {
return false;
}
}
@@ -267,28 +302,43 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
@Override
public boolean sendTestMsg(NoticeReceiver noticeReceiver) {
Map<String, String> tags = new HashMap<>(8);
tags.put(CommonConstants.TAG_MONITOR_ID, "100");
tags.put(CommonConstants.TAG_MONITOR_NAME, "100Name");
tags.put(CommonConstants.TAG_MONITOR_HOST, "127.0.0.1");
tags.put(CommonConstants.TAG_THRESHOLD_ID, "200");
Alert alert = Alert.builder()
.tags(tags)
.id(1003445L)
.target(ALERT_TEST_TARGET)
.priority(CommonConstants.ALERT_PRIORITY_CODE_CRITICAL)
.content(ALERT_TEST_CONTENT)
.alertDefineId(200L)
.times(2)
.status((byte) 0)
.firstAlarmTime(System.currentTimeMillis())
.lastAlarmTime(System.currentTimeMillis()).build();
return dispatcherAlarm.sendNoticeMsg(noticeReceiver, null, alert);
Map<String, String> labels = new HashMap<>(8);
labels.put(CommonConstants.LABEL_INSTANCE, "1000000");
labels.put(CommonConstants.LABEL_ALERT_NAME, "CPU Usage Alert");
labels.put(CommonConstants.LABEL_HOST, "127.0.0.1");
Map<String, String> annotations = new HashMap<>(8);
annotations.put("suggest", "Please check the CPU usage of the server");
SingleAlert singleAlert1 = SingleAlert.builder()
.labels(labels)
.content("test send msg! \\n This is the test data. It is proved that it can be received successfully")
.startAt(System.currentTimeMillis())
.activeAt(System.currentTimeMillis())
.endAt(System.currentTimeMillis())
.triggerTimes(2)
.annotations(annotations)
.status("firing")
.build();
SingleAlert singleAlert2 = SingleAlert.builder()
.labels(labels)
.content("test send msg! \\n This is the test data. It is proved that it can be received successfully")
.startAt(System.currentTimeMillis())
.activeAt(System.currentTimeMillis())
.endAt(System.currentTimeMillis())
.triggerTimes(4)
.annotations(annotations)
.status("firing")
.build();
GroupAlert groupAlert = GroupAlert.builder()
.commonLabels(Map.of(CommonConstants.LABEL_ALERT_NAME, "CPU Usage Alert"))
.commonAnnotations(annotations)
.alerts(List.of(singleAlert1, singleAlert2))
.status("firing")
.build();
return dispatcherAlarm.sendNoticeMsg(noticeReceiver, null, groupAlert);
}
private void clearNoticeRulesCache() {
CommonCacheService<String, Object> noticeCache = CacheFactory.getNoticeCache();
noticeCache.remove(CommonConstants.CACHE_NOTICE_RULE);
CacheFactory.clearNoticeCache();
}
@Override
@@ -0,0 +1,99 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dto.PrometheusExternAlert;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.ExternAlertService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* Prometheus external alarm service impl
*/
@Slf4j
@Service
public class PrometheusExternAlertService implements ExternAlertService {
@Autowired
private AlarmCommonReduce alarmCommonReduce;
@Override
public void addExternAlert(String content) {
TypeReference<List<PrometheusExternAlert>> typeReference = new TypeReference<>() {};
List<PrometheusExternAlert> alerts = JsonUtil.fromJson(content, typeReference);
if (alerts == null || alerts.isEmpty()) {
log.warn("parse prometheus extern alert content failed! content: {}", content);
return;
}
for (PrometheusExternAlert alert : alerts) {
Map<String, String> annotations = alert.getAnnotations();
if (annotations == null) {
annotations = new HashMap<>(8);
}
if (StringUtils.hasText(alert.getGeneratorURL())) {
annotations.put("generatorURL", alert.getGeneratorURL());
}
String description = annotations.get("description");
if (description == null) {
description = annotations.get("summary");
}
if (description == null) {
description = annotations.values().stream().findFirst().orElse("");
}
Map<String, String> labels = alert.getLabels();
if (labels == null) {
labels = new HashMap<>(8);
}
labels.put("__source__", "prometheus");
String status = CommonConstants.ALERT_STATUS_FIRING;
if (alert.getEndsAt() != null && alert.getEndsAt().isBefore(Instant.now())) {
status = CommonConstants.ALERT_STATUS_RESOLVED;
}
SingleAlert singleAlert = SingleAlert.builder()
.content(description)
.status(status)
.activeAt(CommonConstants.ALERT_STATUS_FIRING.equals(status) ? Instant.now().toEpochMilli() : null)
.startAt(alert.getStartsAt() != null ? alert.getStartsAt().toEpochMilli() : Instant.now().toEpochMilli())
.endAt(CommonConstants.ALERT_STATUS_RESOLVED.equals(status) ? alert.getEndsAt().toEpochMilli() : null)
.labels(labels)
.annotations(alert.getAnnotations())
.triggerTimes(1)
.build();
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
}
}
@Override
public String supportSource() {
return "prometheus";
}
}
@@ -0,0 +1,55 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dto.TencentCloudExternAlert;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.ExternAlertService;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Default external alarm service impl
*/
@Slf4j
@Service
public class TencentExternAlertService implements ExternAlertService {
@Autowired
private AlarmCommonReduce alarmCommonReduce;
@Override
public void addExternAlert(String content) {
TencentCloudExternAlert report = JsonUtil.fromJson(content, TencentCloudExternAlert.class);
if (report == null) {
log.warn("parse extern alert content failed! content: {}", content);
return;
}
// todo convert TenCloudAlertReport to SingleAlert
SingleAlert alert = new SingleAlert();
alarmCommonReduce.reduceAndSendAlarm(alert);
}
@Override
public String supportSource() {
return "tencent";
}
}
@@ -0,0 +1,32 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
alerter.availability.recover = 可用性警報已解決,監視狀態現在正常
alerter.alarm.recover = 警報解決通知
alerter.notify.title = HertzBeat 警報通知
alerter.notify.target = 監視目標
alerter.notify.monitorId = 監視 ID
alerter.notify.monitorName = 監視名稱
alerter.notify.monitorHost = 監視主機
alerter.notify.priority = 警報優先級
alerter.notify.triggerTime = 警報觸發時間
alerter.notify.restoreTime = 警報恢復時間
alerter.notify.times = 警報觸發次數
alerter.notify.tags = 警報標籤
alerter.notify.content = 警報內容
alerter.notify.console = 控制台登錄
alerter.priority.0 = 緊急警報
alerter.priority.1 = 嚴重警報
alerter.priority.2 = 警告警報
@@ -64,12 +64,8 @@ class AlertDefineControllerTest {
this.alertDefine = AlertDefine.builder()
.id(1L)
.app("app")
.metric("test")
.field("test")
.preset(false)
.name("alertDefine")
.expr("1 > 0")
.priority((byte) 1)
.times(1)
.template("template")
.creator("tom")
@@ -125,9 +121,6 @@ class AlertDefineControllerTest {
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.data.id").value(alertDefine.getId()))
.andExpect(jsonPath("$.data.app").value(alertDefine.getApp()))
.andExpect(jsonPath("$.data.metric").value(alertDefine.getMetric()))
.andExpect(jsonPath("$.data.field").value(alertDefine.getField()))
.andExpect(jsonPath("$.data.expr").value(alertDefine.getExpr()))
.andExpect(jsonPath("$.data.template").value(alertDefine.getTemplate()))
.andExpect(jsonPath("$.data.gmtCreate").value(alertDefine.getGmtCreate()))
@@ -144,28 +137,4 @@ class AlertDefineControllerTest {
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
@Test
void applyAlertDefineMonitorsBind() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/api/alert/define/" + this.alertDefine.getId() + "/monitors")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(this.alertDefineMonitorBinds)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
@Test
void getAlertDefineMonitorsBind() throws Exception {
Mockito.when(alertDefineService.getBindAlertDefineMonitors(this.alertDefine.getId()))
.thenReturn(this.alertDefineMonitorBinds);
mockMvc.perform(MockMvcRequestBuilders.get("/api/alert/define/" + this.alertDefine.getId() + "/monitors")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.data[0].id").value(alertDefineMonitorBinds.get(0).getId()))
.andExpect(jsonPath("$.data[0].monitor.id").value(alertDefineMonitorBinds.get(0).getMonitor().getId()))
.andReturn();
}
}
@@ -19,10 +19,8 @@ package org.apache.hertzbeat.alert.controller;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -92,8 +90,7 @@ class AlertDefinesControllerTest {
pageRequest = PageRequest.of((Integer) content.get("pageIndex"), (Integer) content.get("pageSize"), sortExp);
}
// @Test
// todo: fix this test
@Test
void getAlertDefines() throws Exception {
// Test the correctness of the mock
@@ -110,32 +107,28 @@ class AlertDefinesControllerTest {
// return false;
// }
// }))).thenReturn(new PageImpl<AlertDefine>(new ArrayList<AlertDefine>()));
AlertDefine define = AlertDefine.builder().id(9L).app("linux").metric("disk").field("usage").expr("x").times(1).tags(new LinkedList<>()).build();
Mockito.when(alertDefineService.getAlertDefines(null, null, null, "id", "desc", 1, 10)).thenReturn(new PageImpl<>(Collections.singletonList(define)));
AlertDefine define = AlertDefine.builder().id(9L).expr("x").times(1).build();
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Order.asc(sort)));
Mockito.when(alertDefineService.getAlertDefines(
Mockito.eq(ids),
Mockito.isNull(),
Mockito.eq(sort),
Mockito.eq(order),
Mockito.eq(pageIndex),
Mockito.eq(pageSize)
)).thenReturn(new PageImpl<>(Collections.singletonList(define), pageRequest, 1));
mockMvc.perform(MockMvcRequestBuilders.get(
"/api/alert/defines")
mockMvc.perform(MockMvcRequestBuilders.get("/api/alert/defines")
.param("ids", ids.toString().substring(1, ids.toString().length() - 1))
.param("priority", priority.toString())
.param("sort", sort)
.param("order", order)
.param("pageIndex", pageIndex.toString())
.param("pageSize", pageSize.toString()))
.param("pageIndex", String.valueOf(pageIndex))
.param("pageSize", String.valueOf(pageSize)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.data.content").value(new ArrayList<>()))
.andExpect(jsonPath("$.data.pageable").value("INSTANCE"))
.andExpect(jsonPath("$.data.totalPages").value(1))
.andExpect(jsonPath("$.data.totalElements").value(0))
.andExpect(jsonPath("$.data.last").value(true))
.andExpect(jsonPath("$.data.number").value(0))
.andExpect(jsonPath("$.data.size").value(0))
.andExpect(jsonPath("$.data.first").value(true))
.andExpect(jsonPath("$.data.numberOfElements").value(0))
.andExpect(jsonPath("$.data.empty").value(true))
.andExpect(jsonPath("$.data.sort.empty").value(true))
.andExpect(jsonPath("$.data.sort.sorted").value(false))
.andExpect(jsonPath("$.data.sort.unsorted").value(true))
.andExpect(jsonPath("$.data.content[0].id").value(9))
.andExpect(jsonPath("$.data.content[0].expr").value("x"))
.andExpect(jsonPath("$.data.content[0].times").value(1))
.andReturn();
}
@@ -27,9 +27,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import org.apache.hertzbeat.alert.service.AlertConvergeService;
import org.apache.hertzbeat.alert.service.AlertGroupConvergeService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -41,28 +42,27 @@ import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
/**
* test case for {@link AlertConvergeController}
* test case for {@link AlertGroupConvergeController}
*/
@ExtendWith(MockitoExtension.class)
public class AlertConvergeControllerTest {
public class AlertGroupConvergeControllerTest {
private MockMvc mockMvc;
@Mock
private AlertConvergeService alertConvergeService;
private AlertGroupConvergeService alertGroupConvergeService;
private AlertConverge alertConverge;
private AlertGroupConverge alertGroupConverge;
@InjectMocks
private AlertConvergeController alertConvergeController;
private AlertGroupConvergeController alertGroupConvergeController;
@BeforeEach
void setUp() {
this.mockMvc = standaloneSetup(alertConvergeController).build();
this.mockMvc = standaloneSetup(alertGroupConvergeController).build();
alertConverge = AlertConverge.builder()
alertGroupConverge = AlertGroupConverge.builder()
.name("test")
.creator("admin")
.modifier("admin")
@@ -71,54 +71,53 @@ public class AlertConvergeControllerTest {
}
@Test
void testAddNewAlertConverge() throws Exception {
void testAddNewAlertGroupConverge() throws Exception {
doNothing().when(alertConvergeService).validate(any(AlertConverge.class), eq(false));
doNothing().when(alertConvergeService).addAlertConverge(any(AlertConverge.class));
doNothing().when(alertGroupConvergeService).validate(any(AlertGroupConverge.class), eq(false));
doNothing().when(alertGroupConvergeService).addAlertGroupConverge(any(AlertGroupConverge.class));
mockMvc.perform(post("/api/alert/converge")
mockMvc.perform(post("/api/alert/group")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(alertConverge))
).andExpect(status().isOk())
.content(JsonUtil.toJson(alertGroupConverge)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.msg").value("Add success"));
}
@Test
void testModifyAlertConverge() throws Exception {
void testModifyAlertGroupConverge() throws Exception {
doNothing().when(alertConvergeService).validate(any(AlertConverge.class), eq(true));
doNothing().when(alertConvergeService).modifyAlertConverge(any(AlertConverge.class));
doNothing().when(alertGroupConvergeService).validate(any(AlertGroupConverge.class), eq(true));
doNothing().when(alertGroupConvergeService).modifyAlertGroupConverge(any(AlertGroupConverge.class));
mockMvc.perform(put("/api/alert/converge")
mockMvc.perform(put("/api/alert/group")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(alertConverge))
).andExpect(status().isOk())
.content(JsonUtil.toJson(alertGroupConverge)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.msg").value("Modify success"));
}
@Test
void testGetAlertConvergeExists() throws Exception {
void testGetAlertGroupConvergeExists() throws Exception {
when(alertConvergeService.getAlertConverge(1L)).thenReturn(alertConverge);
when(alertGroupConvergeService.getAlertGroupConverge(1L)).thenReturn(alertGroupConverge);
mockMvc.perform(get("/api/alert/converge/{id}", 1L)
mockMvc.perform(get("/api/alert/group/{id}", 1L)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.id").value(alertConverge.getId()));
.andExpect(jsonPath("$.data.id").value(alertGroupConverge.getId()));
}
@Test
void testGetAlertConvergeNotExists() throws Exception {
void testGetAlertGroupConvergeNotExists() throws Exception {
when(alertConvergeService.getAlertConverge(1L)).thenReturn(null);
when(alertGroupConvergeService.getAlertGroupConverge(1L)).thenReturn(null);
mockMvc.perform(get("/api/alert/converge/{id}", 1L)
mockMvc.perform(get("/api/alert/group/{id}", 1L)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.MONITOR_NOT_EXIST_CODE))
.andExpect(jsonPath("$.msg").value("AlertConverge not exist."));
.andExpect(jsonPath("$.msg").value("Alert Group Converge not exist."));
}
}
@@ -29,9 +29,9 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import org.apache.hertzbeat.alert.service.AlertConvergeService;
import org.apache.hertzbeat.alert.service.AlertGroupConvergeService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -47,58 +47,59 @@ import org.springframework.test.web.servlet.MockMvc;
/**
* test case for {@link AlertConvergesController}
* test case for {@link AlertGroupConvergesController}
*/
@ExtendWith(MockitoExtension.class)
class AlertConvergesControllerTest {
class AlertGroupConvergesControllerTest {
private MockMvc mockMvc;
@Mock
private AlertConvergeService alertConvergeService;
private AlertGroupConvergeService alertGroupConvergeService;
@InjectMocks
private AlertConvergesController alertConvergesController;
private AlertGroupConvergesController alertGroupConvergesController;
private List<AlertConverge> alertConvergeList;
private List<AlertGroupConverge> alertGroupConvergeList;
@BeforeEach
void setUp() {
this.mockMvc = standaloneSetup(alertConvergesController).build();
this.mockMvc = standaloneSetup(alertGroupConvergesController).build();
AlertConverge alertConverge1 = AlertConverge.builder()
AlertGroupConverge alertGroupConverge1 = AlertGroupConverge.builder()
.name("Converge1")
.id(1L)
.build();
AlertConverge alertConverge2 = AlertConverge.builder()
AlertGroupConverge alertGroupConverge2 = AlertGroupConverge.builder()
.name("Converge2")
.id(2L)
.build();
alertConvergeList = Arrays.asList(alertConverge1, alertConverge2);
alertGroupConvergeList = Arrays.asList(alertGroupConverge1, alertGroupConverge2);
}
@Test
void testGetAlertConverges() throws Exception {
void testGetAlertGroupConverges() throws Exception {
Page<AlertConverge> alertConvergePage = new PageImpl<>(
alertConvergeList,
Page<AlertGroupConverge> alertGroupConvergePage = new PageImpl<>(
alertGroupConvergeList,
PageRequest.of(0, 8, Sort.by("id").descending()),
alertConvergeList.size()
alertGroupConvergeList.size()
);
when(alertConvergeService.getAlertConverges(null, null, "id", "desc", 0, 8)).thenReturn(alertConvergePage);
when(alertGroupConvergeService.getAlertGroupConverges(null, null, "id", "desc", 0, 8)).thenReturn(alertGroupConvergePage);
mockMvc.perform(get("/api/alert/converges")
mockMvc.perform(get("/api/alert/groups")
.param("pageIndex", "0")
.param("pageSize", "8")
.param("sort", "id")
.param("order", "desc")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.data.content[0].id").value(1))
.andExpect(jsonPath("$.data.content[0].name").value("Converge1"))
.andExpect(jsonPath("$.data.content[1].id").value(2))
@@ -108,13 +109,12 @@ class AlertConvergesControllerTest {
@Test
void testDeleteAlertDefines() throws Exception {
doNothing().when(alertConvergeService).deleteAlertConverges(eq(new HashSet<>(Arrays.asList(1L, 2L))));
doNothing().when(alertGroupConvergeService).deleteAlertGroupConverges(eq(new HashSet<>(Arrays.asList(1L, 2L))));
mockMvc.perform(delete("/api/alert/converges")
mockMvc.perform(delete("/api/alert/groups")
.param("ids", "1,2")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE));
}
}
}
@@ -17,15 +17,19 @@
package org.apache.hertzbeat.alert.controller;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.apache.hertzbeat.alert.dto.GeneralCloudAlertReport;
import org.apache.hertzbeat.alert.dto.TenCloudAlertReport;
import org.apache.hertzbeat.alert.service.AlertService;
import java.util.HashMap;
import org.apache.hertzbeat.alert.dto.TencentCloudExternAlert;
import org.apache.hertzbeat.alert.service.ExternAlertService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -36,16 +40,18 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
/**
* unit test for {@link AlertReportController }
*/
@Disabled
@ExtendWith(MockitoExtension.class)
class AlertReportControllerTest {
private MockMvc mockMvc;
@Mock
private AlertService alertService;
private ExternAlertService externAlertService;
@InjectMocks
private AlertReportController alertReportController;
@@ -57,17 +63,17 @@ class AlertReportControllerTest {
@Test
void addNewAlertReportTencent() throws Exception {
TenCloudAlertReport.Dimensions dimensions = new TenCloudAlertReport.Dimensions();
TencentCloudExternAlert.Dimensions dimensions = new TencentCloudExternAlert.Dimensions();
dimensions.setUnInstanceId("3333");
TenCloudAlertReport.AlarmObjInfo alarmObjInfo = new TenCloudAlertReport.AlarmObjInfo();
TencentCloudExternAlert.AlarmObjInfo alarmObjInfo = new TencentCloudExternAlert.AlarmObjInfo();
alarmObjInfo.setRegion("Guangzhou");
alarmObjInfo.setNamespace("Guangzhou1");
alarmObjInfo.setAppId("1111");
alarmObjInfo.setUin("2222");
alarmObjInfo.setDimensions(dimensions);
TenCloudAlertReport.Conditions conditions = new TenCloudAlertReport.Conditions();
TencentCloudExternAlert.Conditions conditions = new TencentCloudExternAlert.Conditions();
conditions.setMetricName("xx");
conditions.setMetricShowName("xxx");
conditions.setCalcType("a");
@@ -80,12 +86,12 @@ class AlertReportControllerTest {
conditions.setEventName("CVS");
conditions.setEventShowName("Core error");
TenCloudAlertReport.AlarmPolicyInfo alarmPolicyInfo = new TenCloudAlertReport.AlarmPolicyInfo();
TencentCloudExternAlert.AlarmPolicyInfo alarmPolicyInfo = new TencentCloudExternAlert.AlarmPolicyInfo();
alarmPolicyInfo.setPolicyTypeCname("x");
alarmPolicyInfo.setPolicyName("Test1");
alarmPolicyInfo.setConditions(conditions);
TenCloudAlertReport report = TenCloudAlertReport.builder()
TencentCloudExternAlert report = TencentCloudExternAlert.builder()
.sessionId("123")
.alarmStatus("1")
.alarmType("event")
@@ -94,31 +100,40 @@ class AlertReportControllerTest {
.alarmObjInfo(alarmObjInfo)
.alarmPolicyInfo(alarmPolicyInfo)
.build();
when(externAlertService.supportSource()).thenReturn("tencent");
doNothing().when(externAlertService).addExternAlert(anyString());
mockMvc.perform(
MockMvcRequestBuilders
.post("/api/alerts/report/tencloud")
.post("/api/alerts/report/tencent")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(report))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(content().json("{\"data\":null,\"msg\":\"Add report success\",\"code\":0}"))
.andReturn();
}
@Test
void addNewAlertReport() throws Exception {
GeneralCloudAlertReport generalCloudAlertReport = new GeneralCloudAlertReport();
generalCloudAlertReport.setAlertDateTime("2023-02-22T07:27:15.404000000Z");
SingleAlert singleAlert = SingleAlert.builder()
.fingerprint("fingerprint")
.labels(new HashMap<>())
.annotations(new HashMap<>())
.content("content")
.status("firing")
.triggerTimes(1)
.startAt(1734005477630L)
.activeAt(1734005477630L)
.build();
when(externAlertService.supportSource()).thenReturn("default");
doNothing().when(externAlertService).addExternAlert(anyString());
mockMvc.perform(MockMvcRequestBuilders
.post("/api/alerts/report")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(generalCloudAlertReport))
.content(JsonUtil.toJson(singleAlert))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(content().json("{\"data\":null,\"msg\":\"Add report success\",\"code\":0}"))
.andReturn();
}
}
@@ -27,7 +27,7 @@ import java.util.stream.LongStream;
import org.apache.hertzbeat.alert.dto.AlertSummary;
import org.apache.hertzbeat.alert.service.AlertService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.Alert;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -71,28 +71,23 @@ class AlertsControllerTest {
void getAlerts() throws Exception {
String sortField = "id";
String orderType = "desc";
Byte priority = 1;
Byte status = 1;
Long monitorId = 1L;
String status = "firing";
String content = "test";
int pageIndex = 0;
int pageSize = 10;
Page<Alert> alertPage = new PageImpl<>(
Collections.singletonList(Alert.builder().build()),
Page<GroupAlert> alertPage = new PageImpl<>(
Collections.singletonList(GroupAlert.builder().build()),
PageRequest.of(pageIndex, pageSize, Sort.by(sortField).descending()),
ids.size()
);
Mockito.when(alertService.getAlerts(ids, monitorId, priority, status, content, sortField, orderType, pageIndex, pageSize))
Mockito.when(alertService.getGroupAlerts(status, content, sortField, orderType, pageIndex, pageSize))
.thenReturn(alertPage);
mockMvc.perform(MockMvcRequestBuilders
.get("/api/alerts")
.param("ids", ids.stream().map(String::valueOf).collect(Collectors.joining(",")))
.param("monitorId", String.valueOf(monitorId))
.param("priority", String.valueOf(priority))
.param("status", String.valueOf(status))
.param("content", content)
.get("/api/alerts/group")
.param("status", status)
.param("search", content)
.param("sort", sortField)
.param("order", orderType)
.param("pageIndex", String.valueOf(pageIndex))
@@ -105,10 +100,10 @@ class AlertsControllerTest {
}
@Test
void deleteAlerts() throws Exception {
void deleteGroupAlerts() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders
.delete("/api/alerts")
.delete("/api/alerts/group")
.param("ids", ids.stream().map(String::valueOf).collect(Collectors.joining(",")))
)
.andExpect(status().isOk())
@@ -118,22 +113,10 @@ class AlertsControllerTest {
}
@Test
void clearAllAlerts() throws Exception {
void applyGroupAlertStatus() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders
.delete("/api/alerts/clear")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(content().json("{\"data\":null,\"msg\":null,\"code\":0}"))
.andReturn();
}
@Test
void applyAlertDefinesStatus() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders
.put("/api/alerts/status/1")
.put("/api/alerts/group/status/resolved")
.param("ids", ids.stream().map(String::valueOf).collect(Collectors.joining(",")))
)
.andExpect(status().isOk())
@@ -15,9 +15,8 @@
* limitations under the License.
*/
package org.apache.hertzbeat.manager.controller;
package org.apache.hertzbeat.alert.controller;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -28,17 +27,15 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.apache.hertzbeat.alert.service.impl.NoticeConfigServiceImpl;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.manager.NoticeReceiver;
import org.apache.hertzbeat.common.entity.manager.NoticeRule;
import org.apache.hertzbeat.common.entity.manager.NoticeTemplate;
import org.apache.hertzbeat.common.entity.manager.TagItem;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeRule;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.manager.service.impl.NoticeConfigServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -46,6 +43,10 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@@ -67,12 +68,6 @@ class NoticeConfigControllerTest {
public NoticeRule getNoticeRule() {
List<TagItem> tags = new ArrayList<>();
TagItem tagItem = new TagItem();
tagItem.setName("key1");
tagItem.setValue("value1");
tags.add(tagItem);
NoticeRule noticeRule = new NoticeRule();
noticeRule.setId(87584674384L);
noticeRule.setName("dispatch-1");
@@ -82,7 +77,6 @@ class NoticeConfigControllerTest {
noticeRule.setTemplateName("test");
noticeRule.setCreator("tom");
noticeRule.setModifier("tom");
noticeRule.setTags(tags);
return noticeRule;
}
@@ -177,12 +171,38 @@ class NoticeConfigControllerTest {
@Test
void getReceivers() throws Exception {
NoticeReceiver receiver1 = new NoticeReceiver();
receiver1.setId(1L);
receiver1.setName("Receiver1");
//Mockito.when(noticeConfigService.getNoticeReceivers())
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receivers?name={name}", "tom"))
NoticeReceiver receiver2 = new NoticeReceiver();
receiver2.setId(2L);
receiver2.setName("Receiver2");
Page<NoticeReceiver> receiverPage = new PageImpl<>(
Arrays.asList(receiver1, receiver2),
PageRequest.of(0, 8, Sort.by("id").descending()),
2
);
when(noticeConfigService.getNoticeReceivers("Receiver", 0, 8)).thenReturn(receiverPage);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receivers")
.param("name", "Receiver")
.param("pageIndex", "0")
.param("pageSize", "8")
.param("sort", "id")
.param("order", "desc")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
.andExpect(jsonPath("$.data.content[0].id").value(1))
.andExpect(jsonPath("$.data.content[0].name").value("Receiver1"))
.andExpect(jsonPath("$.data.content[1].id").value(2))
.andExpect(jsonPath("$.data.content[1].name").value("Receiver2"))
.andExpect(jsonPath("$.data.totalElements").value(2))
.andExpect(jsonPath("$.data.totalPages").value(1))
.andExpect(jsonPath("$.data.size").value(8))
.andExpect(jsonPath("$.data.number").value(0));
}
@Test
@@ -254,15 +274,38 @@ class NoticeConfigControllerTest {
@Test
void getRules() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
NoticeRule rule1 = new NoticeRule();
rule1.setId(1L);
rule1.setName("Rule1");
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules?name={name}", "tom"))
NoticeRule rule2 = new NoticeRule();
rule2.setId(2L);
rule2.setName("Rule2");
Page<NoticeRule> rulePage = new PageImpl<>(
Arrays.asList(rule1, rule2),
PageRequest.of(0, 8, Sort.by("id").descending()),
2
);
when(noticeConfigService.getNoticeRules("Rule", 0, 8)).thenReturn(rulePage);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/rules")
.param("name", "Rule")
.param("pageIndex", "0")
.param("pageSize", "8")
.param("sort", "id")
.param("order", "desc")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
.andExpect(jsonPath("$.data.content[0].id").value(1))
.andExpect(jsonPath("$.data.content[0].name").value("Rule1"))
.andExpect(jsonPath("$.data.content[1].id").value(2))
.andExpect(jsonPath("$.data.content[1].name").value("Rule2"))
.andExpect(jsonPath("$.data.totalElements").value(2))
.andExpect(jsonPath("$.data.totalPages").value(1))
.andExpect(jsonPath("$.data.size").value(8))
.andExpect(jsonPath("$.data.number").value(0));
}
@Test
@@ -371,22 +414,40 @@ class NoticeConfigControllerTest {
}
@Test
void testGetTemplates() throws Exception {
// Mock the service response
void getTemplates() throws Exception {
NoticeTemplate template1 = new NoticeTemplate();
template1.setId(1L);
template1.setName("Template1");
NoticeTemplate template2 = new NoticeTemplate();
template2.setName("Template2");
List<NoticeTemplate> templates = Arrays.asList(template1, template2);
when(noticeConfigService.getNoticeTemplates(any())).thenReturn(templates);
// Perform the GET request and verify the response
this.mockMvc.perform(get("/api/notice/templates")
.param("name", "Template"))
NoticeTemplate template2 = new NoticeTemplate();
template2.setId(2L);
template2.setName("Template2");
Page<NoticeTemplate> templatePage = new PageImpl<>(
Arrays.asList(template1, template2),
PageRequest.of(0, 8, Sort.by("id").descending()),
2
);
when(noticeConfigService.getNoticeTemplates("Template", true, 0, 8)).thenReturn(templatePage);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/templates")
.param("name", "Template")
.param("preset", "true")
.param("pageIndex", "0")
.param("pageSize", "8")
.param("sort", "id")
.param("order", "desc")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.data[0].name").value("Template1"))
.andExpect(jsonPath("$.data[1].name").value("Template2"));
.andExpect(jsonPath("$.data.content[0].id").value(1))
.andExpect(jsonPath("$.data.content[0].name").value("Template1"))
.andExpect(jsonPath("$.data.content[1].id").value(2))
.andExpect(jsonPath("$.data.content[1].name").value("Template2"))
.andExpect(jsonPath("$.data.totalElements").value(2))
.andExpect(jsonPath("$.data.totalPages").value(1))
.andExpect(jsonPath("$.data.size").value(8))
.andExpect(jsonPath("$.data.number").value(0));
}
@Test
@@ -423,5 +484,25 @@ class NoticeConfigControllerTest {
verify(noticeConfigService, times(1)).sendTestMsg(noticeReceiver);
}
@Test
void getAllTemplates() throws Exception {
List<NoticeTemplate> templates = Arrays.asList(new NoticeTemplate(), new NoticeTemplate());
when(noticeConfigService.getAllNoticeTemplates()).thenReturn(templates);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/templates/all"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
@Test
void getAllReceivers() throws Exception {
List<NoticeReceiver> receivers = Arrays.asList(new NoticeReceiver(), new NoticeReceiver());
when(noticeConfigService.getAllNoticeReceivers()).thenReturn(receivers);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/notice/receivers/all"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
}
@@ -0,0 +1,152 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.notice;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import org.apache.hertzbeat.alert.AlerterWorkerPool;
import org.apache.hertzbeat.alert.config.AlertSseManager;
import org.apache.hertzbeat.alert.service.NoticeConfigService;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.plugin.runner.PluginRunner;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
/**
* Test case for Alert Notice Dispatch
*/
@ExtendWith(MockitoExtension.class)
class AlertNoticeDispatchTest {
@Mock
private AlerterWorkerPool workerPool;
@Mock
private NoticeConfigService noticeConfigService;
@Mock
private AlertStoreHandler alertStoreHandler;
@Mock
private PluginRunner pluginRunner;
@Mock
private AlertNotifyHandler alertNotifyHandler;
@Mock
private AlertSseManager emitterManager;
private AlertNoticeDispatch alertNoticeDispatch;
private static final int DISPATCH_THREADS = 3;
private NoticeReceiver receiver;
private GroupAlert alert;
@BeforeEach
void setUp() {
when(alertNotifyHandler.type()).thenReturn((byte) 1);
List<AlertNotifyHandler> alertNotifyHandlerList = List.of(alertNotifyHandler);
alertNoticeDispatch = new AlertNoticeDispatch(
workerPool,
noticeConfigService,
alertStoreHandler,
alertNotifyHandlerList,
pluginRunner,
emitterManager
);
receiver = NoticeReceiver.builder()
.id(1L)
.name("test-receiver")
.type((byte) 1)
.build();
alert = GroupAlert.builder()
.id(1L)
.status("firing")
.alerts(Collections.singletonList(SingleAlert.builder()
.content("test-content")
.build()))
.build();
}
@Test
void testSendNoticeMsg() {
NoticeTemplate template = new NoticeTemplate();
template.setId(1L);
template.setName("default-template");
when(noticeConfigService.getDefaultNoticeTemplateByType((byte) 1)).thenReturn(template);
doNothing().when(alertNotifyHandler).send(eq(receiver), eq(template), eq(alert));
assertTrue(alertNoticeDispatch.sendNoticeMsg(receiver, null, alert));
verify(alertNotifyHandler).send(eq(receiver), eq(template), eq(alert));
}
@Test
void testSendNoticeMsgReceiverNull() {
GroupAlert alert = new GroupAlert();
alert.setId(1L);
alert.setStatus("firing");
boolean result = alertNoticeDispatch.sendNoticeMsg(null, null, alert);
assertFalse(result);
}
@Test
void testSendNoticeMsgReceiverTypeNull() {
NoticeReceiver receiver = new NoticeReceiver();
receiver.setId(1L);
receiver.setName("test-receiver");
GroupAlert alert = new GroupAlert();
alert.setId(1L);
alert.setStatus("firing");
boolean result = alertNoticeDispatch.sendNoticeMsg(receiver, null, alert);
assertFalse(result);
}
@Test
void testSendNoticeMsgNoHandler() {
NoticeReceiver receiver = new NoticeReceiver();
receiver.setId(1L);
receiver.setName("test-receiver");
receiver.setType((byte) 2);
GroupAlert alert = new GroupAlert();
alert.setId(1L);
alert.setStatus("firing");
assertFalse(alertNoticeDispatch.sendNoticeMsg(receiver, null, alert));
}
}
@@ -0,0 +1,122 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.notice.impl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.hertzbeat.alert.dao.GroupAlertDao;
import org.apache.hertzbeat.alert.dao.SingleAlertDao;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList;
import java.util.List;
/**
* Test case for {@link DbAlertStoreHandlerImpl}
*/
@ExtendWith(MockitoExtension.class)
class DbAlertStoreHandlerImplTest {
@Mock
private GroupAlertDao groupAlertDao;
@Mock
private SingleAlertDao singleAlertDao;
@InjectMocks
private DbAlertStoreHandlerImpl dbAlertStoreHandler;
private GroupAlert groupAlert;
private SingleAlert singleAlert;
@BeforeEach
public void setUp() {
groupAlert = new GroupAlert();
singleAlert = new SingleAlert();
List<SingleAlert> alerts = new ArrayList<>();
alerts.add(singleAlert);
groupAlert.setAlerts(alerts);
}
@Test
public void testStoreEmptyAlerts() {
GroupAlert emptyGroupAlert = new GroupAlert();
dbAlertStoreHandler.store(emptyGroupAlert);
verify(groupAlertDao, never()).save(any(GroupAlert.class));
}
@Test
public void testStoreNewAlert() {
String groupKey = "test-group";
groupAlert.setGroupKey(groupKey);
when(groupAlertDao.findByGroupKey(groupKey)).thenReturn(null);
SingleAlert savedSingleAlert = new SingleAlert();
when(singleAlertDao.save(any(SingleAlert.class))).thenReturn(savedSingleAlert);
GroupAlert savedGroupAlert = new GroupAlert();
when(groupAlertDao.save(any(GroupAlert.class))).thenReturn(savedGroupAlert);
dbAlertStoreHandler.store(groupAlert);
verify(singleAlertDao).save(any(SingleAlert.class));
verify(groupAlertDao).save(groupAlert);
}
@Test
public void testStoreExistingAlert() {
String groupKey = "test-group";
String fingerprint = "test-fingerprint";
groupAlert.setGroupKey(groupKey);
singleAlert.setFingerprint(fingerprint);
GroupAlert existingGroup = new GroupAlert();
existingGroup.setId(1L);
when(groupAlertDao.findByGroupKey(groupKey)).thenReturn(existingGroup);
SingleAlert existingAlert = new SingleAlert();
existingAlert.setId(1L);
existingAlert.setStatus("firing");
existingAlert.setStartAt(1000L);
existingAlert.setActiveAt(2000L);
existingAlert.setTriggerTimes(1);
when(singleAlertDao.findByFingerprint(fingerprint)).thenReturn(existingAlert);
when(singleAlertDao.save(any(SingleAlert.class))).thenReturn(existingAlert);
when(groupAlertDao.save(any(GroupAlert.class))).thenReturn(existingGroup);
dbAlertStoreHandler.store(groupAlert);
verify(singleAlertDao).save(any(SingleAlert.class));
verify(groupAlertDao).save(groupAlert);
assertEquals(1L, groupAlert.getId());
}
}

Some files were not shown because too many files have changed in this diff Show More