...
 
Commits (48)
    https://gitcode.net/hertzbeat/hertzbeat/-/commit/a8c5d26286166dbc9f53860660b48bfbc6e0fa65 support collector run as an independent process (#1060) 2023-07-03T10:30:23+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/cdf9ae0320c00325512fcc27b26379cfcad031b8 update monitoring oracle percentage metrics sql script (#1065) 2023-07-03T17:07:07+08:00 richar2022 129016397+richar2022@users.noreply.github.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/08b1c70da6f1a00aacd5bcac5fbb1a755d366b7a bugfix some email server config can not send message (#1066) 2023-07-03T17:07:28+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/2f8f3f70fec73b1b8a95e606678b880ff5676569 bugfix linux shell echo field value is too long and causes a line break (#1068) 2023-07-03T17:13:00+08:00 richar2022 129016397+richar2022@users.noreply.github.com Signed-off-by: <span data-trailer="Signed-off-by:"><a href="mailto:129016397+richar2022@users.noreply.github.com" title="129016397+richar2022@users.noreply.github.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg4" style="text-decoration: none">N</a><a href="mailto:129016397+richar2022@users.noreply.github.com" title="129016397+richar2022@users.noreply.github.com">richar2022</a> &lt;<a href="mailto:129016397+richar2022@users.noreply.github.com" title="129016397+richar2022@users.noreply.github.com">129016397+richar2022@users.noreply.github.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/5bd9212e81c2c984d19d77143b91a469c7dbe94b add san346596324 as a contributor for code (#1071) 2023-07-04T14:20:52+08:00 allcontributors[bot] 46447321+allcontributors[bot]@users.noreply.github.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg4" style="text-decoration: none">N</a><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">allcontributors[bot]</a> &lt;<a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">46447321+allcontributors[bot]@users.noreply.github.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/e6764e61bc6cff7e29720b256aaa05aeef456eed remove auto replace uri special char in http request params (#1070) 2023-07-04T14:21:17+08:00 渭雨 30828520+san346596324@users.noreply.github.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/dfd80ed4e97969fe094b3c31f6b305dc4d115a91 bugfix some alarm message can not be triggered normally (#1069) 2023-07-04T17:03:17+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/9828528dde07d99d49b44acbb13cf6668b4aee0f [hertzbeat] release hertzbeat version v1.3.2 (#1044) 2023-07-05T09:56:37+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/2b423856c8fe10a45c4a2457aa4920a34423c4ad [doc] add v1.3.2 publish doc (#1075) 2023-07-06T22:17:54+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/8dbe1ef58837116c27369cbb7ba229db0edd7274 remove elasticsearch unused param index (#1080) 2023-07-08T00:28:21+08:00 Ceilzcx 48920254+Ceilzcx@users.noreply.github.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/666e94513df86d48f3c84ac8e93b85e0021ac88f feature support monitoring apache airflow (#1081) 2023-07-09T23:25:53+08:00 liuxuezhuo 44692579+luoxuanzao@users.noreply.github.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/9a4b1d681451e958c9a71e7ea1a21104009f236c [doc] add luoxuanzao as a contributor for code (#1083) 2023-07-09T23:26:15+08:00 allcontributors[bot] 46447321+allcontributors[bot]@users.noreply.github.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg1" style="text-decoration: none">N</a><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">allcontributors[bot]</a> &lt;<a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">46447321+allcontributors[bot]@users.noreply.github.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/1c3f5b8becc8b06f4906cfddeb129a38f47ed0cd [collector] bugfix sshd cannot use private key to connect (#1084) 2023-07-10T17:01:09+08:00 进击的阿晨 gcwm99@gmail.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:Musk.Chen@fanruan.com" title="Musk.Chen@fanruan.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg4" style="text-decoration: none">N</a><a href="mailto:Musk.Chen@fanruan.com" title="Musk.Chen@fanruan.com">Musk.Chen</a> &lt;<a href="mailto:Musk.Chen@fanruan.com" title="Musk.Chen@fanruan.com">Musk.Chen@fanruan.com</a>&gt;</span> Co-authored-by: <span data-trailer="Co-authored-by:" data-user="23839"><a href="https://gitcode.net/sinat_25235033" title="tomsun28@outlook.com"><img alt="sinat_25235033's avatar" src="https://profile.csdnimg.cn/1/4/A/1_sinat_25235033" class="avatar s16 avatar-inline" title="sinat_25235033"></a><a href="https://gitcode.net/sinat_25235033" title="tomsun28@outlook.com">tomsun28</a> &lt;<a href="mailto:tomsun28@outlook.com" title="tomsun28@outlook.com">tomsun28@outlook.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/0ea873081b405ec04fe01e7f3db502a2c8709109 bugfix update dashboard alerts cards height not consist (#1087) 2023-07-11T23:23:15+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/840cf4dca6031ff943b7f78e13ab3daa8670a1c3 support sending alert to ServerChan(#1092) 2023-07-12T17:01:04+08:00 Logic zqr10159@126.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/0a9a83c997ed35547243ea613c9d67ad34586fc5 bugfix dm database monitoring connect error (#1094) 2023-07-12T17:08:03+08:00 lisongning 93140178+lisongning@users.noreply.github.com Signed-off-by: <span data-trailer="Signed-off-by:"><a href="mailto:93140178+lisongning@users.noreply.github.com" title="93140178+lisongning@users.noreply.github.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg3" style="text-decoration: none">N</a><a href="mailto:93140178+lisongning@users.noreply.github.com" title="93140178+lisongning@users.noreply.github.com">lisongning</a> &lt;<a href="mailto:93140178+lisongning@users.noreply.github.com" title="93140178+lisongning@users.noreply.github.com">93140178+lisongning@users.noreply.github.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/139d1f4eca6d1fb3a47ad08f2d98f683a6903cd2 [doc] add lisongning as a contributor for code (#1096) 2023-07-12T17:08:24+08:00 allcontributors[bot] 46447321+allcontributors[bot]@users.noreply.github.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg2" style="text-decoration: none">N</a><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">allcontributors[bot]</a> &lt;<a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">46447321+allcontributors[bot]@users.noreply.github.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/34cb8e8fc051c01489db65f0eb3c673d0164fe67 update alert rule operator display "<=" to ">=" (#1097) 2023-07-12T17:09:46+08:00 Ceilzcx 48920254+Ceilzcx@users.noreply.github.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/06014941fd57f3d1dc88ff22ba5442ef308147fe [doc] add custom monitoring relate document (#1098) 2023-07-15T14:43:21+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/b99dd6a45db50927eae34be233efe2077a2dfd3b [doc] add YutingNie as a contributor for code (#1103) 2023-07-17T14:10:48+08:00 allcontributors[bot] 46447321+allcontributors[bot]@users.noreply.github.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg4" style="text-decoration: none">N</a><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">allcontributors[bot]</a> &lt;<a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">46447321+allcontributors[bot]@users.noreply.github.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/823ab127c01a852c6753df8af183408aaf19fe88 remove unreachable status (#1102) 2023-07-17T14:11:06+08:00 YutingNie 104416402+YutingNie@users.noreply.github.com Signed-off-by: <span data-trailer="Signed-off-by:" data-user="743570"><a href="https://gitcode.net/qq_53069902" title="yvettemisaki@outlook.com"><img alt="小白123789's avatar" src="https://profile.csdnimg.cn/C/0/3/1_qq_53069902" class="avatar s16 avatar-inline" title="小白123789"></a><a href="https://gitcode.net/qq_53069902" title="yvettemisaki@outlook.com">Yuting Nie</a> &lt;<a href="mailto:yvettemisaki@outlook.com" title="yvettemisaki@outlook.com">yvettemisaki@outlook.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/ddbd8aa7d2895d45c0076960925b195f647573a5 auto update alert status (#1104) 2023-07-17T22:19:04+08:00 l646505418 50475131+l646505418@users.noreply.github.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/ab6bbe40b4da4161c1d6e7cf148bc852473aa5fd feat: aviator fn for str contains, exists & matches (#1106) 2023-07-19T10:59:29+08:00 Mike Zhou mikezhoudev@gmail.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/da8d504906fc830590b639dff16a83080955fd36 [doc] add mikezzb as a contributor for code (#1107) 2023-07-19T10:59:53+08:00 allcontributors[bot] 46447321+allcontributors[bot]@users.noreply.github.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/88ec707780822ce02c8d614d7d7c0f4996550079 bugfix common alarm do not need monitorId tag existed (#1108) 2023-07-19T14:33:33+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/819a06868e1d9a84fd401e65bd4a249c97289314 bugfix extern alert do not have labels mapping inner monitor (#1111) 2023-07-19T15:25:31+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/5807f8d8adfce6e980a3f641be9ef6dc2c100e6f feature: support apache spark metrics monitoring (#1114) 2023-07-20T17:50:08+08:00 小笨蛋 105542329+a-little-fool@users.noreply.github.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/a5d96984dad6c42ad8db9fcbf7bd875c38194151 [doc] add a-little-fool as a contributor for code (#1116) 2023-07-20T17:50:26+08:00 allcontributors[bot] 46447321+allcontributors[bot]@users.noreply.github.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg2" style="text-decoration: none">N</a><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">allcontributors[bot]</a> &lt;<a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">46447321+allcontributors[bot]@users.noreply.github.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/04eeb10c59a30fce37a1e44c9715b91a5a195758 [Feature]Add third report of TenCloud (#1113) 2023-07-20T21:49:11+08:00 Logic zqr10159@126.com Signed-off-by: <span data-trailer="Signed-off-by:" data-user="1611705"><a href="https://gitcode.net/zqr10159" title="zqr10159@126.com"><img alt="zqr10159's avatar" src="https://profile-avatar.csdnimg.cn/default.jpg!1" class="avatar s16 avatar-inline" title="zqr10159"></a><a href="https://gitcode.net/zqr10159" title="zqr10159@126.com">Logic</a> &lt;<a href="mailto:zqr10159@126.com" title="zqr10159@126.com">zqr10159@126.com</a>&gt;</span> Signed-off-by: <span data-trailer="Signed-off-by:" data-user="23839"><a href="https://gitcode.net/sinat_25235033" title="tomsun28@outlook.com"><img alt="sinat_25235033's avatar" src="https://profile.csdnimg.cn/1/4/A/1_sinat_25235033" class="avatar s16 avatar-inline" title="sinat_25235033"></a><a href="https://gitcode.net/sinat_25235033" title="tomsun28@outlook.com">tomsun28</a> &lt;<a href="mailto:tomsun28@outlook.com" title="tomsun28@outlook.com">tomsun28@outlook.com</a>&gt;</span> Co-authored-by: <span data-trailer="Co-authored-by:" data-user="23839"><a href="https://gitcode.net/sinat_25235033" title="tomsun28@outlook.com"><img alt="sinat_25235033's avatar" src="https://profile.csdnimg.cn/1/4/A/1_sinat_25235033" class="avatar s16 avatar-inline" title="sinat_25235033"></a><a href="https://gitcode.net/sinat_25235033" title="tomsun28@outlook.com">tomsun28</a> &lt;<a href="mailto:tomsun28@outlook.com" title="tomsun28@outlook.com">tomsun28@outlook.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/b20d9b74410a0825e5646a766d3439dd43fc4e20 [manager] fix: can query by tags when tagValue is null (#1118) 2023-07-21T10:49:46+08:00 l646505418 50475131+l646505418@users.noreply.github.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/2de7179b255053b0528c3e4ae953547759bf2678 bugfix the notification template environment variable display error (#1120) 2023-07-22T10:42:58+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/06aa5ebb530cc1fa02b8da76deae51fe585358d9 [doc] add littlezhongzer as a contributor for code (#1127) 2023-07-22T10:43:32+08:00 allcontributors[bot] 46447321+allcontributors[bot]@users.noreply.github.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg5" style="text-decoration: none">N</a><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">allcontributors[bot]</a> &lt;<a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">46447321+allcontributors[bot]@users.noreply.github.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/1c67c068265068862c564619288e7fd95393ba0d feature:monitor brearer token api, ignore letter case to comparison (#1122) 2023-07-22T10:43:48+08:00 littlezhongzer 33685289+littlezhongzer@users.noreply.github.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:zhongcheng@winning.com.cn" title="zhongcheng@winning.com.cn"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg1" style="text-decoration: none">N</a><a href="mailto:zhongcheng@winning.com.cn" title="zhongcheng@winning.com.cn">zhongcheng</a> &lt;<a href="mailto:zhongcheng@winning.com.cn" title="zhongcheng@winning.com.cn">zhongcheng@winning.com.cn</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/13f07997116106d439feaa4b91a988bdb46181c8 docs: enhance README (#1128) 2023-07-23T11:06:21+08:00 Mike Zhou mikezhoudev@gmail.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/6aa52a57f5c297d8fbf96fa441d7f5df2a9884de update app-oracle.yml monitoring template (#1129) 2023-07-24T16:33:29+08:00 ChenXiangxxxxx 90089594+ChenXiangxxxxx@users.noreply.github.com Signed-off-by: <span data-trailer="Signed-off-by:"><a href="mailto:90089594+ChenXiangxxxxx@users.noreply.github.com" title="90089594+ChenXiangxxxxx@users.noreply.github.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg5" style="text-decoration: none">N</a><a href="mailto:90089594+ChenXiangxxxxx@users.noreply.github.com" title="90089594+ChenXiangxxxxx@users.noreply.github.com">ChenXiangxxxxx</a> &lt;<a href="mailto:90089594+ChenXiangxxxxx@users.noreply.github.com" title="90089594+ChenXiangxxxxx@users.noreply.github.com">90089594+ChenXiangxxxxx@users.noreply.github.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/76dfef8c73c27117c64e2dacdef1c87b3dd7d2df [doc] add ChenXiangxxxxx as a contributor for code (#1130) 2023-07-24T16:33:51+08:00 allcontributors[bot] 46447321+allcontributors[bot]@users.noreply.github.com Co-authored-by: <span data-trailer="Co-authored-by:"><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg3" style="text-decoration: none">N</a><a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">allcontributors[bot]</a> &lt;<a href="mailto:46447321+allcontributors%5Bbot%5D@users.noreply.github.com" title="46447321+allcontributors[bot]@users.noreply.github.com">46447321+allcontributors[bot]@users.noreply.github.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/56e62702576557b4fb041318bdddf8f89716e653 fix alarm silence strategy setting failed (#1131) 2023-07-25T15:00:41+08:00 Ceilzcx 48920254+Ceilzcx@users.noreply.github.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/a791f457cf22943dd350ff9b9000ce93b28456d5 support run sql script file in jdbc protocol config (#1117) 2023-07-27T17:02:05+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/ac08d0412493e7b9675500ed0a61a54da52322cf bugfix return old cache json file when upgrade version (#1137) 2023-07-27T21:33:16+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/fd2f058ac3f00d33ac27c7ef9a9c58449a2af368 support ssh protocol config choose if reuse connection (#1136) 2023-07-28T15:09:44+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/bb7277f3221e2f53656b057856d85e64378570a3 feat(web): alert threshold UI support matches & contains (#1138) 2023-07-29T13:57:32+08:00 Mike Zhou mikezhoudev@gmail.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/54de98efc797614fd86f666c835cd5f2334dd570 support hertzbeat metrics collector cluster (#1101) 2023-08-02T16:01:43+08:00 tomsun28 tomsun28@outlook.com Co-authored-by: <span data-trailer="Co-authored-by:" data-user="630945"><a href="https://gitcode.net/zcx_88372565" title="1758619238@qq.com"><img alt="zcx_88372565's avatar" src="https://profile.csdnimg.cn/8/E/4/1_zcx_88372565" class="avatar s16 avatar-inline" title="zcx_88372565"></a><a href="https://gitcode.net/zcx_88372565" title="1758619238@qq.com">Ceilzcx</a> &lt;<a href="mailto:1758619238@qq.com" title="1758619238@qq.com">1758619238@qq.com</a>&gt;</span> https://gitcode.net/hertzbeat/hertzbeat/-/commit/9f2638126cbdf6f9db0e1ddf91d38398586d5945 add collector card in dashboard (#1147) 2023-08-03T19:44:16+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/d9a1798104a8a5e33cd22108e3703a39d71e9772 Merge branch 'master' into issue_1021_system_timezone 2023-08-04T22:02:13+08:00 tomsun28 tomsun28@outlook.com # Conflicts: # manager/src/main/java/org/dromara/hertzbeat/manager/component/alerter/impl/EmailAlertNotifyHandlerImpl.java https://gitcode.net/hertzbeat/hertzbeat/-/commit/fddc7af0a0897ce75f901ac46a34d7e95c162f96 support config timezone locale language region on web ui 2023-08-05T12:12:01+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/7110882b2e003249a4ac6ce597ad061bb24cfcf8 update i18n config 2023-08-05T13:32:55+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/9feb419f642d3239c562c931c390971a1783b97f support config timezone locale language region on web ui 2023-08-05T13:37:48+08:00 tomsun28 tomsun28@outlook.com https://gitcode.net/hertzbeat/hertzbeat/-/commit/cd50eb6d4126b147d34c2095feb88e53b521bf6e add theme 2023-08-05T13:56:50+08:00 tomsun28 tomsun28@outlook.com
......@@ -1206,6 +1206,78 @@
"contributions": [
"code"
]
},
{
"login": "san346596324",
"name": "渭雨",
"avatar_url": "https://avatars.githubusercontent.com/u/30828520?v=4",
"profile": "https://github.com/san346596324",
"contributions": [
"code"
]
},
{
"login": "luoxuanzao",
"name": "liuxuezhuo",
"avatar_url": "https://avatars.githubusercontent.com/u/44692579?v=4",
"profile": "https://github.com/luoxuanzao",
"contributions": [
"code"
]
},
{
"login": "lisongning",
"name": "lisongning",
"avatar_url": "https://avatars.githubusercontent.com/u/93140178?v=4",
"profile": "https://github.com/lisongning",
"contributions": [
"code"
]
},
{
"login": "YutingNie",
"name": "YutingNie",
"avatar_url": "https://avatars.githubusercontent.com/u/104416402?v=4",
"profile": "https://github.com/YutingNie",
"contributions": [
"code"
]
},
{
"login": "mikezzb",
"name": "Mike Zhou",
"avatar_url": "https://avatars.githubusercontent.com/u/23418428?v=4",
"profile": "https://github.com/mikezzb",
"contributions": [
"code"
]
},
{
"login": "a-little-fool",
"name": "小笨蛋",
"avatar_url": "https://avatars.githubusercontent.com/u/105542329?v=4",
"profile": "https://github.com/a-little-fool",
"contributions": [
"code"
]
},
{
"login": "littlezhongzer",
"name": "littlezhongzer",
"avatar_url": "https://avatars.githubusercontent.com/u/33685289?v=4",
"profile": "https://github.com/littlezhongzer",
"contributions": [
"code"
]
},
{
"login": "ChenXiangxxxxx",
"name": "ChenXiangxxxxx",
"avatar_url": "https://avatars.githubusercontent.com/u/90089594?v=4",
"profile": "https://github.com/ChenXiangxxxxx",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
......
......@@ -8,7 +8,7 @@
## HertzBeat | [中文文档](README_CN.md)
> An open source, real-time monitoring system with custom-monitor and agentLess. | 易用友好的开源实时监控告警系统,无需Agent,强大自定义监控能力.
An open-source, real-time monitoring system with custom monitoring and agentless capabilities. | 易用友好的开源实时监控告警系统,无需Agent,强大自定义监控能力.
[![discord](https://img.shields.io/badge/chat-on%20discord-brightgreen)](https://discord.gg/Fb6M73htGr)
[![Gitter](https://badges.gitter.im/hertzbeat/community.svg)](https://gitter.im/hertzbeat/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
......@@ -30,23 +30,25 @@
## 🎡 <font color="green">Introduction</font>
> [HertzBeat](https://github.com/dromara/hertzbeat) is an open source, real-time monitoring system with custom-monitor and agentless.
> **Monitor+Alerter+Notify** all in one. Support monitoring web service, database, os, middleware, cloud-native, network and more.
> More flexible threshold rule(calculation expression), timely notification delivery by `Discord` `Slack` `Telegram` `Email` `DingDing` `WeChat` `FeiShu` `Webhook` `SMS`.
[HertzBeat](https://github.com/dromara/hertzbeat) is an open source, real-time monitoring system with custom monitoring and agentless capabilities.
It combines **monitoring, alarm, and notification** features into one platform, and supports monitoring for web service, database, os, middleware, cloud-native, network and more.
> We make protocols such as `Http, Jmx, Ssh, Snmp, Jdbc` configurable, and you only need to configure `YML` online to collect any metrics you want.
> Do you believe that you can immediately adapt a new monitoring type such as K8s or Docker just by configuring online?
### Features
> `HertzBeat`'s powerful custom-define, multi-type support, easy expansion, low coupling, hope to help developers and micro teams to quickly build their own monitoring system.
> We also provide **[Monitoring SaaS Cloud](https://console.tancloud.cn)**, users no longer need to deploy a cumbersome monitoring system in order to monitor resources. **[Get started for free](https://console.tancloud.cn)**.
* Easy to use, offering full web-based operations for monitoring and alerting with just a click of a mouse, all at zero learning cost.
* Provides a more flexible threshold rules and timely notifications delivered via `Discord` `Slack` `Telegram` `Email` `DingDing` `WeChat` `FeiShu` `Webhook` `SMS`.
* Makes protocols such as `Http, Jmx, Ssh, Snmp, Jdbc` configurable, allowing you to collect any desired metrics by simply configuring the `YML` file online. Imagine being able to quickly adapt to a new monitoring type like K8s or Docker simply by configuring online with HertzBeat.
* Powerful customization, multi-type support, easy expansion, and low coupling.
----
HertzBeat aims to help developers and teams quickly build their own monitoring system. We also provide **[Monitoring SaaS Cloud](https://console.tancloud.cn)**, users no longer need to deploy a cumbersome monitoring system to monitor their resources. **[Get started for free](https://console.tancloud.cn)**.
----
[![hertzbeat](home/static/img/home/1.png)](https://www.bilibili.com/video/BV1LY4y1m7rH/)
[![hertzbeat](home/static/img/home/9.png)](https://www.bilibili.com/video/BV1LY4y1m7rH/)
----
----
## 🥐 Architecture
......@@ -54,7 +56,7 @@
## ⛄ Supported
> We define all monitoring collection types (mysql,jvm,k8s) as yml monitoring templates, and users can import these templates to support corresponding types of monitoring.
> We define all monitoring collection types such as `mysql`, `jvm`, and `k8s` as `YML` monitoring templates, allowing users to import them to support corresponding types of monitoring.
> Welcome everyone to contribute your customized general monitoring type YML template during use.
......@@ -91,22 +93,22 @@
## 🐕 Quick Start
- If you don’t want to deploy but use it directly, we provide [SAAS Monitoring Cloud-TanCloud](https://console.tancloud.cn), **[Log In For Free](https://console.tancloud.cn)**.
- If you want to deploy HertzBeat local, please refer to the following Deployment Documentation for operation.
- If you prefer to use HertzBeat directly without deploying it, we provide [SAAS Monitoring Cloud-TanCloud](https://console.tancloud.cn), **[Log In For Free](https://console.tancloud.cn)**.
- If you wish to deploy HertzBeat locally, please refer to the following Deployment Documentation for instructions.
### 🍞 Install HertzBeat
> HertzBeat supports installation through source code, docker or package, cpu support X86/ARM64.
> HertzBeat supports installation through source code, docker or package, cpu support x86/arm64.
##### 1:Install quickly via docker
1. Just one command to get started:
```docker run -d -p 1157:1157 --name hertzbeat tancloud/hertzbeat```
```docker run -d -p 1157:1157 -p 1158:1158 --name hertzbeat tancloud/hertzbeat```
```or use quay.io (if dockerhub network connect timeout)```
```docker run -d -p 1157:1157 --name hertzbeat quay.io/tancloud/hertzbeat```
```docker run -d -p 1157:1157 -p 1158:1158 --name hertzbeat quay.io/tancloud/hertzbeat```
2. Access `localhost:1157` to start, default account: `admin/hertzbeat`
......@@ -145,7 +147,7 @@ Detailed steps refer to [Install via Docker-Compose](script/docker-compose/READM
## ✨ Contributors
Thanks these wonderful people, welcome to join us:
Thanks to these wonderful people, welcome to join us:
[Contributor Guide](CONTRIBUTING.md)
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
......@@ -317,6 +319,16 @@ Thanks these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/qq471754603"><img src="https://avatars.githubusercontent.com/u/23146592?v=4?s=100" width="100px;" alt="qq471754603"/><br /><sub><b>qq471754603</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=qq471754603" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/san346596324"><img src="https://avatars.githubusercontent.com/u/30828520?v=4?s=100" width="100px;" alt="渭雨"/><br /><sub><b>渭雨</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=san346596324" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/luoxuanzao"><img src="https://avatars.githubusercontent.com/u/44692579?v=4?s=100" width="100px;" alt="liuxuezhuo"/><br /><sub><b>liuxuezhuo</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=luoxuanzao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lisongning"><img src="https://avatars.githubusercontent.com/u/93140178?v=4?s=100" width="100px;" alt="lisongning"/><br /><sub><b>lisongning</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=lisongning" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/YutingNie"><img src="https://avatars.githubusercontent.com/u/104416402?v=4?s=100" width="100px;" alt="YutingNie"/><br /><sub><b>YutingNie</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=YutingNie" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikezzb"><img src="https://avatars.githubusercontent.com/u/23418428?v=4?s=100" width="100px;" alt="Mike Zhou"/><br /><sub><b>Mike Zhou</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=mikezzb" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/a-little-fool"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="小笨蛋"/><br /><sub><b>小笨蛋</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=a-little-fool" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/littlezhongzer"><img src="https://avatars.githubusercontent.com/u/33685289?v=4?s=100" width="100px;" alt="littlezhongzer"/><br /><sub><b>littlezhongzer</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=littlezhongzer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ChenXiangxxxxx"><img src="https://avatars.githubusercontent.com/u/90089594?v=4?s=100" width="100px;" alt="ChenXiangxxxxx"/><br /><sub><b>ChenXiangxxxxx</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=ChenXiangxxxxx" title="Code">💻</a></td>
</tr>
</tbody>
</table>
......@@ -328,7 +340,7 @@ Thanks these wonderful people, welcome to join us:
## 💬 Join discussion
HertzBeat is a top project under the [Dromara Open Source Community](https://dromara.org/).
HertzBeat is a top project under the [Dromara Open Source Community](https://dromara.org/). Gitee GVP.
##### Channel
......
......@@ -32,7 +32,8 @@
> [HertzBeat赫兹跳动](https://github.com/dromara/hertzbeat) 是一个拥有强大自定义监控能力,无需 Agent 的开源实时监控告警系统。
> 集 **监控+告警+通知** 为一体,支持对应用服务,数据库,操作系统,中间件,云原生,网络等监控,阈值告警通知一步到位。
> 更自由化的阈值规则(计算表达式),`邮件` `Discord` `Slack` `Telegram` `钉钉` `微信` `飞书` `短信` `Webhook` 等方式及时送达。
> 易用友好,全 WEB 页面操作,鼠标点一点就能监控告警,零上手学习成本。
> 更自由化的阈值规则,`邮件` `Discord` `Slack` `Telegram` `钉钉` `微信` `飞书` `短信` `Webhook` 等方式及时送达。
> 我们将`Http,Jmx,Ssh,Snmp,Jdbc`等协议规范可配置化,您只需在浏览器配置`YML`就能使用这些协议去自定义采集任何您想要的指标。
> 您相信只需配置下就能立刻适配一款`K8s`或`Docker`等新的监控类型吗?
......@@ -93,17 +94,17 @@
- 如果您是想将HertzBeat部署到内网环境搭建监控系统,请参考下面的部署文档进行操作。
### 🍞 HertzBeat安装
> HertzBeat支持通过源码安装启动,Docker容器运行和安装包方式安装部署,CPU架构支持X86/ARM64。
> HertzBeat支持通过源码安装启动,Docker容器运行和安装包方式安装部署,CPU架构支持x86/arm64。
##### 方式一:Docker方式快速安装
1. `docker` 环境仅需一条命令即可开始
```docker run -d -p 1157:1157 --name hertzbeat tancloud/hertzbeat```
```docker run -d -p 1157:1157 -p 1158:1158 --name hertzbeat tancloud/hertzbeat```
```或者使用 quay.io (若 dockerhub 网络链接超时)```
```docker run -d -p 1157:1157 --name hertzbeat quay.io/tancloud/hertzbeat```
```docker run -d -p 1157:1157 -p 1158:1158 --name hertzbeat quay.io/tancloud/hertzbeat```
2. 浏览器访问 `localhost:1157` 即可开始,默认账号密码 `admin/hertzbeat`
......@@ -312,6 +313,16 @@ Thanks these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/qq471754603"><img src="https://avatars.githubusercontent.com/u/23146592?v=4?s=100" width="100px;" alt="qq471754603"/><br /><sub><b>qq471754603</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=qq471754603" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/san346596324"><img src="https://avatars.githubusercontent.com/u/30828520?v=4?s=100" width="100px;" alt="渭雨"/><br /><sub><b>渭雨</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=san346596324" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/luoxuanzao"><img src="https://avatars.githubusercontent.com/u/44692579?v=4?s=100" width="100px;" alt="liuxuezhuo"/><br /><sub><b>liuxuezhuo</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=luoxuanzao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lisongning"><img src="https://avatars.githubusercontent.com/u/93140178?v=4?s=100" width="100px;" alt="lisongning"/><br /><sub><b>lisongning</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=lisongning" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/YutingNie"><img src="https://avatars.githubusercontent.com/u/104416402?v=4?s=100" width="100px;" alt="YutingNie"/><br /><sub><b>YutingNie</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=YutingNie" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikezzb"><img src="https://avatars.githubusercontent.com/u/23418428?v=4?s=100" width="100px;" alt="Mike Zhou"/><br /><sub><b>Mike Zhou</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=mikezzb" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/a-little-fool"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="小笨蛋"/><br /><sub><b>小笨蛋</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=a-little-fool" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/littlezhongzer"><img src="https://avatars.githubusercontent.com/u/33685289?v=4?s=100" width="100px;" alt="littlezhongzer"/><br /><sub><b>littlezhongzer</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=littlezhongzer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ChenXiangxxxxx"><img src="https://avatars.githubusercontent.com/u/90089594?v=4?s=100" width="100px;" alt="ChenXiangxxxxx"/><br /><sub><b>ChenXiangxxxxx</b></sub></a><br /><a href="https://github.com/dromara/hertzbeat/commits?author=ChenXiangxxxxx" title="Code">💻</a></td>
</tr>
</tbody>
</table>
......@@ -323,7 +334,7 @@ Thanks these wonderful people, welcome to join us:
## 💬 社区交流
HertzBeat 赫兹跳动是 [Dromara开源社区](https://dromara.org/) 下顶级项目。
HertzBeat 赫兹跳动是 [Dromara开源社区](https://dromara.org/) 下顶级项目。Gitee GVP。
##### 微信交流群
......
......@@ -65,6 +65,11 @@ public class AlerterProperties {
*/
private String discordNotifyUrl = "https://discord.com/api/v9/channels/%s/messages";
/**
* ServerChan Notify url
*/
private String serverChanNotifyUrl = "https://sctapi.ftqq.com/%s.send";
/**
* 告警评估时间间隔起始基数 每下一次乘2 单位毫秒
* base of alert eval interval time, unit:ms. The next time is 2 times the previous time.
......
......@@ -24,6 +24,7 @@ import com.googlecode.aviator.exception.ExpressionRuntimeException;
import com.googlecode.aviator.exception.ExpressionSyntaxErrorException;
import org.dromara.hertzbeat.alert.AlerterWorkerPool;
import org.dromara.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.dromara.hertzbeat.alert.service.AlertService;
import org.dromara.hertzbeat.common.queue.CommonDataQueue;
import org.dromara.hertzbeat.alert.dao.AlertMonitorDao;
import org.dromara.hertzbeat.common.entity.alerter.Alert;
......@@ -33,22 +34,29 @@ import org.dromara.hertzbeat.alert.util.AlertTemplateUtil;
import org.dromara.hertzbeat.common.entity.manager.Monitor;
import org.dromara.hertzbeat.common.entity.message.CollectRep;
import org.dromara.hertzbeat.common.constants.CommonConstants;
import org.dromara.hertzbeat.common.support.event.SystemConfigChangeEvent;
import org.dromara.hertzbeat.common.util.CommonUtil;
import org.dromara.hertzbeat.common.util.ResourceBundleUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import static org.dromara.hertzbeat.common.constants.CommonConstants.ALERT_STATUS_CODE_PENDING;
import static org.dromara.hertzbeat.common.constants.CommonConstants.ALERT_STATUS_CODE_SOLVED;
/**
* Calculate alarms based on the alarm definition rules and collected data
* 根据告警定义规则和采集数据匹配计算告警
* @author tom
*
* @author tom
*/
@Configuration
@Component
@Slf4j
public class CalculateAlarm {
......@@ -64,15 +72,17 @@ public class CalculateAlarm {
private final CommonDataQueue dataQueue;
private final AlertDefineService alertDefineService;
private final AlarmCommonReduce alarmCommonReduce;
private final ResourceBundle bundle;
private ResourceBundle bundle;
private final AlertService alertService;
public CalculateAlarm (AlerterWorkerPool workerPool, CommonDataQueue dataQueue,
AlertDefineService alertDefineService, AlertMonitorDao monitorDao,
AlarmCommonReduce alarmCommonReduce) {
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<>(128);
this.unAvailableMonitors = Collections.synchronizedSet(new HashSet<>(16));
......@@ -154,7 +164,8 @@ public class CalculateAlarm {
try {
Expression expression = AviatorEvaluator.compile(expr, true);
match = (Boolean) expression.execute(fieldValueMap);
} catch (CompileExpressionErrorException | ExpressionSyntaxErrorException compileException) {
} catch (CompileExpressionErrorException |
ExpressionSyntaxErrorException compileException) {
log.error("Alert Define Rule: {} Compile Error: {}.", expr, compileException.getMessage());
} catch (ExpressionRuntimeException expressionRuntimeException) {
log.error("Alert Define Rule: {} Run Error: {}.", expr, expressionRuntimeException.getMessage());
......@@ -175,7 +186,7 @@ public class CalculateAlarm {
int defineTimes = define.getTimes() == null ? 1 : define.getTimes();
if (times >= defineTimes) {
triggeredAlertMap.remove(monitorAlertKey);
alarmCommonReduce.reduceAndSendAlarm(triggeredAlert);
alarmCommonReduce.reduceAndSendAlarm(triggeredAlert.clone());
}
} else {
fieldValueMap.put("app", app);
......@@ -188,7 +199,7 @@ public class CalculateAlarm {
.tags(tags)
.alertDefineId(define.getId())
.priority(define.getPriority())
.status(CommonConstants.ALERT_STATUS_CODE_PENDING)
.status(ALERT_STATUS_CODE_PENDING)
.target(app + "." + metrics + "." + define.getField())
.triggerTimes(1)
.firstAlarmTime(currentTimeMilli)
......@@ -263,6 +274,10 @@ public class CalculateAlarm {
.triggerTimes(1)
.build();
alarmCommonReduce.reduceAndSendAlarm(resumeAlert);
Runnable updateStatusJob = () -> {
updateAvailabilityAlertStatus(monitorId, resumeAlert);
};
workerPool.executeJob(updateStatusJob);
}
}
}
......@@ -285,7 +300,7 @@ public class CalculateAlarm {
Alert.AlertBuilder alertBuilder = Alert.builder()
.tags(tags)
.priority(CommonConstants.ALERT_PRIORITY_CODE_EMERGENCY)
.status(CommonConstants.ALERT_STATUS_CODE_PENDING)
.status(ALERT_STATUS_CODE_PENDING)
.target(CommonConstants.AVAILABILITY)
.content(AlertTemplateUtil.render(avaAlertDefine.getTemplate(), valueMap))
.firstAlarmTime(currentTimeMill)
......@@ -300,7 +315,7 @@ public class CalculateAlarm {
triggeredAlertMap.put(String.valueOf(monitorId), alertBuilder.build());
} else {
int times = preAlert.getTriggerTimes() + 1;
if (preAlert.getStatus() == CommonConstants.ALERT_STATUS_CODE_PENDING) {
if (preAlert.getStatus() == ALERT_STATUS_CODE_PENDING) {
times = 1;
preAlert.setContent(AlertTemplateUtil.render(avaAlertDefine.getTemplate(), valueMap));
preAlert.setTags(tags);
......@@ -310,12 +325,52 @@ public class CalculateAlarm {
preAlert.setLastAlarmTime(currentTimeMill);
int defineTimes = avaAlertDefine.getTimes() == null ? 1 : avaAlertDefine.getTimes();
if (times >= defineTimes) {
preAlert.setStatus(CommonConstants.ALERT_STATUS_CODE_PENDING);
alarmCommonReduce.reduceAndSendAlarm(preAlert);
preAlert.setStatus(ALERT_STATUS_CODE_PENDING);
alarmCommonReduce.reduceAndSendAlarm(preAlert.clone());
unAvailableMonitors.add(monitorId);
} else {
preAlert.setStatus(CommonConstants.ALERT_STATUS_CODE_NOT_REACH);
}
}
}
private void updateAvailabilityAlertStatus(long monitorId, Alert restoreAlert) {
List<Alert> availabilityAlerts = queryAvailabilityAlerts(monitorId, restoreAlert);
availabilityAlerts.stream().parallel().forEach(alert -> {
log.info("updating alert 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 onEvent(SystemConfigChangeEvent event) {
log.info("calculate alarm receive system config change event: {}.", event.getSource());
this.bundle = ResourceBundleUtil.getBundle("alerter");
}
}
package org.dromara.hertzbeat.alert.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.dromara.hertzbeat.alert.service.AlertService;
import org.dromara.hertzbeat.alert.service.impl.AlertConvertTenCloudServiceImpl;
import org.dromara.hertzbeat.common.entity.dto.AlertReport;
import org.dromara.hertzbeat.common.entity.dto.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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;
import javax.validation.Valid;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
/**
* @author zqr10159
* 第三方告警上报接口
*/
@Tag(name = "Extern Alarm Manage API | 第三方告警管理API")
@RestController
@RequestMapping(path = "/api/alerts/report", produces = {APPLICATION_JSON_VALUE})
public class AlertReportController {
@Autowired
AlertConvertTenCloudServiceImpl alertConvertTenCloudService;
@Autowired
private AlertService alertService;
@PostMapping("/tencloud")
@Operation(summary = "Interface for reporting external alarm information of tencloud | 对外上报告警信息 接口",
description = "对外 新增一个腾讯云告警")
public ResponseEntity<Message<Void>> addNewAlertReportTencent(@Valid @RequestBody String alertReport) {
AlertReport convert = alertConvertTenCloudService.convert(alertReport);
alertService.addNewAlertReport(convert);
return ResponseEntity.ok(new Message<>("Add report success"));
}
@PostMapping
@Operation(summary = "Interface for reporting external alarm information | 对外上报告警信息 接口",
description = "对外 新增一个告警")
public ResponseEntity<Message<Void>> addNewAlertReport(@Valid @RequestBody AlertReport alertReport) {
// 校验请求数据 TODO
alertService.addNewAlertReport(alertReport);
return ResponseEntity.ok(new Message<>("Add report success"));
}
}
......@@ -20,7 +20,6 @@ package org.dromara.hertzbeat.alert.controller;
import org.dromara.hertzbeat.alert.dto.AlertSummary;
import org.dromara.hertzbeat.common.entity.alerter.Alert;
import org.dromara.hertzbeat.alert.service.AlertService;
import org.dromara.hertzbeat.common.entity.dto.AlertReport;
import org.dromara.hertzbeat.common.entity.dto.Message;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
......@@ -35,7 +34,6 @@ import org.springframework.web.bind.annotation.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
......@@ -144,13 +142,5 @@ public class AlertsController {
Message<AlertSummary> message = new Message<>(alertSummary);
return ResponseEntity.ok(message);
}
@PostMapping("/report")
@Operation(summary = "Interface for reporting external alarm information | 对外上报告警信息 接口",
description = "对外 新增一个告警")
public ResponseEntity<Message<Void>> addNewAlertReport(@Valid @RequestBody AlertReport alertReport) {
// 校验请求数据 TODO
alertService.addNewAlertReport(alertReport);
return ResponseEntity.ok(new Message<>("Add report success"));
}
}
package org.dromara.hertzbeat.alert.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author zqr10159
* 腾讯云告警实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TenCloudAlertReport 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;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class AlarmObjInfo {
private String region;
private String namespace;
@JsonProperty("appID")
private String appId;
private String uin;
private Dimensions dimensions;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Dimensions {
@JsonProperty("unInstanceID")
private String unInstanceId;
@JsonProperty("objID")
private String objId;
}
@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;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Conditions {
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;
}
}
......@@ -35,16 +35,16 @@ public class AlarmCommonReduce {
Map<String, String> tags = alert.getTags();
String monitorIdStr = tags.get(CommonConstants.TAG_MONITOR_ID);
if (monitorIdStr == null) {
log.error("alert tags monitorId can not be null: {}.", alert);
return;
}
long monitorId = Long.parseLong(monitorIdStr);
List<Tag> tagList = alertMonitorDao.findMonitorIdBindTags(monitorId);
tagList.forEach(tag -> {
if (!tags.containsKey(tag.getName())) {
tags.put(tag.getName(), tag.getValue());
}
});
log.debug("receiver extern alarm message: {}", alert);
} else {
long monitorId = Long.parseLong(monitorIdStr);
List<Tag> tagList = alertMonitorDao.findMonitorIdBindTags(monitorId);
tagList.forEach(tag -> {
if (!tags.containsKey(tag.getName())) {
tags.put(tag.getName(), tag.getValue());
}
});
}
// converge -> silence
if (alarmConvergeReduce.filterConverge(alert) && alarmSilenceReduce.filterSilence(alert)) {
dataQueue.sendAlertsData(alert);
......
......@@ -9,6 +9,7 @@ import org.dromara.hertzbeat.common.entity.alerter.AlertConverge;
import org.dromara.hertzbeat.common.entity.manager.TagItem;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
......@@ -79,6 +80,8 @@ public class AlarmConvergeReduce {
return false;
}
});
} else {
match = true;
}
if (match && alertConverge.getPriorities() != null && !alertConverge.getPriorities().isEmpty()) {
match = alertConverge.getPriorities().stream().anyMatch(item -> item != null && item == currentAlert.getPriority());
......@@ -90,13 +93,15 @@ public class AlarmConvergeReduce {
if (evalInterval <= 0) {
return true;
}
int alertHash = Objects.hash(currentAlert.getPriority(), currentAlert.getTags());
int alertHash = Objects.hash(currentAlert.getPriority())
+ Arrays.hashCode(currentAlert.getTags().keySet().toArray(new String[0]))
+ Arrays.hashCode(currentAlert.getTags().values().toArray(new String[0]));
Alert preAlert = converageAlertMap.get(alertHash);
if (preAlert == null) {
currentAlert.setTimes(1);
currentAlert.setFirstAlarmTime(now);
currentAlert.setLastAlarmTime(now);
converageAlertMap.put(alertHash, currentAlert);
converageAlertMap.put(alertHash, currentAlert.clone());
return true;
} else {
if (now - preAlert.getFirstAlarmTime() < evalInterval) {
......@@ -104,10 +109,16 @@ public class AlarmConvergeReduce {
preAlert.setLastAlarmTime(now);
return false;
} else {
currentAlert.setTimes(preAlert.getTimes() + 1);
currentAlert.setFirstAlarmTime(preAlert.getFirstAlarmTime());
currentAlert.setTimes(preAlert.getTimes());
if (preAlert.getTimes() == 1) {
currentAlert.setFirstAlarmTime(now);
} else {
currentAlert.setFirstAlarmTime(preAlert.getFirstAlarmTime());
}
currentAlert.setLastAlarmTime(now);
converageAlertMap.remove(alertHash);
preAlert.setFirstAlarmTime(now);
preAlert.setLastAlarmTime(now);
preAlert.setTimes(1);
return true;
}
}
......
......@@ -61,7 +61,9 @@ public class AlarmSilenceReduce {
return false;
}
});
}
} else {
match = true;
}
if (match && alertSilence.getPriorities() != null && !alertSilence.getPriorities().isEmpty()) {
match = alertSilence.getPriorities().stream().anyMatch(item -> item != null && item == alert.getPriority());
}
......
package org.dromara.hertzbeat.alert.service;
/**
* Alert Convert Interface.
* @author zqr10159
* @param <T> the type of object to convert the JSON into
*/
public interface AlertConvertService<T> {
/**
* Alert Convert Method.
*
* @param json the JSON string to convert
* @return the converted object of type T
*/
T convert(String json);
}
......@@ -91,4 +91,14 @@ public interface AlertService {
*/
void addNewAlertReport(AlertReport alertReport);
/**
* Dynamic conditional query
* 动态条件查询
*
* @param specification Query conditions 查询条件
* @return search result 查询结果
*/
List<Alert> getAlerts(Specification<Alert> specification);
}
package org.dromara.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hertzbeat.alert.dto.TenCloudAlertReport;
import org.dromara.hertzbeat.alert.service.AlertConvertService;
import org.dromara.hertzbeat.common.entity.dto.AlertReport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
/**
* @author zqr10159
* 腾讯云告警转化类
*/
@Component
@Slf4j
public class AlertConvertTenCloudServiceImpl implements AlertConvertService<AlertReport> {
@Autowired
private ObjectMapper objectMapper;
@Override
public AlertReport convert(String json) {
TenCloudAlertReport tenCloudAlertReport;
AlertReport alert = null;
try {
tenCloudAlertReport = objectMapper.readValue(json, TenCloudAlertReport.class);
StringBuilder contentBuilder = new StringBuilder();
String content = contentBuilder.append("[").append("告警对象:地区")
.append(tenCloudAlertReport.getAlarmObjInfo().getRegion()).append("|")
.append(tenCloudAlertReport.getAlarmObjInfo().getNamespace()).append("]")
.append("[").append("告警内容:")
.append(tenCloudAlertReport.getAlarmPolicyInfo().getPolicyTypeCname()).append("|")
.append(tenCloudAlertReport.getAlarmPolicyInfo().getConditions().getMetricShowName()).append("|")
.append(tenCloudAlertReport.getAlarmPolicyInfo().getConditions().getMetricName())
.append(tenCloudAlertReport.getAlarmPolicyInfo().getConditions().getCalcType())
.append(tenCloudAlertReport.getAlarmPolicyInfo().getConditions().getCalcValue())
.append(tenCloudAlertReport.getAlarmPolicyInfo().getConditions().getCalcUnit()).append("]")
.append("[").append("当前数据")
.append(tenCloudAlertReport.getAlarmPolicyInfo().getConditions().getCurrentValue())
.append(tenCloudAlertReport.getAlarmPolicyInfo().getConditions().getCalcUnit()).append("]").toString();
HashMap<String, String> tagMap = new HashMap<>(1);
tagMap.put("app", "TenCloud");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long occurTime = sdf.parse(tenCloudAlertReport.getFirstOccurTime()).getTime();
alert = AlertReport.builder().content(content)
.alertName("TenCloud|腾讯云")
.alertTime(occurTime)
.alertDuration(tenCloudAlertReport.getDurationTime())
.priority(1)
.reportType(1)
.labels(tagMap)
.annotations(tagMap).build();
} catch (JsonProcessingException|ParseException e) {
log.error("解析腾讯云告警内容失败!");
}
return alert;
}
}
......@@ -17,6 +17,7 @@
package org.dromara.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dromara.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.dromara.hertzbeat.alert.dao.AlertDao;
import org.dromara.hertzbeat.alert.dto.AlertPriorityNum;
......@@ -133,6 +134,12 @@ public class AlertServiceImpl implements AlertService {
alarmCommonReduce.reduceAndSendAlarm(buildAlertData(alertReport));
}
@Override
public List<Alert> getAlerts(Specification<Alert> specification) {
return alertDao.findAll(specification);
}
/**
* The external alarm information is converted to Alert 对外告警信息 转换为Alert
* @param alertReport 对外告警信息
......@@ -161,4 +168,6 @@ public class AlertServiceImpl implements AlertService {
.build();
}
}
......@@ -14,6 +14,7 @@ import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.Set;
/**
......@@ -31,7 +32,11 @@ public class AlertSilenceServiceImpl implements AlertSilenceService {
@Override
public void validate(AlertSilence alertSilence, boolean isModify) throws IllegalArgumentException {
// todo
// todo
// 兜底策略, 如果周期性情况下设置的告警静默选择日期为空, 视为全部勾选
if (alertSilence.getType() == 1 && alertSilence.getDays() == null) {
alertSilence.setDays(Arrays.asList((byte)7, (byte)1, (byte)2, (byte)3, (byte)4, (byte)5, (byte)6));
}
}
@Override
......
package org.dromara.hertzbeat.alert.controller;
import org.dromara.hertzbeat.alert.dto.TenCloudAlertReport;
import org.dromara.hertzbeat.alert.service.AlertService;
import org.dromara.hertzbeat.alert.service.impl.AlertConvertTenCloudServiceImpl;
import org.dromara.hertzbeat.common.constants.CommonConstants;
import org.dromara.hertzbeat.common.entity.dto.AlertReport;
import org.dromara.hertzbeat.common.util.JsonUtil;
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 org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* unit test for {@link AlertReportController }
* @author tom
*/
@ExtendWith(MockitoExtension.class)
class AlertReportControllerTest {
private MockMvc mockMvc;
@InjectMocks
private AlertReportController alertReportController;
@Mock
private AlertService alertService;
@Mock
AlertConvertTenCloudServiceImpl alertConvertTenCloudService;
@BeforeEach
void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(alertReportController).build();
}
@Test
void addNewAlertReportTencent() throws Exception {
TenCloudAlertReport report = TenCloudAlertReport.builder()
.sessionId("xxxxxxxx")
.alarmStatus("1")
.alarmType("metric")
.build();
mockMvc.perform(
MockMvcRequestBuilders
.post("/api/alerts/report/tencloud")
.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 {
mockMvc.perform(
MockMvcRequestBuilders
.post("/api/alerts/report")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(AlertReport.builder().build()))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(content().json("{\"data\":null,\"msg\":\"Add report success\",\"code\":0}"))
.andReturn();
}
}
......@@ -150,19 +150,4 @@ class AlertsControllerTest {
.andExpect(content().json("{\"data\":{\"total\":0,\"dealNum\":0,\"rate\":0.0,\"priorityWarningNum\":0,\"priorityCriticalNum\":0,\"priorityEmergencyNum\":0},\"msg\":null,\"code\":0}"))
.andReturn();
}
@Test
void addNewAlertReport() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders
.post("/api/alerts/report")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(AlertReport.builder().build()))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(content().json("{\"data\":null,\"msg\":\"Add report success\",\"code\":0}"))
.andReturn();
}
}
......@@ -110,7 +110,7 @@ class AlertDefineServiceTest {
@Test
void getAlertDefine() {
long id = 1L;
AlertDefine alertDefine = AlertDefine.builder().id(1L).build();
AlertDefine alertDefine = AlertDefine.builder().id(id).build();
when(alertDefineDao.findById(id)).thenReturn(Optional.of(alertDefine));
assertDoesNotThrow(() -> alertDefineService.getAlertDefine(id));
}
......
......@@ -5,32 +5,52 @@ Collect Metrics Data from peer-to-peer machine by common protocols.
support protocols:http/https, jdbc, ssh, telnet, jmx, ping, prometheus...
#### Monitor Type
* OS
* Linux
* Windows
* Ubuntu
* CentOs
* Database
* Mysql
* Oracle
* PostgreSQL
* Middleware
* Kafka
* Zookeeper
* RocketMq
* Etcd
* CloudNative
* Docker
* Kubernetes
* Istio
* Service
* Tomcat
* Jetty
* Http
* Ping
* Port
....
## Supported
> We define all monitoring collection types such as `mysql`, `jvm`, and `k8s` as `YML` monitoring templates, allowing users to import them to support corresponding types of monitoring.
> Welcome everyone to contribute your customized general monitoring type YML template during use.
- [Website](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-website.yml), [Port Telnet](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-port.yml),
[Http Api](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-api.yml), [Ping Connect](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-ping.yml),
[Jvm](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-jvm.yml), [SiteMap](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-fullsite.yml),
[Ssl Certificate](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-ssl_cert.yml), [SpringBoot2](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-springboot2.yml),
[FTP Server](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-ftp.yml), [SpringBoot3](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-springboot3.yml)
- [Mysql](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-mysql.yml), [PostgreSQL](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-postgresql.yml),
[MariaDB](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-mariadb.yml), [Redis](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-redis.yml),
[ElasticSearch](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-elasticsearch.yml), [SqlServer](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-sqlserver.yml),
[Oracle](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-oracle.yml), [MongoDB](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-mongodb.yml),
[DM](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-dm.yml), [OpenGauss](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-opengauss.yml),
[ClickHouse](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-clickhouse.yml), [IoTDB](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-iotdb.yml),
[Redis Cluster](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-redis_cluster.yml), [Redis Sentinel](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-redis_sentinel.yml)
- [Linux](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-linux.yml), [Ubuntu](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-ubuntu.yml),
[CentOS](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-centos.yml), [Windows](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-windows.yml),
[EulerOS](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-euleros.yml), [Fedora CoreOS](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-coreos.yml),
[OpenSUSE](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-opensuse.yml), [Rocky Linux](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-rockylinux.yml),
[Red Hat](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-redhat.yml), [FreeBSD](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-freebsd.yml),
[AlmaLinux](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-almalinux.yml), [Debian Linux](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-debian.yml)
- [Tomcat](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-tomcat.yml), [Nacos](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-nacos.yml),
[Zookeeper](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-zookeeper.yml), [RabbitMQ](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-rabbitmq.yml),
[Flink](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-flink.yml), [Kafka](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-kafka.yml),
[ShenYu](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-shenyu.yml), [DynamicTp](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-dynamic_tp.yml),
[Jetty](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-jetty.yml), [ActiveMQ](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-activemq.yml)
- [Kubernetes](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-kubernetes.yml), [Docker](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-docker.yml)
- [CiscoSwitch](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-cisco_switch.yml), [HpeSwitch](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-hpe_switch.yml),
[HuaweiSwitch](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-huawei_switch.yml), [TpLinkSwitch](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-tplink_switch.yml),
[H3cSwitch](https://raw.githubusercontent.com/dromara/hertzbeat/master/manager/src/main/resources/define/app-h3c_switch.yml)
- And More Your Custom Template.
- Notified Support `Discord` `Slack` `Telegram` `Email` `DingDing` `WeChat` `FeiShu` `Webhook` `SMS`.
## Build HertzBeat Collector Install Package
1. Execute command in root
```mvn clean install```
2. Execute command in collector module
```shell
cd collector
mvn clean package -Pcluster
```
......@@ -22,29 +22,20 @@
<groupId>org.dromara.hertzbeat</groupId>
<version>1.0</version>
</parent>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>10</source>
<target>10</target>
</configuration>
</plugin>
</plugins>
</build>
<modelVersion>4.0.0</modelVersion>
<artifactId>hertzbeat-collector</artifactId>
<name>${project.artifactId}</name>
<properties>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
</properties>
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
......@@ -65,6 +56,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- run sql script-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
<!-- kafka -->
<dependency>
<groupId>org.apache.kafka</groupId>
......@@ -171,5 +168,110 @@
<version>4.9.4</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>inner</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<finalName>hertzbeat-collector</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>cluster</id>
<build>
<finalName>hertzbeat-collector</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>*.yml</include>
<include>*.properties</include>
<include>banner.txt</include>
<include>META-INF/**</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<configuration>
<classesDirectory>target/classes/</classesDirectory>
<archive>
<!--生成的jar包不包含maven描述相关文件-->
<addMavenDescriptor>false</addMavenDescriptor>
<manifest>
<!--项目启动类-->
<mainClass>org.dromara.hertzbeat.collector.Collector</mainClass>
<useUniqueVersions>false</useUniqueVersions>
<!--第三方JAR加入类构建的路径maven-dependency-plugin-->
<addClasspath>true</addClasspath>
<!--外部依赖jar包的位置-->
<classpathPrefix>lib/</classpathPrefix>
</manifest>
<manifestEntries>
<Class-Path>. config</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>${maven-assembly-plugin.version}</version>
<executions>
<execution>
<id>make-zip</id>
<!--绑定的maven操作-->
<phase>package</phase>
<!--运行一次-->
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>../script/assembly/collector/assembly.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
package org.dromara.hertzbeat.collector;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* collector startup
* @author tom
*/
@SpringBootApplication
public class Collector {
public static void main(String[] args) {
SpringApplication.run(Collector.class, args);
}
}
package org.dromara.hertzbeat.collector.collect;
import org.dromara.hertzbeat.common.entity.job.protocol.HttpProtocol;
import org.dromara.hertzbeat.common.entity.message.CollectRep;
import org.springframework.beans.factory.InitializingBean;
import java.util.List;
/**
* 不同数据格式解析抽象类
* @author myth
*
*/
public interface AbstractParseResponse extends InitializingBean {
/**
* 通用解析抽象方法
*
* @param resp
* @param aliasFields
* @param http
* @param builder
* @param responseTime
*/
public default void parseResponse(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder, Long responseTime) {
}
}
......@@ -159,6 +159,7 @@ public class CommonCache {
* @param timeDiff 缓存对象保存时间 millis
*/
public void addCache(Object key, Object value, Long timeDiff) {
removeCache(key);
if (timeDiff == null) {
timeDiff = DEFAULT_CACHE_TIMEOUT;
}
......
......@@ -14,23 +14,25 @@ import org.apache.sshd.core.CoreModuleProperties;
@Slf4j
public class CommonSshClient {
private static SshClient sshClient;
private static final SshClient SSH_CLIENT;
static {
sshClient = SshClient.setUpDefaultClient();
SSH_CLIENT = SshClient.setUpDefaultClient();
// 接受所有服务端公钥校验,会打印warn日志 Server at {} presented unverified {} key: {}
AcceptAllServerKeyVerifier verifier = AcceptAllServerKeyVerifier.INSTANCE;
sshClient.setServerKeyVerifier(verifier);
// 设置链接保活心跳2000毫秒一次, 客户端等待保活心跳响应超时时间300_0000毫秒
SSH_CLIENT.setServerKeyVerifier(verifier);
// 设置链接保活心跳2000毫秒一次, 客户端等待保活心跳响应超时时间300_000毫秒
PropertyResolverUtils.updateProperty(
sshClient, CoreModuleProperties.HEARTBEAT_INTERVAL.getName(), 2000);
SSH_CLIENT, CoreModuleProperties.HEARTBEAT_INTERVAL.getName(), 2000);
PropertyResolverUtils.updateProperty(
sshClient, CoreModuleProperties.HEARTBEAT_REPLY_WAIT.getName(), 300_000);
sshClient.start();
SSH_CLIENT, CoreModuleProperties.HEARTBEAT_REPLY_WAIT.getName(), 300_000);
PropertyResolverUtils.updateProperty(
SSH_CLIENT, CoreModuleProperties.SOCKET_KEEPALIVE.getName(), true);
SSH_CLIENT.start();
}
public static SshClient getSshClient() {
return sshClient;
return SSH_CLIENT;
}
}
......@@ -32,6 +32,8 @@ import org.dromara.hertzbeat.common.constants.CommonConstants;
import org.dromara.hertzbeat.common.util.CommonUtil;
import lombok.extern.slf4j.Slf4j;
import org.postgresql.util.PSQLException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import java.sql.Connection;
import java.sql.DriverManager;
......@@ -54,6 +56,8 @@ public class JdbcCommonCollect extends AbstractCollect {
private static final String QUERY_TYPE_ONE_ROW = "oneRow";
private static final String QUERY_TYPE_MULTI_ROW = "multiRow";
private static final String QUERY_TYPE_COLUMNS = "columns";
private static final String RUN_SCRIPT = "runScript";
public JdbcCommonCollect(){}
......@@ -84,6 +88,11 @@ public class JdbcCommonCollect extends AbstractCollect {
case QUERY_TYPE_COLUMNS:
queryOneRowByMatchTwoColumns(statement, jdbcProtocol.getSql(), metrics.getAliasFields(), builder, startTime);
break;
case RUN_SCRIPT:
Connection connection = statement.getConnection();
FileSystemResource rc = new FileSystemResource(jdbcProtocol.getSql());
ScriptUtils.executeSqlScript(connection, rc);
break;
default:
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("Not support database query type: " + jdbcProtocol.getQueryType());
......
......@@ -91,14 +91,14 @@ import static org.dromara.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
*/
@Slf4j
public class HttpCollectImpl extends AbstractCollect {
private final Set<Integer> defaultSuccessStatusCodes = Stream.of(HttpStatus.SC_OK, HttpStatus.SC_CREATED,
private final Set<Integer> defaultSuccessStatusCodes = Stream.of(HttpStatus.SC_OK, HttpStatus.SC_CREATED,
HttpStatus.SC_ACCEPTED, HttpStatus.SC_MULTIPLE_CHOICES, HttpStatus.SC_MOVED_PERMANENTLY,
HttpStatus.SC_MOVED_TEMPORARILY).collect(Collectors.toSet());
public HttpCollectImpl() {
}
@Override
public void collect(CollectRep.MetricsData.Builder builder,
long appId, String app, Metrics metrics) {
......@@ -115,7 +115,7 @@ public class HttpCollectImpl extends AbstractCollect {
HttpUriRequest request = createHttpRequest(metrics.getHttp());
try {
CloseableHttpResponse response = CommonHttpClient.getHttpClient()
.execute(request, httpContext);
.execute(request, httpContext);
int statusCode = response.getStatusLine().getStatusCode();
boolean isSuccessInvoke = checkSuccessInvoke(metrics, statusCode);
log.debug("http response status: {}", statusCode);
......@@ -193,28 +193,28 @@ public class HttpCollectImpl extends AbstractCollect {
}
}
}
@Override
public String supportProtocol() {
return DispatchConstants.PROTOCOL_HTTP;
}
private void validateParams(Metrics metrics) throws Exception {
if (metrics == null || metrics.getHttp() == null) {
throw new Exception("Http/Https collect must has http params");
}
HttpProtocol httpProtocol = metrics.getHttp();
if (httpProtocol.getUrl() == null
|| "".equals(httpProtocol.getUrl())
|| !httpProtocol.getUrl().startsWith(RIGHT_DASH)) {
|| "".equals(httpProtocol.getUrl())
|| !httpProtocol.getUrl().startsWith(RIGHT_DASH)) {
httpProtocol.setUrl(httpProtocol.getUrl() == null ? RIGHT_DASH : RIGHT_DASH + httpProtocol.getUrl().trim());
}
if (CollectionUtils.isEmpty(httpProtocol.getSuccessCodes())) {
httpProtocol.setSuccessCodes(List.of("200"));
}
}
private void parseResponseByWebsite(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder, Long responseTime) {
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
......@@ -231,7 +231,7 @@ public class HttpCollectImpl extends AbstractCollect {
}
builder.addValues(valueRowBuilder.build());
}
private void parseResponseBySiteMap(String resp, List<String> aliasFields,
CollectRep.MetricsData.Builder builder) {
List<String> siteUrls = new LinkedList<>();
......@@ -306,7 +306,7 @@ public class HttpCollectImpl extends AbstractCollect {
valueRowBuilder.addColumns(siteUrl);
} else if (CollectorConstants.STATUS_CODE.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumns(statusCode == null ?
CommonConstants.NULL_VALUE : String.valueOf(statusCode));
CommonConstants.NULL_VALUE : String.valueOf(statusCode));
} else if (CollectorConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumns(String.valueOf(responseTime));
} else if (CollectorConstants.ERROR_MSG.equalsIgnoreCase(alias)) {
......@@ -318,11 +318,11 @@ public class HttpCollectImpl extends AbstractCollect {
builder.addValues(valueRowBuilder.build());
}
}
private void parseResponseByXmlPath(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder) {
}
private void parseResponseByJsonPath(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder, Long responseTime) {
List<Object> results = JsonPathParser.parseContentWithJsonPath(resp, http.getParseScript());
......@@ -375,15 +375,15 @@ public class HttpCollectImpl extends AbstractCollect {
}
}
}
private void parseResponseByPromQl(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder) {
AbstractPrometheusParse prometheusParser = PrometheusParseCreater.getPrometheusParse();
prometheusParser.handle(resp, aliasFields, http, builder);
}
private static final Map<Long, ExporterParser> EXPORTER_PARSER_TABLE = new ConcurrentHashMap<>();
private void parseResponseByPrometheusExporter(String resp, List<String> aliasFields,
CollectRep.MetricsData.Builder builder) {
if (!EXPORTER_PARSER_TABLE.containsKey(builder.getId())) {
......@@ -396,8 +396,8 @@ public class HttpCollectImpl extends AbstractCollect {
MetricFamily metricFamily = metricFamilyMap.get(metrics);
for (MetricFamily.Metric metric : metricFamily.getMetricList()) {
Map<String, String> labelMap = metric.getLabelPair()
.stream()
.collect(Collectors.toMap(MetricFamily.Label::getName, MetricFamily.Label::getValue));
.stream()
.collect(Collectors.toMap(MetricFamily.Label::getName, MetricFamily.Label::getValue));
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String aliasField : aliasFields) {
if ("value".equals(aliasField)) {
......@@ -418,7 +418,7 @@ public class HttpCollectImpl extends AbstractCollect {
}
}
}
private void parseResponseByDefault(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder, Long responseTime) {
JsonElement element = JsonParser.parseString(resp);
......@@ -432,7 +432,7 @@ public class HttpCollectImpl extends AbstractCollect {
getValueFromJson(aliasFields, builder, responseTime, element, keywordNum);
}
}
private void getValueFromJson(List<String> aliasFields, CollectRep.MetricsData.Builder builder, Long responseTime, JsonElement element, int keywordNum) {
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
......@@ -455,7 +455,7 @@ public class HttpCollectImpl extends AbstractCollect {
builder.addValues(valueRowBuilder.build());
}
}
/**
* create httpContext
*
......@@ -467,7 +467,7 @@ public class HttpCollectImpl extends AbstractCollect {
if (auth != null && DispatchConstants.DIGEST_AUTH.equals(auth.getType())) {
HttpClientContext clientContext = new HttpClientContext();
if (StringUtils.hasText(auth.getDigestAuthUsername())
&& StringUtils.hasText(auth.getDigestAuthPassword())) {
&& StringUtils.hasText(auth.getDigestAuthPassword())) {
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials
= new UsernamePasswordCredentials(auth.getBasicAuthUsername(), auth.getBasicAuthPassword());
......@@ -481,7 +481,7 @@ public class HttpCollectImpl extends AbstractCollect {
}
return null;
}
/**
* 根据http配置参数构造请求头
*
......@@ -512,8 +512,7 @@ public class HttpCollectImpl extends AbstractCollect {
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> param : params.entrySet()) {
if (StringUtils.hasText(param.getValue())) {
requestBuilder.addParameter(CollectUtil.replaceUriSpecialChar(param.getKey()),
CollectUtil.replaceUriSpecialChar(param.getValue()));
requestBuilder.addParameter(param.getKey(), param.getValue());
}
}
}
......@@ -533,44 +532,46 @@ public class HttpCollectImpl extends AbstractCollect {
}
// add accept
if (DispatchConstants.PARSE_DEFAULT.equals(httpProtocol.getParseType())
|| DispatchConstants.PARSE_JSON_PATH.equals(httpProtocol.getParseType())) {
|| DispatchConstants.PARSE_JSON_PATH.equals(httpProtocol.getParseType())) {
requestBuilder.addHeader(HttpHeaders.ACCEPT, "application/json");
} else if (DispatchConstants.PARSE_XML_PATH.equals(httpProtocol.getParseType())) {
requestBuilder.addHeader(HttpHeaders.ACCEPT, "text/xml,application/xml");
} else {
requestBuilder.addHeader(HttpHeaders.ACCEPT, "*/*");
}
// 判断是否使用Bearer Token认证
if (httpProtocol.getAuthorization() != null) {
HttpProtocol.Authorization authorization = httpProtocol.getAuthorization();
if (DispatchConstants.BEARER_TOKEN.equals(authorization.getType())) {
if (DispatchConstants.BEARER_TOKEN.equalsIgnoreCase(authorization.getType())) {
// 若使用 将token放入到header里面
String value = DispatchConstants.BEARER + " " + authorization.getBearerTokenToken();
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, value);
} else if (DispatchConstants.BASIC_AUTH.equals(authorization.getType())) {
if (StringUtils.hasText(authorization.getBasicAuthUsername())
&& StringUtils.hasText(authorization.getBasicAuthPassword())) {
&& StringUtils.hasText(authorization.getBasicAuthPassword())) {
String authStr = authorization.getBasicAuthUsername() + ":" + authorization.getBasicAuthPassword();
String encodedAuth = new String(Base64.encodeBase64(authStr.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
requestBuilder.addHeader(HttpHeaders.AUTHORIZATION, DispatchConstants.BASIC + " " + encodedAuth);
}
}
}
// 请求内容,会覆盖post协议的params
if (StringUtils.hasLength(httpProtocol.getPayload())) {
requestBuilder.setEntity(new StringEntity(httpProtocol.getPayload(), StandardCharsets.UTF_8));
}
// uri
String uri = CollectUtil.replaceUriSpecialChar(httpProtocol.getUrl());
if (IpDomainUtil.isHasSchema(httpProtocol.getHost())) {
requestBuilder.setUri(httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri);
} else {
String ipAddressType = IpDomainUtil.checkIpAddressType(httpProtocol.getHost());
String baseUri = CollectorConstants.IPV6.equals(ipAddressType) ? String.format("[%s]:%s%s",httpProtocol.getHost(),httpProtocol.getPort(),uri) : String.format("%s:%s%s",httpProtocol.getHost(),httpProtocol.getPort(),uri);
String baseUri = CollectorConstants.IPV6.equals(ipAddressType)
? String.format("[%s]:%s%s", httpProtocol.getHost(), httpProtocol.getPort(), uri)
: String.format("%s:%s%s", httpProtocol.getHost(), httpProtocol.getPort(), uri);
boolean ssl = Boolean.parseBoolean(httpProtocol.getSsl());
if (ssl) {
requestBuilder.setUri(CollectorConstants.HTTPS_HEADER + baseUri);
......@@ -578,20 +579,20 @@ public class HttpCollectImpl extends AbstractCollect {
requestBuilder.setUri(CollectorConstants.HTTP_HEADER + baseUri);
}
}
// custom timeout
int timeout = CollectUtil.getTimeout(httpProtocol.getTimeout(), 0);
if (timeout > 0) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.setRedirectsEnabled(true)
.build();
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.setRedirectsEnabled(true)
.build();
requestBuilder.setConfig(requestConfig);
}
return requestBuilder.build();
}
private boolean checkSuccessInvoke(Metrics metrics, int statusCode) {
List<String> successCodes = metrics.getHttp().getSuccessCodes();
Set<Integer> successCodeSet = successCodes != null ? successCodes.stream().map(code -> {
......
......@@ -17,8 +17,8 @@
package org.dromara.hertzbeat.collector.collect.mongodb;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Optional;
......@@ -47,20 +47,18 @@ import lombok.extern.slf4j.Slf4j;
* Mongodb 单机指标收集器
*
* @author <a href="mailto:liudonghua123@gmail.com">liudonghua</a>
* @version 1.0
* Created by liudonghua on 2023/01/01
* see also https://www.mongodb.com/languages/java,
* https://www.mongodb.com/docs/manual/reference/command/serverStatus/#metrics
* see also https://www.mongodb.com/languages/java,
* https://www.mongodb.com/docs/manual/reference/command/serverStatus/#metrics
*/
@Slf4j
public class MongodbSingleCollectImpl extends AbstractCollect {
/**
* 支持的 mongodb diagnostic 命令,排除internal/deprecated相关的命令
* 可参考 https://www.mongodb.com/docs/manual/reference/command/nav-diagnostic/,
* https://www.mongodb.com/docs/mongodb-shell/run-commands/
* 可参考 <a href="https://www.mongodb.com/docs/manual/reference/command/nav-diagnostic/">...</a>,
* <a href="https://www.mongodb.com/docs/mongodb-shell/run-commands/">...</a>
* 注意:一些命令需要相应的权限才能执行,否则执行虽然不会报错,但是返回的结果是空的,
* 详见 https://www.mongodb.com/docs/manual/reference/built-in-roles/
* 详见 <a href="https://www.mongodb.com/docs/manual/reference/built-in-roles/">...</a>
*/
private static final String[] SUPPORTED_MONGODB_DIAGNOSTIC_COMMANDS = {
"buildInfo",
......@@ -199,14 +197,10 @@ public class MongodbSingleCollectImpl extends AbstractCollect {
}
// 复用失败则新建连接 connect to mongodb
String url;
try {
// 密码可能包含特殊字符,需要使用类似js的encodeURIComponent进行编码,这里使用java的URLEncoder
url = String.format("mongodb://%s:%s@%s:%s/%s?authSource=%s", mongodbProtocol.getUsername(),
URLEncoder.encode(mongodbProtocol.getPassword(), "UTF-8"), mongodbProtocol.getHost(), mongodbProtocol.getPort(),
mongodbProtocol.getDatabase(), mongodbProtocol.getAuthenticationDatabase());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
// 密码可能包含特殊字符,需要使用类似js的encodeURIComponent进行编码,这里使用java的URLEncoder
url = String.format("mongodb://%s:%s@%s:%s/%s?authSource=%s", mongodbProtocol.getUsername(),
URLEncoder.encode(mongodbProtocol.getPassword(), StandardCharsets.UTF_8), mongodbProtocol.getHost(), mongodbProtocol.getPort(),
mongodbProtocol.getDatabase(), mongodbProtocol.getAuthenticationDatabase());
mongoClient = MongoClients.create(url);
MongodbConnect mongodbConnect = new MongodbConnect(mongoClient);
CommonCache.getInstance().addCache(identifier, mongodbConnect);
......
......@@ -17,6 +17,10 @@
package org.dromara.hertzbeat.collector.collect.ssh;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.exception.SshChannelOpenException;
import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.dromara.hertzbeat.collector.collect.AbstractCollect;
import org.dromara.hertzbeat.collector.collect.common.cache.CacheIdentifier;
import org.dromara.hertzbeat.collector.collect.common.cache.CommonCache;
......@@ -24,8 +28,8 @@ import org.dromara.hertzbeat.collector.collect.common.cache.SshConnect;
import org.dromara.hertzbeat.collector.collect.common.ssh.CommonSshClient;
import org.dromara.hertzbeat.collector.dispatch.DispatchConstants;
import org.dromara.hertzbeat.collector.util.CollectUtil;
import org.dromara.hertzbeat.collector.util.PrivateKeyUtils;
import org.dromara.hertzbeat.common.constants.CollectorConstants;
import org.dromara.hertzbeat.collector.util.KeyPairUtil;
import org.dromara.hertzbeat.common.entity.job.Metrics;
import org.dromara.hertzbeat.common.entity.job.protocol.SshProtocol;
import org.dromara.hertzbeat.common.entity.message.CollectRep;
......@@ -39,10 +43,14 @@ import org.apache.sshd.client.session.ClientSession;
import org.springframework.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
......@@ -55,7 +63,6 @@ import java.util.stream.Collectors;
* ssh协议采集实现
*
* @author tom
*
*/
@Slf4j
public class SshCollectImpl extends AbstractCollect {
......@@ -63,7 +70,7 @@ public class SshCollectImpl extends AbstractCollect {
private static final String PARSE_TYPE_ONE_ROW = "oneRow";
private static final String PARSE_TYPE_MULTI_ROW = "multiRow";
private static final String PARSE_TYPE_NETCAT = "netcat";
private static final int DEFAULT_TIMEOUT = 10_000;
public SshCollectImpl() {
......@@ -81,24 +88,24 @@ public class SshCollectImpl extends AbstractCollect {
return;
}
SshProtocol sshProtocol = metrics.getSsh();
boolean reuseConnection = Boolean.parseBoolean(sshProtocol.getReuseConnection());
int timeout = CollectUtil.getTimeout(sshProtocol.getTimeout(), DEFAULT_TIMEOUT);
ClientChannel channel = null;
ClientSession clientSession = null;
try {
ClientSession clientSession = getConnectSession(sshProtocol, timeout);
clientSession = getConnectSession(sshProtocol, timeout, reuseConnection);
channel = clientSession.createExecChannel(sshProtocol.getScript());
ByteArrayOutputStream response = new ByteArrayOutputStream();
channel.setOut(response);
if (!channel.open().verify(timeout).isOpened()) {
removeConnectSessionCache(sshProtocol);
channel.close();
clientSession.close();
throw new Exception("ssh channel open failed");
}
channel.setErr(new NoCloseOutputStream(System.err));
channel.open().verify(timeout);
List<ClientChannelEvent> list = new ArrayList<>();
list.add(ClientChannelEvent.CLOSED);
channel.waitFor(list, timeout);
Collection<ClientChannelEvent> waitEvents = channel.waitFor(list, timeout);
if (waitEvents.contains(ClientChannelEvent.TIMEOUT)) {
throw new SocketTimeoutException("Failed to retrieve command result in time: " + sshProtocol.getScript());
}
Long responseTime = System.currentTimeMillis() - startTime;
channel.close();
String result = response.toString();
if (!StringUtils.hasText(result)) {
builder.setCode(CollectRep.Code.FAIL);
......@@ -124,11 +131,19 @@ public class SshCollectImpl extends AbstractCollect {
log.info(errorMsg);
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
builder.setMsg("The peer refused to connect: service port does not listening or firewall: " + errorMsg);
} catch (SshException sshException) {
Throwable throwable = sshException.getCause();
if (throwable instanceof SshChannelOpenException) {
log.warn("Remote ssh server no more session channel, please increase sshd_config MaxSessions.");
}
String errorMsg = CommonUtil.getMessageFromThrowable(sshException);
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
builder.setMsg("Peer ssh connection failed: " + errorMsg);
} catch (IOException ioException) {
String errorMsg = CommonUtil.getMessageFromThrowable(ioException);
log.info(errorMsg);
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
builder.setMsg("Peer connection failed: " + errorMsg);
builder.setMsg("Peer io connection failed: " + errorMsg);
} catch (Exception exception) {
String errorMsg = CommonUtil.getMessageFromThrowable(exception);
log.warn(errorMsg, exception);
......@@ -142,6 +157,13 @@ public class SshCollectImpl extends AbstractCollect {
log.error(e.getMessage(), e);
}
}
if (clientSession != null && !reuseConnection) {
try {
clientSession.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
}
......@@ -236,7 +258,7 @@ public class SshCollectImpl extends AbstractCollect {
builder.addValues(valueRowBuilder.build());
}
}
private void removeConnectSessionCache(SshProtocol sshProtocol) {
CacheIdentifier identifier = CacheIdentifier.builder()
.ip(sshProtocol.getHost()).port(sshProtocol.getPort())
......@@ -245,28 +267,31 @@ public class SshCollectImpl extends AbstractCollect {
CommonCache.getInstance().removeCache(identifier);
}
private ClientSession getConnectSession(SshProtocol sshProtocol, int timeout) throws IOException {
private ClientSession getConnectSession(SshProtocol sshProtocol, int timeout, boolean reuseConnection)
throws IOException, GeneralSecurityException {
CacheIdentifier identifier = CacheIdentifier.builder()
.ip(sshProtocol.getHost()).port(sshProtocol.getPort())
.username(sshProtocol.getUsername()).password(sshProtocol.getPassword())
.build();
Optional<Object> cacheOption = CommonCache.getInstance().getCache(identifier, true);
.ip(sshProtocol.getHost()).port(sshProtocol.getPort())
.username(sshProtocol.getUsername()).password(sshProtocol.getPassword())
.build();
ClientSession clientSession = null;
if (cacheOption.isPresent()) {
clientSession = ((SshConnect) cacheOption.get()).getConnection();
try {
if (clientSession == null || clientSession.isClosed() || clientSession.isClosing()) {
if (reuseConnection) {
Optional<Object> cacheOption = CommonCache.getInstance().getCache(identifier, true);
if (cacheOption.isPresent()) {
clientSession = ((SshConnect) cacheOption.get()).getConnection();
try {
if (clientSession == null || clientSession.isClosed() || clientSession.isClosing()) {
clientSession = null;
CommonCache.getInstance().removeCache(identifier);
}
} catch (Exception e) {
log.warn(e.getMessage());
clientSession = null;
CommonCache.getInstance().removeCache(identifier);
}
} catch (Exception e) {
log.warn(e.getMessage());
clientSession = null;
CommonCache.getInstance().removeCache(identifier);
}
}
if (clientSession != null) {
return clientSession;
if (clientSession != null) {
return clientSession;
}
}
SshClient sshClient = CommonSshClient.getSshClient();
clientSession = sshClient.connect(sshProtocol.getUsername(), sshProtocol.getHost(), Integer.parseInt(sshProtocol.getPort()))
......@@ -274,21 +299,20 @@ public class SshCollectImpl extends AbstractCollect {
if (StringUtils.hasText(sshProtocol.getPassword())) {
clientSession.addPasswordIdentity(sshProtocol.getPassword());
} else if (StringUtils.hasText(sshProtocol.getPrivateKey())) {
var keyPair = KeyPairUtil.getKeyPairFromPrivateKey(sshProtocol.getPrivateKey());
if (keyPair != null) {
clientSession.addPublicKeyIdentity(keyPair);
}
} else {
clientSession.close();
throw new IllegalArgumentException("please input password or secret.");
}
var resourceKey = PrivateKeyUtils.writePrivateKey(sshProtocol.getHost(), sshProtocol.getPrivateKey());
SecurityUtils.loadKeyPairIdentities(null, () -> resourceKey, new FileInputStream(resourceKey), null)
.forEach(clientSession::addPublicKeyIdentity);
} // else auth with localhost private public key certificates
// auth
if (!clientSession.auth().verify(timeout, TimeUnit.MILLISECONDS).isSuccess()) {
clientSession.close();
throw new IllegalArgumentException("ssh auth failed.");
}
SshConnect sshConnect = new SshConnect(clientSession);
CommonCache.getInstance().addCache(identifier, sshConnect);
if (reuseConnection) {
SshConnect sshConnect = new SshConnect(clientSession);
CommonCache.getInstance().addCache(identifier, sshConnect);
}
return clientSession;
}
......
......@@ -3,6 +3,8 @@ package org.dromara.hertzbeat.collector.collect.strategy;
import org.dromara.hertzbeat.collector.collect.AbstractCollect;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
......@@ -14,6 +16,7 @@ import java.util.concurrent.ConcurrentHashMap;
*
*/
@Configuration
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class CollectStrategyFactory implements CommandLineRunner {
/**
......
package org.dromara.hertzbeat.collector.collect.strategy;
import org.dromara.hertzbeat.collector.collect.AbstractParseResponse;
import org.dromara.hertzbeat.collector.dispatch.DispatchConstants;
import org.springframework.util.StringUtils;
import java.util.concurrent.ConcurrentHashMap;
/**
* 数据收集策略工厂
* @author :myth
*
*/
public class ParseStrategyFactory {
/**
* 策略容器
*/
private static ConcurrentHashMap<String, AbstractParseResponse> strategy = new ConcurrentHashMap<>();
/**
* 获取注册的收集实现类
*
* @param name
* @return
*/
public static AbstractParseResponse invoke(String name) {
AbstractParseResponse abstractCollect = strategy.get(name);
if (abstractCollect == null) strategy.get(DispatchConstants.PARSE_DEFAULT);
return abstractCollect;
}
/**
* 注册的收集实现类
*
* @param name
* @param abstractParseResponse
*/
public static void register(String name, AbstractParseResponse abstractParseResponse) {
if (StringUtils.isEmpty(name) || abstractParseResponse == null) {
return;
}
strategy.put(name, abstractParseResponse);
}
}
......@@ -67,11 +67,24 @@ public class DispatchProperties {
* 入口可以时etcd信息,http请求,消息中间件消息请求
*/
public static class EntranceProperties {
/**
* netty server client config
*/
private NettyProperties netty;
/**
* etcd配置信息
*/
private EtcdProperties etcd;
public NettyProperties getNetty() {
return netty;
}
public void setNetty(NettyProperties netty) {
this.netty = netty;
}
public EtcdProperties getEtcd() {
return etcd;
}
......@@ -86,7 +99,7 @@ public class DispatchProperties {
* Whether etcd scheduling is started
* etcd调度是否启动
*/
private boolean enabled = true;
private boolean enabled = false;
/**
* etcd's connection endpoint url
......@@ -194,6 +207,62 @@ public class DispatchProperties {
this.jobDir = jobDir;
}
}
public static class NettyProperties {
/**
* whether netty scheduling is started
*/
private boolean enabled = false;
/**
* this collector unique identity
* default is the host name
*/
private String identity;
/**
* connect cluster master ip
*/
private String managerIp;
/**
* connect cluster master port
*/
private int managerPort = 1158;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getIdentity() {
return identity;
}
public void setIdentity(String identity) {
this.identity = identity;
}
public String getManagerIp() {
return managerIp;
}
public void setManagerIp(String managerIp) {
this.managerIp = managerIp;
}
public int getManagerPort() {
return managerPort;
}
public void setManagerPort(int managerPort) {
this.managerPort = managerPort;
}
}
}
/**
......
......@@ -139,7 +139,6 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
response.setCode(CollectRep.Code.FAIL);
response.setMsg("not support " + app + ", "
+ metrics.getName() + ", " + metrics.getProtocol());
return;
} else {
try {
abstractCollect.collect(response, monitorId, app, metrics);
......
......@@ -17,17 +17,27 @@
package org.dromara.hertzbeat.collector.dispatch.entrance.internal;
import io.netty.channel.Channel;
import org.dromara.hertzbeat.collector.dispatch.DispatchProperties;
import org.dromara.hertzbeat.collector.dispatch.WorkerPool;
import org.dromara.hertzbeat.collector.dispatch.timer.TimerDispatch;
import org.dromara.hertzbeat.common.entity.dto.CollectorInfo;
import org.dromara.hertzbeat.common.entity.job.Job;
import org.dromara.hertzbeat.common.entity.message.ClusterMsg;
import org.dromara.hertzbeat.common.entity.message.CollectRep;
import org.dromara.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.dromara.hertzbeat.common.util.IpDomainUtil;
import org.dromara.hertzbeat.common.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.dromara.hertzbeat.common.util.ProtoJsonUtil;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
......@@ -40,9 +50,32 @@ import java.util.concurrent.TimeUnit;
@Slf4j
public class CollectJobService {
@Autowired
private TimerDispatch timerDispatch;
private static final String COLLECTOR_STR = "-collector";
private final TimerDispatch timerDispatch;
private final WorkerPool workerPool;
private String collectorIdentity = null;
private volatile Channel collectorChannel = null;
public CollectJobService(TimerDispatch timerDispatch, DispatchProperties properties, WorkerPool workerPool) {
this.timerDispatch = timerDispatch;
this.workerPool = workerPool;
if (properties != null && properties.getEntrance() != null
&& properties.getEntrance().getNetty() != null && properties.getEntrance().getNetty().isEnabled()) {
String collectorName = properties.getEntrance().getNetty().getIdentity();
if (StringUtils.hasText(collectorName)) {
collectorIdentity = collectorName;
} else {
String identity = IpDomainUtil.getCurrentHostName() + COLLECTOR_STR;
log.info("user not config this collector identity, use [host name - host ip] default: {}.", identity);
collectorIdentity = IpDomainUtil.getCurrentHostName() + COLLECTOR_STR;
}
}
}
/**
* Execute a one-time collection task and get the collected data response
* 执行一次性采集任务,获取采集数据响应
......@@ -70,35 +103,38 @@ public class CollectJobService {
}
return metricsData;
}
/**
* Issue periodic asynchronous collection tasks
* 下发周期性异步采集任务
* Execute a one-time collection task and send the collected data response
*
* @param job Collect task details 采集任务详情
* @return long Job ID 任务ID
* @param oneTimeJob Collect task details 采集任务详情
* @param channel channel
*/
public long addAsyncCollectJob(Job job) {
long jobId = SnowFlakeIdGenerator.generateId();
job.setId(jobId);
timerDispatch.addJob(job, null);
return job.getId();
public void collectSyncJobData(Job oneTimeJob, Channel channel) {
workerPool.executeJob(() -> {
List<CollectRep.MetricsData> metricsDataList = this.collectSyncJobData(oneTimeJob);
List<String> jsons = new ArrayList<>(metricsDataList.size());
for (CollectRep.MetricsData metricsData : metricsDataList) {
String json = ProtoJsonUtil.toJsonStr(metricsData);
if (json != null) {
jsons.add(json);
}
}
String response = JsonUtil.toJson(jsons);
channel.writeAndFlush(ClusterMsg.Message.newBuilder()
.setMsg(response)
.setType(ClusterMsg.MessageType.RESPONSE_ONE_TIME_TASK_DATA).build());
});
}
/**
* Update the periodic asynchronous collection tasks that have been delivered
* 更新已经下发的周期性异步采集任务
* Issue periodic asynchronous collection tasks
* 下发周期性异步采集任务
*
* @param modifyJob Collect task details 采集任务详情
* @return long Job ID 新任务ID
* @param job Collect task details 采集任务详情
*/
public long updateAsyncCollectJob(Job modifyJob) {
long preJobId = modifyJob.getId();
long newJobId = SnowFlakeIdGenerator.generateId();
modifyJob.setId(newJobId);
timerDispatch.deleteJob(preJobId, true);
timerDispatch.addJob(modifyJob, null);
return newJobId;
public void addAsyncCollectJob(Job job) {
timerDispatch.addJob(job.clone(), null);
}
/**
......@@ -112,5 +148,50 @@ public class CollectJobService {
timerDispatch.deleteJob(jobId, true);
}
}
/**
* collector online
* @param channel message channel
*/
public void collectorGoOnline(Channel channel) {
collectorChannel = channel;
CollectorInfo collectorInfo = CollectorInfo.builder()
.name(collectorIdentity)
.ip(IpDomainUtil.getLocalhostIp())
// todo more info
.build();
String msg = JsonUtil.toJson(collectorInfo);
ClusterMsg.Message message = ClusterMsg.Message.newBuilder()
.setIdentity(collectorIdentity)
.setType(ClusterMsg.MessageType.GO_ONLINE)
.setMsg(msg)
.build();
channel.writeAndFlush(message);
// start a thread to send heartbeat to cluster server periodically
ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
scheduledExecutor.scheduleAtFixedRate(() -> {
if (collectorChannel.isActive()) {
ClusterMsg.Message heartbeat = ClusterMsg.Message.newBuilder()
.setIdentity(collectorIdentity)
.setType(ClusterMsg.MessageType.HEARTBEAT)
.build();
collectorChannel.writeAndFlush(heartbeat);
log.info("collector send cluster server heartbeat, time: {}.", System.currentTimeMillis());
}
}, 5, 5, TimeUnit.SECONDS);
}
/**
* send async collect response data
* @param metricsData collect data
*/
public void sendAsyncCollectData(CollectRep.MetricsData metricsData) {
String data = ProtoJsonUtil.toJsonStr(metricsData);
ClusterMsg.Message heartbeat = ClusterMsg.Message.newBuilder()
.setIdentity(collectorIdentity)
.setMsg(data)
.setType(ClusterMsg.MessageType.RESPONSE_CYCLIC_TASK_DATA)
.build();
collectorChannel.writeAndFlush(heartbeat);
}
}
package org.dromara.hertzbeat.collector.dispatch.entrance.netty;
import com.fasterxml.jackson.core.type.TypeReference;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hertzbeat.collector.dispatch.entrance.internal.CollectJobService;
import org.dromara.hertzbeat.common.entity.job.Job;
import org.dromara.hertzbeat.common.entity.message.ClusterMsg;
import org.dromara.hertzbeat.common.util.JsonUtil;
import java.util.List;
/**
* netty inbound collector message handler
* @author tom
*/
@Slf4j
public class ClientInboundMessageHandler extends SimpleChannelInboundHandler<ClusterMsg.Message> {
private final CollectJobService collectJobService;
public ClientInboundMessageHandler(CollectJobService collectJobService) {
this.collectJobService = collectJobService;
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ClusterMsg.Message message) throws Exception {
Channel channel = channelHandlerContext.channel();
switch (message.getType()) {
case HEARTBEAT:
log.info("collector receive manager server response heartbeat, time: {}. ", System.currentTimeMillis());
break;
case ISSUE_CYCLIC_TASK:
Job job = JsonUtil.fromJson(message.getMsg(), Job.class);
if (job == null) {
log.error("collector receive cyclic task job is null");
return;
}
collectJobService.addAsyncCollectJob(job);
break;
case ISSUE_ONE_TIME_TASK:
Job oneTimeJob = JsonUtil.fromJson(message.getMsg(), Job.class);
collectJobService.collectSyncJobData(oneTimeJob, channel);
break;
case DELETE_CYCLIC_TASK:
TypeReference<List<Long>> typeReference = new TypeReference<>() {};
List<Long> jobs = JsonUtil.fromJson(message.getMsg(), typeReference);
if (jobs != null && !jobs.isEmpty()) {
for (Long jobId : jobs) {
collectJobService.cancelAsyncCollectJob(jobId);
}
}
break;
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
// go online to cluster master
collectJobService.collectorGoOnline(channel);
}
}
package org.dromara.hertzbeat.collector.dispatch.entrance.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hertzbeat.collector.dispatch.DispatchProperties;
import org.dromara.hertzbeat.collector.dispatch.entrance.internal.CollectJobService;
import org.dromara.hertzbeat.common.support.CommonThreadPool;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
/**
* collect server for cluster
* @author tom
*/
@Component
@ConditionalOnProperty(prefix = "collector.dispatch.entrance.netty",
name = "enabled", havingValue = "true")
@Slf4j
public class CollectServer {
private final CollectJobService collectJobService;
private final CommonThreadPool commonThreadPool;
public CollectServer(DispatchProperties properties, CollectJobService jobService, CommonThreadPool threadPool) throws Exception {
if (properties == null || properties.getEntrance() == null || properties.getEntrance().getNetty() == null) {
log.error("init error, please config dispatch entrance netty props in application.yml");
throw new IllegalArgumentException("please config dispatch entrance netty props");
}
DispatchProperties.EntranceProperties.NettyProperties nettyProperties = properties.getEntrance().getNetty();
if (nettyProperties.getManagerIp() == null || nettyProperties.getManagerPort() == 0) {
throw new IllegalArgumentException("please config dispatch entrance netty master ip and port");
}
this.collectJobService = jobService;
this.commonThreadPool = threadPool;
collectorClientStartup(nettyProperties);
}
private void collectorClientStartup(DispatchProperties.EntranceProperties.NettyProperties properties) throws Exception {
commonThreadPool.execute(() -> {
EventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
b.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ProtoClientInitializer(collectJobService));
Channel channel = null;
boolean first = true;
while (first || channel == null || !channel.isActive()) {
first = false;
try {
channel = b.connect(properties.getManagerIp(), properties.getManagerPort()).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException ignored) {
log.error("collector shutdown now!");
} catch (Exception e2) {
log.error("collector connect cluster server error: {}. try after 10s." , e2.getMessage());
try {
Thread.sleep(10000);
} catch (InterruptedException ignored) {}
}
}
workerGroup.shutdownGracefully();
});
}
}
package org.dromara.hertzbeat.collector.dispatch.entrance.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import org.dromara.hertzbeat.collector.dispatch.entrance.internal.CollectJobService;
import org.dromara.hertzbeat.common.entity.message.ClusterMsg;
/**
* netty client initializer
* @author tom
*/
public class ProtoClientInitializer extends ChannelInitializer<SocketChannel> {
private final CollectJobService collectJobService;
public ProtoClientInitializer(CollectJobService collectJobService) {
this.collectJobService = collectJobService;
}
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// zip
pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
// protocol buf encode decode
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(ClusterMsg.Message.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
// message handler
pipeline.addLast(new ClientInboundMessageHandler(collectJobService));
}
}
/*
* 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.dromara.hertzbeat.collector.dispatch.export;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hertzbeat.collector.dispatch.entrance.internal.CollectJobService;
import org.dromara.hertzbeat.common.entity.alerter.Alert;
import org.dromara.hertzbeat.common.entity.message.CollectRep;
import org.dromara.hertzbeat.common.queue.CommonDataQueue;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* for collector instance
* send collect response data by netty
* @author tom
*/
@Configuration
@ConditionalOnProperty(prefix = "common.queue", name = "type", havingValue = "netty", matchIfMissing = true)
@Slf4j
public class NettyDataQueue implements CommonDataQueue {
private final CollectJobService collectJobService;
public NettyDataQueue(CollectJobService collectJobService) {
this.collectJobService = collectJobService;
}
@Override
public void sendAlertsData(Alert alert) {}
@Override
public Alert pollAlertsData() throws InterruptedException {
return null;
}
@Override
public CollectRep.MetricsData pollMetricsDataToAlerter() throws InterruptedException {
return null;
}
@Override
public CollectRep.MetricsData pollMetricsDataToPersistentStorage() throws InterruptedException {
return null;
}
@Override
public CollectRep.MetricsData pollMetricsDataToRealTimeStorage() throws InterruptedException {
return null;
}
@Override
public void sendMetricsData(CollectRep.MetricsData metricsData) {
collectJobService.sendAsyncCollectData(metricsData);
}
}
......@@ -18,6 +18,7 @@
package org.dromara.hertzbeat.collector.dispatch.timer;
import org.dromara.hertzbeat.common.util.NetworkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -528,7 +529,7 @@ public class HashedWheelTimer implements Timer {
return currentTime;
}
}
if (isWindows()) {
if (NetworkUtil.isWindowsPlatform()) {
sleepTimeMs = sleepTimeMs / 10 * 10;
}
......@@ -802,9 +803,4 @@ public class HashedWheelTimer implements Timer {
}
}
private static final boolean IS_OS_WINDOWS = System.getProperty("os.name", "").toLowerCase(Locale.US).contains("win");
private boolean isWindows() {
return IS_OS_WINDOWS;
}
}
package org.dromara.hertzbeat.collector.util;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;
/**
* 将私钥写入~/.ssh
*
* @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>
* Created by gcdd1993 on 2023/7/9
*/
@Slf4j
@UtilityClass
public class PrivateKeyUtils {
private static final String KEY_PATH = System.getProperty("user.home") + "/.ssh";
/**
* write private key to ~/.ssh, filename is ~/.ssh/id_rsa_${host}
*
* @param host host
* @param keyStr key string
* @return key file path
* @throws IOException if ~/.ssh is not exist and create dir error
*/
public static String writePrivateKey(String host, String keyStr) throws IOException {
var sshPath = Paths.get(KEY_PATH);
if (!Files.exists(sshPath)) {
Files.createDirectories(sshPath);
}
var keyPath = Paths.get(KEY_PATH, "id_rsa_" + host);
if (!Files.exists(keyPath)) {
Files.writeString(keyPath, keyStr);
} else {
var oldKey = Files.readString(keyPath);
if (!Objects.equals(oldKey, keyStr)) {
Files.writeString(keyPath, keyStr);
}
}
return keyPath.toString();
}
}
# 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.
server:
port: 1159
spring:
application:
name: ${HOSTNAME:@hertzbeat-collector@}${PID}
profiles:
active: cluster
jackson:
default-property-inclusion: ALWAYS
# need to disable spring boot mongodb auto config, or default mongodb connection tried and failed...
autoconfigure:
exclude: org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
---
spring:
config:
activate:
on-profile: cluster
collector:
dispatch:
entrance:
netty:
enabled: true
manager-ip: ${MANAGER_IP:127.0.0.1}
manager-port: ${MANAGER_PORT:1158}
common:
queue:
# memory or kafka
type: ${QUEUE_TYPE:netty}
# properties when queue type is kafka
kafka:
servers: ${KAFKA_SERVERS:127.0.0.1:9092}
metrics-data-topic: ${KAFKA_TOPIC:async-metrics-data}
_ _ _ ____ _
| | | | ___ _ __| |_ ___| __ ) ___ __ _| |_
| |_| |/ _ \ '__| __|_ / _ \ / _ \/ _` | __| Profile: ${spring.profiles.active}
| _ | __/ | | |_ / /| |_) | __/ (_| | |_ Name: ${spring.application.name} Port: ${server.port} Pid: ${pid}
|_| |_|\___|_| \__/___|____/ \___|\__,_|\__|
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
<springProperty scope="context" name="application_name" source="spring.application.name" defaultValue="collector"/>
<!-- 输出日志到控制台 ConsoleAppender -->
<appender name="ConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--<pattern>%d %p (%file:%line\)- %m%n</pattern>-->
<!--格式化输出:%d:表示日期 %thread:表示线程名 %-5level:级别从左显示5个字符宽度 %msg:日志消息 %n:是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="SystemOutFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 归档的日志文件的路径。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
<fileNamePattern>logs/${application_name}-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
<!--日志保留时长-->
<maxHistory>7</maxHistory>
<!--日志保留最大值-->
<totalSizeCap>10GB</totalSizeCap>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<!-- 除按日志记录之外,还配置了日志文件不能超过200M,若超过200M,日志文件会以索引0开始 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>200MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 追加方式记录日志 -->
<append>true</append>
<!-- 日志文件的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger Line:%-3L - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<appender name="ErrOutFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/${application_name}-%d{yyyy-MM-dd}-error.%i.log.zip</fileNamePattern>
<!--日志保留时长-->
<maxHistory>7</maxHistory>
<!--日志保留最大值-->
<totalSizeCap>10GB</totalSizeCap>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>200MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 追加方式记录日志 -->
<append>true</append>
<!-- 日志文件的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger Line:%-3L - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!-- 此日志文件记录error及以上级别的 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<!--这个logger的设置是:举例在org.springframework包下面的所有输出日志必须级别level在info及以上级别才会被输出!-->
<!--这样可以避免输出一些spring框架的许多常见debug信息!-->
<logger name="org.springframework" level="info" />
<logger name="org.json" level="error"/>
<logger name="io.netty" level="info"/>
<logger name="org.slf4j" level="info"/>
<logger name="ch.qos.logback" level="info"/>
<logger name="org.apache.kafka.clients" level="info"/>
<logger name="org.hibernate" level="info"/>
<logger name="org.apache.http" level="info"/>
<logger name="com.zaxxer" level="info"/>
<logger name="springfox" level="info"/>
<logger name="org.mongodb" level="warn"/>
<logger name="io.greptime" level="warn"/>
<!-- 生产环境配置 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
</root>
</springProfile>
<springProfile name="cluster">
<root level="INFO">
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
</root>
</springProfile>
<!-- 开发环境配置 -->
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
</root>
</springProfile>
<!-- 开发环境配置 -->
<springProfile name="mysql">
<root level="INFO">
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
</root>
</springProfile>
</configuration>
package org.dromara.hertzbeat.collector.util;
import org.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.SecureRandom;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* Test case for {@link KeyPairUtil}
*/
class KeyPairUtilTest {
@BeforeEach
void setUp() {
}
@Test
void getKeyPairFromPublicKey() {
// test null key
KeyPair nullKey = KeyPairUtil.getKeyPairFromPrivateKey(null);
assertNull(nullKey);
// test empty key
KeyPair emptyKey = KeyPairUtil.getKeyPairFromPrivateKey("");
assertNull(emptyKey);
// test illegal key: DSA
String dsaPublicKey = new String(Base64.encodeBase64(genPublicKey("DSA").getEncoded()));
KeyPair illegalKey = KeyPairUtil.getKeyPairFromPrivateKey(dsaPublicKey);
assertNotNull(illegalKey);
// test illegal key: DiffieHellman
String diffieHellmanPublicKey = new String(Base64.encodeBase64(genPublicKey("DiffieHellman").getEncoded()));
illegalKey = KeyPairUtil.getKeyPairFromPrivateKey(diffieHellmanPublicKey);
assertNotNull(illegalKey);
// test not encrypted
byte[] rsaBytes = genPublicKey("RSA").getEncoded();
String rawRsaPublicKey = new String(rsaBytes);
KeyPair raw = KeyPairUtil.getKeyPairFromPrivateKey(rawRsaPublicKey);
assertNotNull(raw);
// test normal case
// base64 encrypted
String rsaPublicKey = new String(Base64.encodeBase64(rsaBytes));
KeyPair normal = KeyPairUtil.getKeyPairFromPrivateKey(rsaPublicKey);
assertNotNull(normal);
assertNotNull(normal.getPublic());
}
private PublicKey genPublicKey(String key) {
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance(key);
} catch (Exception e) {
// nothing to do
// this case shouldn't happen
throw new RuntimeException(e);
}
// init
keyPairGen.initialize(1024, new SecureRandom());
// generate key
KeyPair keyPair = keyPairGen.generateKeyPair();
// get public key
return keyPair.getPublic();
}
}
\ No newline at end of file
/*
* 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.dromara.hertzbeat.collector.util;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.IOException;
/**
* @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>
* Created by gcdd1993 on 2023/7/9
*/
class PrivateKeyUtilsTest {
@DisplayName("write key to ~/.ssh")
@Test
void writePrivateKey() throws IOException {
var key = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIEogIBAAKCAQEA4ctFYk/xy89L6/6YFeeMrwCW9lCP/ThXMn+9G63s5bGn4oIN\n" +
"8cEf/JYkmGw8vMP41IAP9dyH8ji2wIZSLeTPWucEK6P6jA01iIBQ95ng6RTsnQgL\n" +
"h4pYHxlEaNHcXkjy5GlMdzaWadjdRevpThGR1VOtWFtK3yoC0c/te2Junu04f+11\n" +
"cpk8QvmVfzrBUooVnG0/7oekwUy1c5sSl0qVoLzXOv4XG9w34cyvacFC30zv1Nl8\n" +
"ASi2pmOBVx9njPvqQ7qZrDk0nwn+RZUmGh/PbmHxrBV7ZA5NjZcEnf2VGIfjGUVu\n" +
"qE4VnkbvS4j03afV2rsp1yo74K+k/ZC6GCHB5QIBIwKCAQBG9r4I9I3SVxfcdJYy\n" +
"xR2WFiDREgFeNkdKYqkl9NVsws5dIY9am8g5cQQv54DNnK1KGZ6dulaclXtD0nGZ\n" +
"ZSs505OYr+EHcd2f7dBN0Uavp32QcD4jSLycD0FixZ0HsIbaEnceJxlUd1t8YBYf\n" +
"2aLcpUUbxOulORbUOgjPAa286uDeQYN5IbdruDfvbuFFm7hBoGZoKLJ7FPcJ0U3A\n" +
"14KRK+Z1oCYJIS0ubaHbhaPIVPPQEmTNHpsvxIJXfZtVy9+XIuBGmD3+Aq6SSFPC\n" +
"A8mU1iKzzdRCXZwvPeUiivIIZc6DRXjhtJ2Lya/XndKidOT/QUj8Z+f9pWAonlzM\n" +
"3PMXAoGBAPvzctkkDjUJjLyEuYQq8soYokS4n4ykFTP5oFgnodK/cYocbxTT6Tn9\n" +
"vH7b6lK6ZAf+tZk8rcEeIO650pOvmaa1/OuZSxfcFUGBvOvYXiHF7zmkePh/pQgB\n" +
"7Cl0RYrI52Cjbd9aCUIYK3A82qsUq30INGeOhMNrfaHn2pgx8xlDAoGBAOVsNctw\n" +
"CHnLaIQX8eS+eUcQEm+NZppnDBJavdpP48ZZM/t5v/2fQ5ytbYqk0KEzIGu0dP8g\n" +
"jfB76JbMvStvTfB+TrXsfhGyA3oJrEcG+3IUshsRU2sohT1ScY27z2VMLgilnWvF\n" +
"7t49sQm9uB/yn669n8LIciHxDItOpvqgKdG3AoGBAO2NxA6PtZ+4jAIz/19bsbc7\n" +
"zDIqaovrKe8tMMglXg/ZE0e0aLvdvqRkRAKU1Z51Ob5lLuDwEYoyWZCgk1gL90Vp\n" +
"wpT+P3zlcyCBo39IWMDB8C8IydRbF/GbaaNtoKds92m+qWwwUd87XCf+3M0wvvI6\n" +
"75TW1PLEbyOgFz8Khh8hAoGBAJbDc87Ul9sCAtp2Ip2hvWk2coPR8vfADz9C8cn5\n" +
"/BShBOcVfipSt2b1n8GCP/TnFU4XgBVeiSkA9+4Rg6AzMzejdY1+JvWvfqCnRVM/\n" +
"GkOnMzZb17tyZi+ck8OKC/IcHkAyUYFWL0GWQSOojvBsPQxt+0V8aEIwsHjNSSha\n" +
"nyNpAoGAd0XqdByRxbWgg5ZsvM0tvrpMITpEZsGMG9VeQPGl0wsQvC2zw5QGLvz/\n" +
"57YhofOOr0M3yElcFA9Imvek5CYZsyL8eIWGZyadfRiYvGOUyvDDO3BYRG4DmhyF\n" +
"KVk3URjEuOCC29ORvZ/7HaCO9iuEbvAA/mrAtd7KdCA+3PzfEOw=\n" +
"-----END RSA PRIVATE KEY-----";
PrivateKeyUtils.writePrivateKey("127.0.0.1", key);
}
}
\ No newline at end of file
......@@ -22,10 +22,12 @@ import com.googlecode.aviator.lexer.token.OperatorType;
import com.googlecode.aviator.runtime.function.AbstractFunction;
import com.googlecode.aviator.runtime.type.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import java.util.regex.Pattern;
/**
* @author tomsun28
......@@ -43,6 +45,8 @@ public class AviatorConfiguration {
AviatorEvaluator.getInstance()
.useLRUExpressionCache(AVIATOR_LRU_CACHE_SIZE)
.addFunction(new StrEqualFunction());
// 配置自定义aviator函数
AviatorEvaluator.getInstance().addOpFunction(OperatorType.BIT_OR, new AbstractFunction() {
@Override
public AviatorObject call(final Map<String, Object> env, final AviatorObject arg1,
......@@ -66,6 +70,10 @@ public class AviatorConfiguration {
return OperatorType.BIT_OR.getToken();
}
});
AviatorEvaluator.getInstance().addFunction(new StrContainsFunction());
AviatorEvaluator.getInstance().addFunction(new StrExistsFunction());
AviatorEvaluator.getInstance().addFunction(new StrMatchesFunction());
}
/**
......@@ -75,12 +83,12 @@ public class AviatorConfiguration {
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
if (arg1 == null || arg2 == null) {
return AviatorBoolean.valueOf(false);
return AviatorBoolean.FALSE;
}
Object leftTmp = arg1.getValue(env);
Object rightTmp = arg2.getValue(env);
if (leftTmp == null || rightTmp == null) {
return AviatorBoolean.valueOf(false);
return AviatorBoolean.FALSE;
}
String left = String.valueOf(leftTmp);
String right = String.valueOf(rightTmp);
......@@ -91,4 +99,76 @@ public class AviatorConfiguration {
return "equals";
}
}
/**
* 自定义aviator判断字符串1是否包含字符串2 (case-insensitive)
*/
private static class StrContainsFunction extends AbstractFunction {
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
if (arg1 == null || arg2 == null) {
return AviatorBoolean.FALSE;
}
Object leftTmp = arg1.getValue(env);
Object rightTmp = arg2.getValue(env);
if (leftTmp == null || rightTmp == null) {
return AviatorBoolean.FALSE;
}
String left = String.valueOf(leftTmp);
String right = String.valueOf(rightTmp);
return AviatorBoolean.valueOf(StringUtils.containsIgnoreCase(left, right));
}
@Override
public String getName() {
return "contains";
}
}
/**
* 自定义aviator判断环境中是否存在字符串
*/
private static class StrExistsFunction extends AbstractFunction {
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg) {
if (arg == null) {
return AviatorBoolean.FALSE;
}
Object keyTmp = arg.getValue(env);
if (keyTmp == null) {
return AviatorBoolean.FALSE;
}
String key = String.valueOf(keyTmp);
return AviatorBoolean.valueOf(env.containsKey(key));
}
@Override
public String getName() {
return "exists";
}
}
/**
* 自定义aviator判断字符串是否匹配regex
* - regex需要加上""或者''
*/
private static class StrMatchesFunction extends AbstractFunction {
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
if (arg1 == null || arg2 == null) {
return AviatorBoolean.FALSE;
}
Object strTmp = arg1.getValue(env);
Object regexTmp = arg2.getValue(env);
if (strTmp == null || regexTmp == null) {
return AviatorBoolean.FALSE;
}
String str = String.valueOf(strTmp);
String regex = String.valueOf(regexTmp);
boolean isMatch = Pattern.compile(regex).matcher(str).matches();
return AviatorBoolean.valueOf(isMatch);
}
@Override
public String getName() {
return "matches";
}
}
}
......@@ -95,6 +95,8 @@ public class CommonProperties {
Memory,
/** kafka **/
Kafka,
/** with netty connect **/
Netty,
/** rabbit mq **/
Rabbit_Mq
}
......
......@@ -19,10 +19,7 @@ package org.dromara.hertzbeat.common.constants;
/**
* Public Constant
* 公共常量
*
* @author tomsun28
*
*/
public interface CommonConstants {
......@@ -279,4 +276,24 @@ public interface CommonConstants {
* cache key alert converge
*/
String CACHE_ALERT_CONVERGE = "alert_converge";
/**
* collector status online 0
*/
byte COLLECTOR_STATUS_ONLINE = 0;
/**
* collector status offline 1
*/
byte COLLECTOR_STATUS_OFFLINE = 1;
/**
* default main collector name
*/
String MAIN_COLLECTOR_NODE = "main-default-collector";
/**
* locale spilt
*/
String LOCALE_SEPARATOR = "_";
}
package org.dromara.hertzbeat.common.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
* collector info
* @author tom
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "collector info")
public class CollectorInfo {
@NotNull
private String name;
@NotNull
private String ip;
// todo more
}
package org.dromara.hertzbeat.common.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.hertzbeat.common.entity.manager.Collector;
/**
* collector summary
* @author tom
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "collector summary")
public class CollectorSummary {
@Schema(description = "the collector info")
private Collector collector;
@Schema(description = "the number of monitors pinned in this collector")
private int pinMonitorNum;
@Schema(description = "the number of monitors dispatched in this collector")
private int dispatchMonitorNum;
}
......@@ -61,7 +61,7 @@ public class JdbcProtocol {
*/
private String platform;
/**
* SQL查询方式: oneRow, multiRow, columns
* SQL查询方式: oneRow, multiRow, columns, runScript
*/
private String queryType;
/**
......
......@@ -63,6 +63,11 @@ public class SshProtocol {
* 私钥(可选)
*/
private String privateKey;
/**
* reuse connection session
*/
private String reuseConnection = "true";
/**
* SSH执行脚本
......
package org.dromara.hertzbeat.common.entity.manager;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* collector entity
* @author tom
*/
@Entity
@Table(name = "hzb_collector", uniqueConstraints = @UniqueConstraint(columnNames = {"name"}))
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "slave collector entity | 注册采集器实体")
@EntityListeners(AuditingEntityListener.class)
public class Collector {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(title = "primary id", example = "2")
private Long id;
@Schema(title = "collector identity name", description = "collector identity name")
@NotNull
private String name;
@Schema(title = "collector ip", description = "collector remote ip")
@NotNull
private String ip;
@Schema(title = "collector status: 0-online 1-offline")
@Min(0)
private byte status;
@Schema(title = "The creator of this record", description = "此条记录创建者", example = "tom")
@CreatedBy
private String creator;
@Schema(title = "This record was last modified by", description = "此条记录最新修改者")
@LastModifiedBy
private String modifier;
@Schema(title = "This record creation time (millisecond timestamp)", description = "记录创建时间")
@CreatedDate
private LocalDateTime gmtCreate;
@Schema(title = "Record the latest modification time (timestamp in milliseconds)", description = "记录最新修改时间")
@LastModifiedDate
private LocalDateTime gmtUpdate;
}
package org.dromara.hertzbeat.common.entity.manager;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import java.time.LocalDateTime;
/**
* collector entity
* @author tom
*/
@Entity
@Table(name = "hzb_collector_monitor_bind", indexes = {
@Index(name = "index_collector_monitor", columnList = "collector"),
@Index(name = "index_collector_monitor", columnList = "monitor_id")
})
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "slave collector monitor bind entity | 采集器与监控关联实体")
@EntityListeners(AuditingEntityListener.class)
public class CollectorMonitorBind {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(title = "primary id", example = "23")
private Long id;
@Schema(title = "collector name", example = "87432674384")
private String collector;
@Schema(title = "monitor ID", example = "87432674336")
@Column(name = "monitor_id")
private Long monitorId;
@Schema(title = "The creator of this record", description = "此条记录创建者", example = "tom")
@CreatedBy
private String creator;
@Schema(title = "This record was last modified by", description = "此条记录最新修改者")
@LastModifiedBy
private String modifier;
@Schema(title = "This record creation time (millisecond timestamp)", description = "记录创建时间")
@CreatedDate
private LocalDateTime gmtCreate;
@Schema(title = "Record the latest modification time (timestamp in milliseconds)", description = "记录最新修改时间")
@LastModifiedDate
private LocalDateTime gmtUpdate;
}
......@@ -188,6 +188,13 @@ public class NoticeReceiver {
@Column(length = 300)
private String smnTopicUrn;
@Schema(title = "serverChanToken : The notification method is valid for ServerChan",
description = "访问token : 通知方式为Server酱有效",
example = "SCT193569TSNm6xIabdjqeZPtOGOWcvU1e", accessMode = READ_WRITE)
@Length(max = 300)
@Column(length = 300)
private String serverChanToken;
@Schema(title = "The creator of this record", description = "此条记录创建者", example = "tom",
accessMode = READ_ONLY)
@CreatedBy
......
package org.dromara.hertzbeat.common.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.HashMap;
import java.util.Map;
/**
* msa 微服务指标模型
* @author myth
*
*/
@Setter
@Getter
@ToString
public class ServicePodModel {
/**
* pod名称
*/
private String podName;
/**
* pod host地址
*/
private String podHost;
/**
* pod 端口
*/
private String podPort;
/**
* pod 状态
*/
private String status;
/**
* 指标采集时间
*/
private String timestamp;
/**
* 采集的指标信息
* key: 指标名称 或者 指标唯一标识
* value: 采集到指标的具体信息
*/
private Map<String, Object> metricsMap = new HashMap<>();
}
/*
* 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.dromara.hertzbeat.common.support;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* common task worker thread pool
* @author tom
*/
@Component
@Slf4j
public class CommonThreadPool implements DisposableBean {
private ThreadPoolExecutor workerExecutor;
public CommonThreadPool() {
initWorkExecutor();
}
private void initWorkExecutor() {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((thread, throwable) -> {
log.error("common executor has uncaughtException.");
log.error(throwable.getMessage(), throwable);
})
.setDaemon(true)
.setNameFormat("common-worker-%d")
.build();
workerExecutor = new ThreadPoolExecutor(2,
Integer.MAX_VALUE,
10,
TimeUnit.SECONDS,
new SynchronousQueue<>(),
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
}
/**
* Run the task thread
* @param runnable Task
* @throws RejectedExecutionException when thread pool full
*/
public void execute(Runnable runnable) throws RejectedExecutionException {
workerExecutor.execute(runnable);
}
@Override
public void destroy() throws Exception {
if (workerExecutor != null) {
workerExecutor.shutdownNow();
}
}
}
......@@ -31,8 +31,8 @@ import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
/**
* i18n resource bundle control
* @author tom
*
*/
public class ResourceBundleUtf8Control extends ResourceBundle.Control {
......
package org.dromara.hertzbeat.common.support.event;
import org.springframework.context.ApplicationEvent;
/**
* the event for system config change
* @author tom
*/
public class SystemConfigChangeEvent extends ApplicationEvent {
public SystemConfigChangeEvent(Object source) {
super(source);
}
}
......@@ -24,6 +24,7 @@ import org.dromara.hertzbeat.common.constants.CollectorConstants;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.regex.Pattern;
......@@ -110,7 +111,7 @@ public class IpDomainUtil {
/**
*
* @param ipDomain
* @param ipDomain ip domain
* @return IP address type
*/
public static String checkIpAddressType(String ipDomain){
......@@ -119,5 +120,18 @@ public class IpDomainUtil {
}
return CollectorConstants.IPV4;
}
/**
* get current local host name
* @return hostname
*/
public static String getCurrentHostName() {
try {
InetAddress inetAddress = InetAddress.getLocalHost();
return inetAddress.getHostName();
} catch (UnknownHostException e) {
return null;
}
}
}
......@@ -27,7 +27,7 @@ import java.util.Map;
*/
public class LruHashMap<K, V> extends LinkedHashMap<K, V> {
private int threshold;
private final int threshold;
public LruHashMap(int threshold) {
super(16, 0.75f, true);
......
......@@ -2,8 +2,7 @@ package org.dromara.hertzbeat.common.util;
/**
* map initial capacity calculation
* @author: hdd
* @create: 2023/03/04
* @author hdd
*/
public class MapCapUtil {
......@@ -12,8 +11,8 @@ public class MapCapUtil {
/**
* Prevent expansion
* @param size
* @return
* @param size size
* @return capacity
*/
public static int calInitMap(int size) {
return (int) Math.ceil (size / LOAD_FACTOR);
......
package org.dromara.hertzbeat.common.util;
/**
* @author ceilzcx
* @since 19/7/2023
*/
public class NetworkUtil {
public static final String OS_NAME = System.getProperty("os.name");
private static final String LINUX = "linux";
private static final String WINDOWS = "windows";
private static boolean isLinuxPlatform = false;
private static boolean isWindowsPlatform = false;
static {
if (OS_NAME != null && OS_NAME.toLowerCase().contains(LINUX)) {
isLinuxPlatform = true;
}
if (OS_NAME != null && OS_NAME.toLowerCase().contains(WINDOWS)) {
isWindowsPlatform = true;
}
}
/**
* whether the running environment is linux
* @return is linux platform or not
*/
public static boolean isLinuxPlatform() {
return isLinuxPlatform;
}
/**
* whether the running environment is windows
* @return is windows platform or not
*/
public static boolean isWindowsPlatform() {
return isWindowsPlatform;
}
}
/*
* 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.
*/
syntax = "proto3";
package org.dromara.hertzbeat.common.entity.message;
message Message
{
// collector identity
string identity = 1;
// message type
MessageType type = 2;
// message content
string msg = 3;
}
enum MessageType
{
// heartbeat message
HEARTBEAT = 0;
// collector go online to master message
GO_ONLINE = 1;
// collector go offline to master message
GO_OFFLINE = 2;
// issue cyclic collect task
ISSUE_CYCLIC_TASK = 3;
// delete cyclic collect task
DELETE_CYCLIC_TASK = 4;
// issue one-time collect task
ISSUE_ONE_TIME_TASK = 5;
// response one-time collect data
RESPONSE_ONE_TIME_TASK_DATA = 6;
// response cyclic collect data
RESPONSE_CYCLIC_TASK_DATA = 7;
}
package org.dromara.hertzbeat.common.config;
import com.googlecode.aviator.AviatorEvaluator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
/**
* @author mikezzb
*
*/
class AviatorConfigurationTest {
@BeforeAll
static void setUp() {
AviatorConfiguration aviatorConfig = new AviatorConfiguration();
aviatorConfig.configAviatorEvaluator();
}
@Test
void testCustomStringFunctions() {
Map<String, Object> env = new HashMap<>();
env.put("k1", "Intel");
env.put("k2", "intel");
env.put("k3", "Ubuntu 18.04.6 LTS");
env.put("k4", "ubuntu");
env.put("k5", "Ubntu");
env.put("k6", null);
// test StrEqualFunction
String expr1 = "equals(k1,k2)"; // case-insensitive
Boolean res1 = (Boolean) AviatorEvaluator.compile(expr1).execute(env);
Assertions.assertTrue(res1);
String expr2 = "equals(k1,k3)";
Boolean res2 = (Boolean) AviatorEvaluator.compile(expr2).execute(env);
Assertions.assertFalse(res2);
// test StrContainsFunction
String expr3 = "contains(k3,k4)"; // case-insensitive
Boolean res3 = (Boolean) AviatorEvaluator.compile(expr3).execute(env);
Assertions.assertTrue(res3);
String expr4 = "contains(k4,k3)";
Boolean res4 = (Boolean) AviatorEvaluator.compile(expr4).execute(env);
Assertions.assertFalse(res4);
String expr5 = "contains(k3,k5)"; // subsequence
Boolean res5 = (Boolean) AviatorEvaluator.compile(expr5).execute(env);
Assertions.assertFalse(res5);
// test StrExistsFunction
String expr6 = "exists('DNE_Key1')";
Boolean res6 = (Boolean) AviatorEvaluator.compile(expr6).execute(env);
Assertions.assertFalse(res6);
String expr7 = "exists('k6')";
Boolean res7 = (Boolean) AviatorEvaluator.compile(expr7).execute(env);
Assertions.assertTrue(res7);
// test StrMatchesFunction
String regex1 = "'^[a-zA-Z0-9]+$'"; // only alphanumeric
String expr8 = "matches(k6," + regex1 + ")";
env.put("k6", "Ubntu50681269");
Boolean res8 = (Boolean) AviatorEvaluator.compile(expr8).execute(env);
Assertions.assertTrue(res8);
env.put("k6", "Ubnt_u50681269");
Boolean res9 = (Boolean) AviatorEvaluator.compile(expr8).execute(env);
Assertions.assertFalse(res9);
String regex2 = "'^Ubuntu.*'"; // starts with
String expr9 = "matches(k3," + regex2 + ")";
Boolean res10 = (Boolean) AviatorEvaluator.compile(expr9).execute(env);
Assertions.assertTrue(res10);
env.put("k3", "Ubunt_u50681269");
Boolean res11 = (Boolean) AviatorEvaluator.compile(expr9).execute(env);
Assertions.assertFalse(res11);
String regex3 = "\"^\\\\[LOG\\\\].*error$\""; // starts & ends with
String expr10 = "matches(k7," + regex3 + ")";
env.put("k7", "[LOG] detected system error");
Boolean res12 = (Boolean) AviatorEvaluator.compile(expr10).execute(env);
Assertions.assertTrue(res12);
env.put("k7", "[LOG detected system error");
Boolean res13 = (Boolean) AviatorEvaluator.compile(expr10).execute(env);
Assertions.assertFalse(res13);
}
}
\ No newline at end of file
......@@ -9,6 +9,14 @@ Learn From [webdriverio](https://webdriver.io/)
yarn install
```
## I18N
```console
yarn write-translations --locale zh-cn
yarn write-translations --locale en
```
## Local Development
```console
......
......@@ -17,13 +17,16 @@ Website: hertzbeat.com | tancloud.cn
### What is HertzBeat?
> HertzBeat is an open source real-time monitoring and alerting tool with powerful custom monitoring capabilities and no Agent required.
> It supports monitoring of application services, database, operating system, middleware, cloud native, network and other metrics, and threshold alert notification in one step.
> Support more liberal threshold rules (calculation expressions), `email` `Discord` `Slack` `Telegram` `Pegging` `WeChat` `FlyBook` `SMS` `Webhook` and other ways to timely delivery.
> [HertzBeat](https://github.com/dromara/hertzbeat) is an open source, real-time monitoring system with custom-monitoring and agentLess.
> **Monitoring+Alarm+Notify** all in one. Support monitoring web service, database, os, middleware, cloud-native, network and more.
> Easy to use, full web-based operation, monitoring and alerting at the click of a mouse, zero learning cost.
> More flexible threshold rule, timely notification delivery by `Discord` `Slack` `Telegram` `Email` `DingDing` `WeChat` `FeiShu` `Webhook` `SMS`.
> We have made the protocol specifications such as `Http,Jmx,Ssh,Snmp,Jdbc` configurable so that you can simply configure `YML` to use these protocols to customize the collection of any metrics you want.
> We make protocols such as `Http, Jmx, Ssh, Snmp, Jdbc` configurable, and you only need to configure `YML` online to collect any metrics you want.
> Do you believe that you can immediately adapt a new monitoring type such as K8s or Docker just by configuring online?
> Do you believe that you can immediately adapt a new monitoring type such as K8s or Docker just by defining YML?
> `HertzBeat`'s powerful custom-define, multi-type support, easy expansion, low coupling, hope to help developers and micro teams to quickly build their own monitoring system.
> We also provide **[Monitoring SaaS Cloud](https://console.tancloud.cn)**, users no longer need to deploy a cumbersome monitoring system in order to monitor resources. **[Get started for free](https://console.tancloud.cn)**.
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4236e748f5ac4352b7cf4bb65ccf97aa~tplv-k3u1fbpfcp-zoom-1.image)
......
---
title: Open source monitoring HertzBeat v1.3.2 released, Easier to use
author: tom
author_title: tom
author_url: https://github.com/tomsun28
author_image_url: https://avatars.githubusercontent.com/u/24788200?s=400&v=4
tags: [opensource, practice]
keywords: [open source monitoring system, alerting system, Linux monitoring]
---
Website: hertzbeat.com | tancloud.cn
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a9629ef5bb6e486cacddb899f1495c6e~tplv-k3u1fbpfcp-zoom-1.image)
### What is HertzBeat?
> [HertzBeat](https://github.com/dromara/hertzbeat) is an open source, real-time monitoring system with custom-monitoring and agentLess.
> **Monitoring+Alarm+Notify** all in one. Support monitoring web service, database, os, middleware, cloud-native, network and more.
> Easy to use, full web-based operation, monitoring and alerting at the click of a mouse, zero learning cost.
> More flexible threshold rule, timely notification delivery by `Discord` `Slack` `Telegram` `Email` `DingDing` `WeChat` `FeiShu` `Webhook` `SMS`.
> We make protocols such as `Http, Jmx, Ssh, Snmp, Jdbc` configurable, and you only need to configure `YML` online to collect any metrics you want.
> Do you believe that you can immediately adapt a new monitoring type such as K8s or Docker just by configuring online?
> `HertzBeat`'s powerful custom-define, multi-type support, easy expansion, low coupling, hope to help developers and micro teams to quickly build their own monitoring system.
> We also provide **[Monitoring SaaS Cloud](https://console.tancloud.cn)**, users no longer need to deploy a cumbersome monitoring system in order to monitor resources. **[Get started for free](https://console.tancloud.cn)**.
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4236e748f5ac4352b7cf4bb65ccf97aa~tplv-k3u1fbpfcp-zoom-1.image)
**Github: https://github.com/dromara/hertzbeat**
**Gitee: https://gitee.com/dromara/hertzbeat**
### v1.3.2
Hi guys! Major release. HertzBeat v1.3.2 has published.
**This delightful version came out of the hard work of 27 friends. Thank them! Love 💗**
In this version, we support new monitoring types and indicators for **freebsd, debian, opensuse, redhat, apache doris**, etc.
- Support WEB page configuration mail server, replace the previous file configuration
- Supports alarm convergence. If repeated alarms are frequently sent, it will be resolved immediately with alarm convergence
- The public message queue supports Kafka. In addition to our default built-in memory message queue, it also supports an external Kafka message queue to improve system performance.
- The new monitoring center page aggregates all monitoring types, no need to switch back and forth like before.
- Support label group display, group and mark monitors of the same business category for unified management.
- Threshold configuration not only has expressions, but also supports a more user-friendly operation UI. The previous expressions were not friendly to newcomers and were prone to errors. Now you can directly operate the UI, and it can switch between expressions.
- There are many more functions such as HTTP ipv6.
Fixed a large number of BUG, improved the document code, and improved the overall stability and usability. More new features are welcome to explore!
Let's Try Now!
Only one docker command is needed to install and experience hertzbeat:
`docker run -d -p 1157:1157 --name hertzbeat tancloud/hertzbeat`
```or use quay.io (if dockerhub network connect timeout)```
```docker run -d -p 1157:1157 --name hertzbeat quay.io/tancloud/hertzbeat```
---
Upgrade Note⚠️.
For h2 database users, sholud exec sql below:
```sql
ALTER TABLE HZB_PARAM DROP CONSTRAINT CONSTRAINT_82;;
```
How to Enable H2 WEB Console:
Modify `application.yml` and restart, access `ip:1157/h2-console`
```
spring:
h2:
console:
path: /h2-console
enabled: true
```
----
## ⛄ Supported
- Site Monitor, Port Availability, Http Api, Ping Connectivity, Jvm, SiteMap Full Site, Ssl Certificate, SpringBoot, FTP Server
- Mysql, PostgreSQL, MariaDB, Redis, ElasticSearch, SqlServer, Oracle, MongoDB, Damon, OpenGauss, ClickHouse, IoTDB, Redis Cluster
- Linux, Ubuntu, CentOS, Windows
- Tomcat, Nacos, Zookeeper, RabbitMQ, Flink, Kafka, ShenYu, DynamicTp, Jetty, ActiveMQ
- Kubernetes, Docker
- Huawei Switch, HPE Switch, TP-LINK Switch, Cisco Switch
- and more for your custom monitoring.
- Notifications support `Discord` `Slack` `Telegram` `Mail` `Pinning` `WeChat` `FlyBook` `SMS` `Webhook`.
----
**Github: https://github.com/dromara/hertzbeat**
**Gitee: https://gitee.com/dromara/hertzbeat**
---
id: extend-http-default
title: HTTP Protocol System Default Parsing Method
sidebar_label: System Default Parsing Method
sidebar_label: Default Parsing Method
---
> After calling the HTTP interface to obtain the response data, use the default parsing method of hertzbeat to parse the response data.
> After calling the HTTP api to obtain the response data, use the default parsing method of hertzbeat to parse the response data.
**The interface response data structure must be consistent with the data structure rules specified by hertzbeat**
......@@ -76,7 +76,7 @@ If there are multiple virtual machines, the multilayer format is: :
]
```
**The corresponding monitoring configuration definition file YML can be configured as follows**
**The corresponding monitoring template yml can be configured as follows**
```yaml
# The monitoring type category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
......@@ -15,6 +15,8 @@ Here is the architecture.
**We define all monitoring collection types (mysql, website, jvm, k8s) as yml templates, and users can import these templates into the hertzbeat system to support corresponding types of monitoring, which is very convenient!**
![](/img/docs/advanced/extend-point-1.png)
**Welcome everyone to contribute your customized general monitoring type YML template during use. The available templates are as follows:**
### Application service monitoring
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。