Compare commits

...
78 Commits
Author SHA1 Message Date
Jast aeee629c5f Merge branch 'master' into traces 2025-05-09 13:16:04 +08:00
tomsun28andshown d0b7e38537 [bugfix] fix the calculator expr exist not work (#3314)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: shown <yuluo08290126@gmail.com>
2025-05-09 13:15:46 +08:00
Logic cc4b63f9ad Merge branch 'master' into traces 2025-05-07 22:52:23 +08:00
096153b3a4 [license] update the license file, to add the plc4j-driver-s7 license (#3318)
Co-authored-by: cuijianzhong <shangyuancjz@126.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-05-07 22:00:05 +08:00
Logic 93a22c3b22 style(hertzbeat-otel): format OpenTelemetryConfig and OpenTelemetryLogbackAppenderInstaller
- Update code formatting in OpenTelemetryConfig.java and OpenTelemetryLogbackAppenderInstaller.java
- Adjust indentation and line breaks for better readability
- Remove unnecessary spaces in method implementations
2025-05-07 18:17:45 +08:00
Logic 8954775632 Merge branch 'master' into traces 2025-05-07 17:30:16 +08:00
Logic 59781ad673 feat(otel): integrate OpenTelemetry for GreptimeDB logs and traces- Rename hertzbeat-log to hertzbeat-otel
- Update dependencies to use OpenTelemetry instrumentation
- Refactor OpenTelemetry configuration for GreptimeDB integration
- Add OpenTelemetryLogbackAppenderInstaller for Logback integration
- Update Maven pom files to reflect new dependencies and structure
2025-05-07 17:26:19 +08:00
zhangyaxi b313db36c6 [feature] Support Zabbix alert source (#3317)
Signed-off-by: zhangyaxi <yaxi.zhang950408@outlook.com>
2025-05-07 08:54:23 +08:00
Logic ce61c7d456 Merge branch 'master' of github.com:apache/hertzbeat 2025-05-06 10:47:33 +08:00
tomsun28andCopilot 47298033dc [refactor] set default encoding charset utf8 (#3315)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-05 23:30:30 +08:00
tomsun28 f05e91247a [refactor] refactor auto discovery http sd (#3300)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-05-04 00:07:06 +08:00
zhangyaxiandtomsun28 1592c93d25 [feature] Support Uptime Kuma alert source (#3312)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-05-01 14:31:52 +08:00
Yang Chenandtomsun28 46d609a95e [docs] add help documentation for alert integration (#3308)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-30 16:05:59 +08:00
Yang Chen f17fb02441 [docs] Correct Markdown formatting and update lint config to enable local checking (#3310) 2025-04-30 11:08:38 +08:00
e6cc71ed56 [Improve] Add unit test for PeriodicAlertCalculator (#3304)
Co-authored-by: yinyijun <yinyijun6@mgtv.com>
Co-authored-by: yinyijun <yingey2011>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-28 17:46:14 +08:00
Cyanty e2a9160e59 [refactor] Remove the possible security in action and ignore the 500 code for link check (#3303) 2025-04-28 11:30:14 +08:00
7ebeee2c1f [docs] Add Chinese and English versions of the Hertzbeat upgrade guide (#3294)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: shown <yuluo08290126@gmail.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-04-27 00:26:40 +08:00
Cyantyandtomsun28 ccfc8dc8ce [improve] Optimize the way of Dead Link Check (#3302)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-26 23:04:49 +08:00
Yang Chenandaias00 ca9db17241 [bugfix] enables support for VictoriaMetrics in cluster mode within HertzBeat (#3297)
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-04-26 22:29:47 +08:00
Cyanty 7a40bdee12 [Doc] Add English doc for springboot auto practice usecase (#3298) 2025-04-26 20:40:16 +08:00
tomsun28 5db20d33f4 [bugfix] fix collector direct oom (#3295) 2025-04-24 20:22:50 +08:00
Cyanty 896869c611 [Doc] Add springboot auto practice usecase (#3293)
Signed-off-by: Cyanty <153884653+Cyanty@users.noreply.github.com>
2025-04-24 12:51:04 +08:00
tomsun28 32ce88b2c0 [webapp] try reduce memory growth and fix crash (#3292)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-04-23 19:47:24 +08:00
2288d8e900 [e2e] add e2e test for k8s monitor (#3280)
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: Jast <shenghang@apache.org>
2025-04-22 23:57:01 +08:00
tomsun28andJast dbb81d44ff [doc] update doc, usecase blog and help doc (#3286)
Co-authored-by: Jast <shenghang@apache.org>
2025-04-22 22:33:47 +08:00
227666dd46 [Task] Set local database as default file server provider (#3282)
Co-authored-by: yinyijun <yingey2011>
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: yinyijun <yinyijun6@mgtv.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-22 22:32:04 +08:00
liutianyouandtomsun28 b14bc6569f [bugfix] page title show "Not page name" when filter monitor list by type (#3289)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-22 22:23:54 +08:00
Jastandtomsun28 7c7102da17 [feature] support ai bot (#3285)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-22 22:19:17 +08:00
Jay丿167andaias00 489eac8e11 [improve] Add E2E tests for Redis collector (#3283)
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-04-22 20:33:46 +08:00
900655680a [improve] Add jdbc common collect e2e code (#3273) (#3273)
Signed-off-by: Cyanty <153884653+Cyanty@users.noreply.github.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: Jast <shenghang@apache.org>
Co-authored-by: shown <yuluo08290126@gmail.com>
2025-04-22 10:19:22 +08:00
tomsun28 e87ed449f2 [doc] update contributors wall (#3281)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-04-22 01:17:41 +08:00
Logic 320766a48e Merge branch 'master' of github.com:apache/hertzbeat 2025-04-17 16:57:54 +08:00
Logic efb2ac02df Merge branch 'master' of github.com:apache/hertzbeat 2025-04-07 10:14:36 +08:00
Logic 06c939c810 Merge branch 'master' of github.com:apache/hertzbeat 2025-04-03 15:57:12 +08:00
Logic e56823b240 Merge branch 'master' of github.com:apache/hertzbeat 2025-03-27 10:20:45 +08:00
Logic 3b18d1ddaf Merge branch 'master' of github.com:apache/hertzbeat 2025-03-06 16:21:20 +08:00
Logic d44fe43eb0 Merge branch 'master' of github.com:apache/hertzbeat 2025-01-24 08:12:57 +08:00
Logic a221ddfba2 Merge branch 'master' of github.com:apache/hertzbeat 2025-01-23 15:29:18 +08:00
Logic 8e31a3147b Merge branch 'master' of github.com:apache/hertzbeat 2025-01-10 08:47:21 +08:00
Logic d2f9a56fe7 Merge branch 'master' of github.com:apache/hertzbeat 2025-01-08 10:33:11 +08:00
Logic a0b41dc7bb Merge branch 'master' of github.com:apache/hertzbeat 2024-12-27 08:24:42 +08:00
Logic 3f572a1ddc Merge branch 'master' of github.com:apache/hertzbeat 2024-11-26 11:22:35 +08:00
Logic 767deccee5 Merge branch 'master' of github.com:apache/hertzbeat 2024-10-26 10:42:23 +08:00
Logic 9221e2f6c1 Merge branch 'master' of github.com:apache/hertzbeat 2024-10-22 17:29:48 +08:00
Logic 0a36b3df90 Merge branch 'master' of github.com:apache/hertzbeat 2024-09-24 11:20:58 +08:00
zqr10159 f3202133d8 Merge branch 'master' of github.com:apache/hertzbeat 2024-09-12 15:48:50 +08:00
zqr10159 6ee458c521 Merge branch 'master' of github.com:apache/hertzbeat 2024-09-11 15:04:01 +08:00
zqr10159 cbc1caf4ac Merge branch 'master' of github.com:apache/hertzbeat 2024-09-06 08:55:12 +08:00
zqr10159 86db630fc7 Merge branch 'master' of github.com:apache/hertzbeat 2024-09-04 08:25:56 +08:00
zqr10159 145c4d1d5e Merge branch 'master' of github.com:apache/hertzbeat 2024-08-31 09:05:14 +08:00
zqr10159 0217cbb7f9 Merge branch 'master' of github.com:apache/hertzbeat 2024-08-30 08:54:44 +08:00
zqr10159 9f5df95f29 Merge branch 'master' of github.com:apache/hertzbeat 2024-08-29 11:35:20 +08:00
zqr10159 0f955019bf Merge branch 'master' of github.com:apache/hertzbeat 2024-08-29 08:36:55 +08:00
zqr10159 129b6282d3 Merge branch 'master' of github.com:apache/hertzbeat 2024-08-23 10:28:34 +08:00
zqr10159 36c5d8f518 Merge branch 'master' of github.com:apache/hertzbeat 2024-07-17 14:55:40 +08:00
zqr10159 de8a473e24 Merge branch 'master' of github.com:apache/hertzbeat 2024-07-16 09:09:47 +08:00
zqr10159 ffc4641dc9 Merge branch 'master' of github.com:apache/hertzbeat 2024-07-09 15:45:37 +08:00
zqr10159 d0cb9f8bf3 Merge branch 'master' of github.com:apache/hertzbeat 2024-07-05 11:36:50 +08:00
zqr10159 0d4ff33f19 Merge branch 'master' of github.com:apache/hertzbeat 2024-07-05 11:15:02 +08:00
zqr10159 86f40e5e15 Merge branch 'master' of github.com:apache/hertzbeat 2024-07-03 08:06:25 +08:00
zqr10159 dbbbc045d4 Merge branch 'master' of github.com:apache/hertzbeat 2024-07-02 15:41:38 +08:00
zqr10159 b0fdd5e11d Merge branch 'master' of github.com:apache/hertzbeat 2024-06-18 11:30:05 +08:00
zqr10159 9c5ea20b15 [doc]HertzBeat 1.6.0 Upgrade Guide. 2024-06-11 14:57:35 +08:00
zqr10159 7a74f80906 Merge branch 'master' of github.com:apache/hertzbeat 2024-06-11 14:36:25 +08:00
zqr10159 ee32a890eb Merge branch 'master' of github.com:dromara/hertzbeat 2024-06-01 10:08:28 +08:00
zqr10159 ec1c04194b Merge branch 'master' of github.com:dromara/hertzbeat 2024-05-18 15:52:12 +08:00
zqr10159 55d8dfdede Merge branch 'master' of github.com:dromara/hertzbeat 2024-05-14 08:15:58 +08:00
zqr10159 918073dca7 Merge branch 'master' of github.com:dromara/hertzbeat 2024-05-11 08:45:53 +08:00
zqr10159 2defaecd93 Merge branch 'master' of github.com:dromara/hertzbeat 2024-05-10 08:39:59 +08:00
zqr10159 641ba47f4c Merge branch 'master' of github.com:dromara/hertzbeat 2024-05-09 17:08:47 +08:00
zqr10159 ccbc2151ae Merge branch 'master' of github.com:dromara/hertzbeat 2024-05-07 09:05:40 +08:00
zqr10159 1cb919a301 Merge branch 'master' of github.com:dromara/hertzbeat 2024-04-29 10:47:25 +08:00
zqr10159 7351a03c81 Merge branch 'master' of github.com:dromara/hertzbeat 2024-04-28 11:47:27 +08:00
zqr10159 26c3a802d1 Merge branch 'master' of github.com:dromara/hertzbeat 2024-04-25 09:17:04 +08:00
zqr10159 47dbe2fd2a Merge branch 'master' of github.com:dromara/hertzbeat 2024-04-24 08:13:26 +08:00
zqr10159 f4943e9d89 Merge branch 'master' of github.com:dromara/hertzbeat 2024-04-23 14:53:04 +08:00
zqr10159 1f6614d1a1 [remove]Remove optional comments. 2024-04-23 10:36:58 +08:00
zqr10159 4a96c02b9a [remove]Remove optional comments. 2024-04-23 10:35:21 +08:00
239 changed files with 6411 additions and 2746 deletions
+116 -37
View File
@@ -267,7 +267,9 @@
"contributions": [
"code",
"bug",
"design"
"design",
"test",
"blog"
]
},
{
@@ -361,7 +363,9 @@
"contributions": [
"bug",
"code",
"doc"
"doc",
"test",
"design"
]
},
{
@@ -683,7 +687,8 @@
"contributions": [
"code",
"test",
"doc"
"doc",
"design"
]
},
{
@@ -797,7 +802,11 @@
"profile": "https://github.com/zqr10159",
"contributions": [
"doc",
"code"
"code",
"blog",
"bug",
"test",
"design"
]
},
{
@@ -889,7 +898,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/50475131?v=4",
"profile": "https://github.com/l646505418",
"contributions": [
"code"
"code",
"bug"
]
},
{
@@ -898,7 +908,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/78642589?v=4",
"profile": "http://www.carpewang.com",
"contributions": [
"code"
"code",
"bug"
]
},
{
@@ -1263,7 +1274,11 @@
"avatar_url": "https://avatars.githubusercontent.com/u/105542329?v=4",
"profile": "https://github.com/a-little-fool",
"contributions": [
"code"
"code",
"doc",
"blog",
"bug",
"design"
]
},
{
@@ -1326,7 +1341,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/67301054?v=4",
"profile": "http://www.jimmyqiao.top",
"contributions": [
"code"
"code",
"bug"
]
},
{
@@ -1419,7 +1435,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/28394742?v=4",
"profile": "https://github.com/Clownsw",
"contributions": [
"code"
"code",
"bug"
]
},
{
@@ -1430,7 +1447,9 @@
"contributions": [
"doc",
"code",
"design"
"design",
"bug",
"test"
]
},
{
@@ -1469,7 +1488,8 @@
"contributions": [
"code",
"design",
"doc"
"doc",
"bug"
]
},
{
@@ -1541,7 +1561,9 @@
"avatar_url": "https://avatars.githubusercontent.com/u/42080876?v=4",
"profile": "https://github.com/yqxxgh",
"contributions": [
"doc"
"doc",
"code",
"bug"
]
},
{
@@ -1586,7 +1608,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/55646681?v=4",
"profile": "https://github.com/starmilkxin",
"contributions": [
"code"
"code",
"bug"
]
},
{
@@ -1605,7 +1628,11 @@
"profile": "https://github.com/LiuTianyou",
"contributions": [
"code",
"doc"
"doc",
"bug",
"test",
"blog",
"design"
]
},
{
@@ -1651,9 +1678,11 @@
"avatar_url": "https://avatars.githubusercontent.com/u/71874373?v=4",
"profile": "https://github.com/gjjjj0101",
"contributions": [
"test",
"code",
"doc",
"design",
"test"
"bug"
]
},
{
@@ -1662,7 +1691,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/37864182?v=4",
"profile": "https://jangto.tistory.com/",
"contributions": [
"code"
"code",
"doc"
]
},
{
@@ -1672,7 +1702,10 @@
"profile": "https://crossoverjie.top/",
"contributions": [
"code",
"doc"
"doc",
"blog",
"test",
"design"
]
},
{
@@ -1708,7 +1741,9 @@
"avatar_url": "https://avatars.githubusercontent.com/u/8465969?v=4",
"profile": "https://github.com/xuziyang",
"contributions": [
"code"
"code",
"doc",
"bug"
]
},
{
@@ -1762,7 +1797,13 @@
"avatar_url": "https://avatars.githubusercontent.com/u/29418975?v=4",
"profile": "http://www.hadoop.wiki/",
"contributions": [
"code"
"code",
"ideas",
"doc",
"blog",
"bug",
"test",
"design"
]
},
{
@@ -1771,7 +1812,11 @@
"avatar_url": "https://avatars.githubusercontent.com/u/61108539?v=4",
"profile": "https://github.com/zuobiao-zhou",
"contributions": [
"code"
"code",
"doc",
"bug",
"blog",
"test"
]
},
{
@@ -1807,7 +1852,11 @@
"avatar_url": "https://avatars.githubusercontent.com/u/25810623?v=4",
"profile": "https://github.com/Aias00",
"contributions": [
"code"
"code",
"doc",
"bug",
"ideas",
"test"
]
},
{
@@ -1825,7 +1874,10 @@
"avatar_url": "https://avatars.githubusercontent.com/u/77964041?v=4",
"profile": "https://blog.csdn.net/qq_52397471",
"contributions": [
"code"
"code",
"bug",
"test",
"blog"
]
},
{
@@ -1834,7 +1886,9 @@
"avatar_url": "https://avatars.githubusercontent.com/u/118667222?v=4",
"profile": "https://github.com/Yanshuming1",
"contributions": [
"code"
"code",
"doc",
"bug"
]
},
{
@@ -1934,8 +1988,10 @@
"avatar_url": "https://avatars.githubusercontent.com/u/3371163?v=4",
"profile": "https://github.com/kerwin612",
"contributions": [
"design",
"code",
"design"
"doc",
"bug"
]
},
{
@@ -1962,7 +2018,9 @@
"avatar_url": "https://avatars.githubusercontent.com/u/69385076?v=4",
"profile": "https://github.com/pwallk",
"contributions": [
"code"
"code",
"doc",
"bug"
]
},
{
@@ -2007,7 +2065,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/83876361?v=4",
"profile": "https://github.com/keaifafafa",
"contributions": [
"code"
"code",
"bug"
]
},
{
@@ -2142,7 +2201,9 @@
"avatar_url": "https://avatars.githubusercontent.com/u/59016860?v=4",
"profile": "https://github.com/Rancho-7",
"contributions": [
"doc"
"doc",
"code",
"bug"
]
},
{
@@ -2160,7 +2221,11 @@
"avatar_url": "https://avatars.githubusercontent.com/u/91836599?v=4",
"profile": "https://zzrl.cc/",
"contributions": [
"code"
"code",
"doc",
"bug",
"test",
"blog"
]
},
{
@@ -2178,7 +2243,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/14932913?v=4",
"profile": "https://simonsigre.com/",
"contributions": [
"doc"
"doc",
"code"
]
},
{
@@ -2241,7 +2307,9 @@
"avatar_url": "https://avatars.githubusercontent.com/u/11783444?v=4",
"profile": "https://medium.com/@pjfanning",
"contributions": [
"code"
"code",
"bug",
"test"
]
},
{
@@ -2259,7 +2327,9 @@
"avatar_url": "https://avatars.githubusercontent.com/u/22274133?v=4",
"profile": "https://github.com/MasamiYui",
"contributions": [
"code"
"code",
"doc",
"bug"
]
},
{
@@ -2314,7 +2384,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/147966331?v=4",
"profile": "https://github.com/notbugggg",
"contributions": [
"code"
"code",
"bug"
]
},
{
@@ -2332,7 +2403,10 @@
"avatar_url": "https://avatars.githubusercontent.com/u/44892459?v=4",
"profile": "https://github.com/JuJinPark",
"contributions": [
"code"
"code",
"doc",
"ideas",
"blog"
]
},
{
@@ -2341,7 +2415,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/43002118?v=4",
"profile": "https://github.com/LL-LIN",
"contributions": [
"code"
"code",
"bug"
]
},
{
@@ -2350,7 +2425,9 @@
"avatar_url": "https://avatars.githubusercontent.com/u/73413979?v=4",
"profile": "https://bigcyy.github.io/",
"contributions": [
"code"
"code",
"doc",
"bug"
]
},
{
@@ -2359,7 +2436,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/74091160?v=4",
"profile": "https://github.com/sarthakeash",
"contributions": [
"code"
"code",
"doc"
]
},
{
@@ -2395,7 +2473,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/153884653?v=4",
"profile": "https://github.com/Cyanty",
"contributions": [
"code"
"code",
"doc"
]
},
{
+53
View File
@@ -0,0 +1,53 @@
# 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.
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Monitor E2E Test
on:
schedule:
# trigger at 01:00 everyday
- cron: '0 1 * * *'
push:
branches: [ action* ]
jobs:
check-license-header:
name: Monitor E2E Test
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: ./script/ci/github-actions/setup-deps
- name: Build with Maven
run: mvn clean -B package -Prelease -Dmaven.test.skip=true --file pom.xml
- name: Build Image
uses: docker/build-push-action@v3
with:
context: ./dist
file: ./script/docker/server/Dockerfile
push: false
tags: apache/hertzbeat:test
- name: Run K8s Monitor E2E Test
run: |
sudo curl -L https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
sudo chmod u+x /usr/local/bin/docker-compose
cd e2e/k8s
sudo docker-compose version
sudo docker-compose up --exit-code-from testing --remove-orphans
+2
View File
@@ -13,6 +13,8 @@ package-lock.json
jdk/
data/
!/e2e/**/data/
### STS ###
.apt_generated
.classpath
+5 -4
View File
@@ -15,6 +15,7 @@
// limitations under the License.
//
{
"globs": ["home/**/*.md"],
"config": {
"default": true,
"MD001": true,
@@ -39,9 +40,9 @@
"MD047": true,
"MD052": true
},
"ignore": [
"node_modules/",
"target/",
"dist/"
"ignores": [
"**/node_modules/**",
"**/target/**",
"**/dist/**"
]
}
+37 -37
View File
@@ -222,7 +222,7 @@ Thanks to these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="http://mroldx.xyz/"><img src="https://avatars.githubusercontent.com/u/34847828?v=4?s=100" width="100px;" alt="mroldx"/><br /><sub><b>mroldx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mroldx" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/woshiniusange"><img src="https://avatars.githubusercontent.com/u/91513022?v=4?s=100" width="100px;" alt="woshiniusange"/><br /><sub><b>woshiniusange</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=woshiniusange" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://vampireachao.github.io/"><img src="https://avatars.githubusercontent.com/u/52746628?v=4?s=100" width="100px;" alt="VampireAchao"/><br /><sub><b>VampireAchao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=VampireAchao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Ceilzcx"><img src="https://avatars.githubusercontent.com/u/48920254?v=4?s=100" width="100px;" alt="zcx"/><br /><sub><b>zcx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACeilzcx" title="Bug reports">🐛</a> <a href="#design-Ceilzcx" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Ceilzcx"><img src="https://avatars.githubusercontent.com/u/48920254?v=4?s=100" width="100px;" alt="zcx"/><br /><sub><b>zcx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACeilzcx" title="Bug reports">🐛</a> <a href="#design-Ceilzcx" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Tests">⚠️</a> <a href="#blog-Ceilzcx" title="Blogposts">📝</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CharlieXCL"><img src="https://avatars.githubusercontent.com/u/91540487?v=4?s=100" width="100px;" alt="CharlieXCL"/><br /><sub><b>CharlieXCL</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=CharlieXCL" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Privauto"><img src="https://avatars.githubusercontent.com/u/36581456?v=4?s=100" width="100px;" alt="Privauto"/><br /><sub><b>Privauto</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Privauto" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Privauto" title="Documentation">📖</a></td>
</tr>
@@ -236,7 +236,7 @@ Thanks to these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://www.zhihu.com/people/kevinbauer"><img src="https://avatars.githubusercontent.com/u/28581579?v=4?s=100" width="100px;" alt="Kevin Huang"/><br /><sub><b>Kevin Huang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=kevinhuangwl" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TJxiaobao"><img src="https://avatars.githubusercontent.com/u/85919258?v=4?s=100" width="100px;" alt="铁甲小宝"/><br /><sub><b>铁甲小宝</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3ATJxiaobao" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TJxiaobao"><img src="https://avatars.githubusercontent.com/u/85919258?v=4?s=100" width="100px;" alt="铁甲小宝"/><br /><sub><b>铁甲小宝</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3ATJxiaobao" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Tests">⚠️</a> <a href="#design-TJxiaobao" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jack-123-power"><img src="https://avatars.githubusercontent.com/u/84333501?v=4?s=100" width="100px;" alt="Captain Jack"/><br /><sub><b>Captain Jack</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Jack-123-power" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/haibo-duan"><img src="https://avatars.githubusercontent.com/u/7974845?v=4?s=100" width="100px;" alt="haibo.duan"/><br /><sub><b>haibo.duan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=haibo-duan" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=haibo-duan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/assassinfym"><img src="https://avatars.githubusercontent.com/u/15188754?v=4?s=100" width="100px;" alt="assassin"/><br /><sub><b>assassin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3Aassassinfym" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=assassinfym" title="Code">💻</a></td>
@@ -278,7 +278,7 @@ Thanks to these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://wjl110.xyz/"><img src="https://avatars.githubusercontent.com/u/53851034?v=4?s=100" width="100px;" alt="wjl110"/><br /><sub><b>wjl110</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wjl110" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ngyhd"><img src="https://avatars.githubusercontent.com/u/29095207?v=4?s=100" width="100px;" alt="Sean"/><br /><sub><b>Sean</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ngyhd" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Daydreamer-ia"><img src="https://avatars.githubusercontent.com/u/83362909?v=4?s=100" width="100px;" alt="chenyiqin"/><br /><sub><b>chenyiqin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Daydreamer-ia" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Daydreamer-ia" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hudongdong129"><img src="https://avatars.githubusercontent.com/u/34374227?v=4?s=100" width="100px;" alt="hudongdong129"/><br /><sub><b>hudongdong129</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hudongdong129"><img src="https://avatars.githubusercontent.com/u/34374227?v=4?s=100" width="100px;" alt="hudongdong129"/><br /><sub><b>hudongdong129</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Documentation">📖</a> <a href="#design-hudongdong129" title="Design">🎨</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TherChenYang"><img src="https://avatars.githubusercontent.com/u/124348939?v=4?s=100" width="100px;" alt="TherChenYang"/><br /><sub><b>TherChenYang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=TherChenYang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TherChenYang" title="Tests">⚠️</a></td>
@@ -294,7 +294,7 @@ Thanks to these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/baiban114"><img src="https://avatars.githubusercontent.com/u/59152619?v=4?s=100" width="100px;" alt="TableRow"/><br /><sub><b>TableRow</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=baiban114" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=baiban114" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ByteIDance"><img src="https://avatars.githubusercontent.com/u/100207562?v=4?s=100" width="100px;" alt="ByteIDance"/><br /><sub><b>ByteIDance</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ByteIDance" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mangel2002"><img src="https://avatars.githubusercontent.com/u/9348020?v=4?s=100" width="100px;" alt="Jangfe"/><br /><sub><b>Jangfe</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mangel2002" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zqr10159"><img src="https://avatars.githubusercontent.com/u/30048352?v=4?s=100" width="100px;" alt="zqr10159"/><br /><sub><b>zqr10159</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zqr10159"><img src="https://avatars.githubusercontent.com/u/30048352?v=4?s=100" width="100px;" alt="zqr10159"/><br /><sub><b>zqr10159</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Code">💻</a> <a href="#blog-zqr10159" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azqr10159" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Tests">⚠️</a> <a href="#design-zqr10159" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vinci-897"><img src="https://avatars.githubusercontent.com/u/55838224?v=4?s=100" width="100px;" alt="vinci"/><br /><sub><b>vinci</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=vinci-897" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=vinci-897" title="Documentation">📖</a> <a href="#design-vinci-897" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/js110"><img src="https://avatars.githubusercontent.com/u/51191863?v=4?s=100" width="100px;" alt="js110"/><br /><sub><b>js110</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=js110" title="Code">💻</a></td>
</tr>
@@ -308,8 +308,8 @@ Thanks to these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZangJuxy"><img src="https://avatars.githubusercontent.com/u/71380295?v=4?s=100" width="100px;" alt="ZangJuxy"/><br /><sub><b>ZangJuxy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZangJuxy" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/l646505418"><img src="https://avatars.githubusercontent.com/u/50475131?v=4?s=100" width="100px;" alt="l646505418"/><br /><sub><b>l646505418</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=l646505418" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.carpewang.com"><img src="https://avatars.githubusercontent.com/u/78642589?v=4?s=100" width="100px;" alt="Carpe-Wang"/><br /><sub><b>Carpe-Wang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Carpe-Wang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/l646505418"><img src="https://avatars.githubusercontent.com/u/50475131?v=4?s=100" width="100px;" alt="l646505418"/><br /><sub><b>l646505418</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=l646505418" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Al646505418" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.carpewang.com"><img src="https://avatars.githubusercontent.com/u/78642589?v=4?s=100" width="100px;" alt="Carpe-Wang"/><br /><sub><b>Carpe-Wang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Carpe-Wang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACarpe-Wang" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/moshu023"><img src="https://avatars.githubusercontent.com/u/48593205?v=4?s=100" width="100px;" alt="莫枢"/><br /><sub><b>莫枢</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=moshu023" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/huangcanda"><img src="https://avatars.githubusercontent.com/u/4470566?v=4?s=100" width="100px;" alt="huangcanda"/><br /><sub><b>huangcanda</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=huangcanda" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.zrkizzy.com"><img src="https://avatars.githubusercontent.com/u/85340613?v=4?s=100" width="100px;" alt="世纪末的架构师"/><br /><sub><b>世纪末的架构师</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Architect-Java" title="Code">💻</a></td>
@@ -359,7 +359,7 @@ Thanks to these wonderful people, welcome to join us:
<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/apache/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/apache/hertzbeat/commits?author=YutingNie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Documentation">📖</a> <a href="#design-YutingNie" title="Design">🎨</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/apache/hertzbeat/commits?author=mikezzb" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Documentation">📖</a> <a href="#design-mikezzb" title="Design">🎨</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/apache/hertzbeat/commits?author=a-little-fool" 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/apache/hertzbeat/commits?author=a-little-fool" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=a-little-fool" title="Documentation">📖</a> <a href="#blog-a-little-fool" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Aa-little-fool" title="Bug reports">🐛</a> <a href="#design-a-little-fool" title="Design">🎨</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/apache/hertzbeat/commits?author=littlezhongzer" title="Code">💻</a></td>
@@ -368,7 +368,7 @@ Thanks to these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/XimfengYao"><img src="https://avatars.githubusercontent.com/u/17541537?v=4?s=100" width="100px;" alt="姚贤丰"/><br /><sub><b>姚贤丰</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=XimfengYao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LINGLUOJUN"><img src="https://avatars.githubusercontent.com/u/16778977?v=4?s=100" width="100px;" alt="lingluojun"/><br /><sub><b>lingluojun</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LINGLUOJUN" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.luelueking.com"><img src="https://avatars.githubusercontent.com/u/93204032?v=4?s=100" width="100px;" alt="1ue"/><br /><sub><b>1ue</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=luelueking" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.jimmyqiao.top"><img src="https://avatars.githubusercontent.com/u/67301054?v=4?s=100" width="100px;" alt="qyaaaa"/><br /><sub><b>qyaaaa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=qyaaaa" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.jimmyqiao.top"><img src="https://avatars.githubusercontent.com/u/67301054?v=4?s=100" width="100px;" alt="qyaaaa"/><br /><sub><b>qyaaaa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=qyaaaa" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Aqyaaaa" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://novohit.top"><img src="https://avatars.githubusercontent.com/u/101090395?v=4?s=100" width="100px;" alt="novohit"/><br /><sub><b>novohit</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=novohit" title="Code">💻</a></td>
@@ -382,14 +382,14 @@ Thanks to these wonderful people, welcome to join us:
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/1036664317"><img src="https://avatars.githubusercontent.com/u/7696697?v=4?s=100" width="100px;" alt="moghn"/><br /><sub><b>moghn</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=1036664317" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xiaoguolong"><img src="https://avatars.githubusercontent.com/u/33684988?v=4?s=100" width="100px;" alt="xiaoguolong"/><br /><sub><b>xiaoguolong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xiaoguolong" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Clownsw"><img src="https://avatars.githubusercontent.com/u/28394742?v=4?s=100" width="100px;" alt="Smliexx"/><br /><sub><b>Smliexx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Clownsw" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Calvin979"><img src="https://avatars.githubusercontent.com/u/131688897?v=4?s=100" width="100px;" alt="Naruse"/><br /><sub><b>Naruse</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Code">💻</a> <a href="#design-Calvin979" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Clownsw"><img src="https://avatars.githubusercontent.com/u/28394742?v=4?s=100" width="100px;" alt="Smliexx"/><br /><sub><b>Smliexx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Clownsw" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AClownsw" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Calvin979"><img src="https://avatars.githubusercontent.com/u/131688897?v=4?s=100" width="100px;" alt="Naruse"/><br /><sub><b>Naruse</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Code">💻</a> <a href="#design-Calvin979" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACalvin979" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bbelide2"><img src="https://avatars.githubusercontent.com/u/26840796?v=4?s=100" width="100px;" alt="Bala Sukesh"/><br /><sub><b>Bala Sukesh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bbelide2" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jinyaoMa"><img src="https://avatars.githubusercontent.com/u/25066570?v=4?s=100" width="100px;" alt="Jinyao Ma"/><br /><sub><b>Jinyao Ma</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jinyaoMa" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://linuxsuren.github.io/open-source-best-practice/"><img src="https://avatars.githubusercontent.com/u/1450685?v=4?s=100" width="100px;" alt="Rick"/><br /><sub><b>Rick</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LinuxSuRen" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LinuxSuRen" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZY945"><img src="https://avatars.githubusercontent.com/u/74083801?v=4?s=100" width="100px;" alt="东风"/><br /><sub><b>东风</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Code">💻</a> <a href="#design-ZY945" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZY945"><img src="https://avatars.githubusercontent.com/u/74083801?v=4?s=100" width="100px;" alt="东风"/><br /><sub><b>东风</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Code">💻</a> <a href="#design-ZY945" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AZY945" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/prolevel1"><img src="https://avatars.githubusercontent.com/u/51995525?v=4?s=100" width="100px;" alt="sonam singh"/><br /><sub><b>sonam singh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=prolevel1" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZhangZixuan1994"><img src="https://avatars.githubusercontent.com/u/20011653?v=4?s=100" width="100px;" alt="ZhangZixuan1994"/><br /><sub><b>ZhangZixuan1994</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZhangZixuan1994" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hurenjie1"><img src="https://avatars.githubusercontent.com/u/40120355?v=4?s=100" width="100px;" alt="SHIG"/><br /><sub><b>SHIG</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hurenjie1" title="Code">💻</a></td>
@@ -399,29 +399,29 @@ Thanks to these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/avvCode"><img src="https://avatars.githubusercontent.com/u/113538532?v=4?s=100" width="100px;" alt="avv"/><br /><sub><b>avv</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=avvCode" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yqxxgh"><img src="https://avatars.githubusercontent.com/u/42080876?v=4?s=100" width="100px;" alt="yqxxgh"/><br /><sub><b>yqxxgh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yqxxgh"><img src="https://avatars.githubusercontent.com/u/42080876?v=4?s=100" width="100px;" alt="yqxxgh"/><br /><sub><b>yqxxgh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayqxxgh" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CharlieShi46"><img src="https://avatars.githubusercontent.com/u/149798885?v=4?s=100" width="100px;" alt="CharlieShi46"/><br /><sub><b>CharlieShi46</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=CharlieShi46" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Nctllnty"><img src="https://avatars.githubusercontent.com/u/33241818?v=4?s=100" width="100px;" alt="Nctllnty"/><br /><sub><b>Nctllnty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Nctllnty" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Wang-Yonghao"><img src="https://avatars.githubusercontent.com/u/48146606?v=4?s=100" width="100px;" alt="Wang-Yonghao"/><br /><sub><b>Wang-Yonghao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Wang-Yonghao" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.yuque.com/dudiao/yy"><img src="https://avatars.githubusercontent.com/u/38355949?v=4?s=100" width="100px;" alt="读钓"/><br /><sub><b>读钓</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dudiao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starmilkxin"><img src="https://avatars.githubusercontent.com/u/55646681?v=4?s=100" width="100px;" alt="Xin"/><br /><sub><b>Xin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starmilkxin" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starmilkxin"><img src="https://avatars.githubusercontent.com/u/55646681?v=4?s=100" width="100px;" alt="Xin"/><br /><sub><b>Xin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starmilkxin" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Astarmilkxin" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/handy-git"><img src="https://avatars.githubusercontent.com/u/32837980?v=4?s=100" width="100px;" alt="handy"/><br /><sub><b>handy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=handy-git" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LiuTianyou"><img src="https://avatars.githubusercontent.com/u/30208283?v=4?s=100" width="100px;" alt="LiuTianyou"/><br /><sub><b>LiuTianyou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LiuTianyou"><img src="https://avatars.githubusercontent.com/u/30208283?v=4?s=100" width="100px;" alt="LiuTianyou"/><br /><sub><b>LiuTianyou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ALiuTianyou" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Tests">⚠️</a> <a href="#blog-LiuTianyou" title="Blogposts">📝</a> <a href="#design-LiuTianyou" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/WinterKi1ler"><img src="https://avatars.githubusercontent.com/u/160592092?v=4?s=100" width="100px;" alt="WinterKi1ler"/><br /><sub><b>WinterKi1ler</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=WinterKi1ler" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://sharehoo.cn/"><img src="https://avatars.githubusercontent.com/u/45377370?v=4?s=100" width="100px;" alt="miki"/><br /><sub><b>miki</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=miki-hmt" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://codeflex.substack.com/"><img src="https://avatars.githubusercontent.com/u/85513042?v=4?s=100" width="100px;" alt="Keshav Carpenter"/><br /><sub><b>Keshav Carpenter</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=alpha951" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=alpha951" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/makechoicenow"><img src="https://avatars.githubusercontent.com/u/9911918?v=4?s=100" width="100px;" alt="makechoicenow"/><br /><sub><b>makechoicenow</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=makechoicenow" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gjjjj0101"><img src="https://avatars.githubusercontent.com/u/71874373?v=4?s=100" width="100px;" alt="Gao Jian"/><br /><sub><b>Gao Jian</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Code">💻</a> <a href="#design-gjjjj0101" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gjjjj0101"><img src="https://avatars.githubusercontent.com/u/71874373?v=4?s=100" width="100px;" alt="Gao Jian"/><br /><sub><b>Gao Jian</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Documentation">📖</a> <a href="#design-gjjjj0101" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Agjjjj0101" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://jangto.tistory.com/"><img src="https://avatars.githubusercontent.com/u/37864182?v=4?s=100" width="100px;" alt="Hyeon Sung"/><br /><sub><b>Hyeon Sung</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://crossoverjie.top/"><img src="https://avatars.githubusercontent.com/u/15684156?v=4?s=100" width="100px;" alt="crossoverJie"/><br /><sub><b>crossoverJie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://jangto.tistory.com/"><img src="https://avatars.githubusercontent.com/u/37864182?v=4?s=100" width="100px;" alt="Hyeon Sung"/><br /><sub><b>Hyeon Sung</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://crossoverjie.top/"><img src="https://avatars.githubusercontent.com/u/15684156?v=4?s=100" width="100px;" alt="crossoverJie"/><br /><sub><b>crossoverJie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Documentation">📖</a> <a href="#blog-crossoverJie" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Tests">⚠️</a> <a href="#design-crossoverJie" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PeixyJ"><img src="https://avatars.githubusercontent.com/u/45998593?v=4?s=100" width="100px;" alt="PeixyJ"/><br /><sub><b>PeixyJ</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PeixyJ" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Hi-Mr-Wind"><img src="https://avatars.githubusercontent.com/u/85803831?v=4?s=100" width="100px;" alt="风如歌"/><br /><sub><b>风如歌</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Hi-Mr-Wind" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MananPoojara"><img src="https://avatars.githubusercontent.com/u/104253184?v=4?s=100" width="100px;" alt="Manan Pujara"/><br /><sub><b>Manan Pujara</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MananPoojara" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xuziyang"><img src="https://avatars.githubusercontent.com/u/8465969?v=4?s=100" width="100px;" alt="xuziyang"/><br /><sub><b>xuziyang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xuziyang"><img src="https://avatars.githubusercontent.com/u/8465969?v=4?s=100" width="100px;" alt="xuziyang"/><br /><sub><b>xuziyang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Axuziyang" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lwqzz"><img src="https://avatars.githubusercontent.com/u/62584513?v=4?s=100" width="100px;" alt="lwqzz"/><br /><sub><b>lwqzz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lwqzz" title="Code">💻</a></td>
</tr>
<tr>
@@ -429,17 +429,17 @@ Thanks to these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomorrowshipyltm"><img src="https://avatars.githubusercontent.com/u/61336903?v=4?s=100" width="100px;" alt="tomorrowshipyltm"/><br /><sub><b>tomorrowshipyltm</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=tomorrowshipyltm" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/15613060203"><img src="https://avatars.githubusercontent.com/u/41351615?v=4?s=100" width="100px;" alt="栗磊"/><br /><sub><b>栗磊</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=15613060203" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Alanxtl"><img src="https://avatars.githubusercontent.com/u/25652981?v=4?s=100" width="100px;" alt="Alan"/><br /><sub><b>Alan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Alanxtl" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.hadoop.wiki/"><img src="https://avatars.githubusercontent.com/u/29418975?v=4?s=100" width="100px;" alt="Jast"/><br /><sub><b>Jast</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zuobiao-zhou"><img src="https://avatars.githubusercontent.com/u/61108539?v=4?s=100" width="100px;" alt="Zhang Yuxuan"/><br /><sub><b>Zhang Yuxuan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.hadoop.wiki/"><img src="https://avatars.githubusercontent.com/u/29418975?v=4?s=100" width="100px;" alt="Jast"/><br /><sub><b>Jast</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Code">💻</a> <a href="#ideas-zhangshenghang" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Documentation">📖</a> <a href="#blog-zhangshenghang" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azhangshenghang" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Tests">⚠️</a> <a href="#design-zhangshenghang" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zuobiao-zhou"><img src="https://avatars.githubusercontent.com/u/61108539?v=4?s=100" width="100px;" alt="Zhang Yuxuan"/><br /><sub><b>Zhang Yuxuan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azuobiao-zhou" title="Bug reports">🐛</a> <a href="#blog-zuobiao-zhou" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Pzz-2021"><img src="https://avatars.githubusercontent.com/u/118056735?v=4?s=100" width="100px;" alt="P.P."/><br /><sub><b>P.P.</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Pzz-2021" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LLP2333"><img src="https://avatars.githubusercontent.com/u/61670545?v=4?s=100" width="100px;" alt="llp2333"/><br /><sub><b>llp2333</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LLP2333" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/HeartLinked"><img src="https://avatars.githubusercontent.com/u/78212101?v=4?s=100" width="100px;" alt="feiyang li"/><br /><sub><b>feiyang li</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=HeartLinked" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Aias00"><img src="https://avatars.githubusercontent.com/u/25810623?v=4?s=100" width="100px;" alt="aias00"/><br /><sub><b>aias00</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Aias00"><img src="https://avatars.githubusercontent.com/u/25810623?v=4?s=100" width="100px;" alt="aias00"/><br /><sub><b>aias00</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AAias00" title="Bug reports">🐛</a> <a href="#ideas-Aias00" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/westboy"><img src="https://avatars.githubusercontent.com/u/6385565?v=4?s=100" width="100px;" alt="Jin"/><br /><sub><b>Jin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=westboy" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://blog.csdn.net/qq_52397471"><img src="https://avatars.githubusercontent.com/u/77964041?v=4?s=100" width="100px;" alt="YuLuo"/><br /><sub><b>YuLuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Yanshuming1"><img src="https://avatars.githubusercontent.com/u/118667222?v=4?s=100" width="100px;" alt="linDong"/><br /><sub><b>linDong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://blog.csdn.net/qq_52397471"><img src="https://avatars.githubusercontent.com/u/77964041?v=4?s=100" width="100px;" alt="YuLuo"/><br /><sub><b>YuLuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayuluo-yx" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Tests">⚠️</a> <a href="#blog-yuluo-yx" title="Blogposts">📝</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Yanshuming1"><img src="https://avatars.githubusercontent.com/u/118667222?v=4?s=100" width="100px;" alt="linDong"/><br /><sub><b>linDong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AYanshuming1" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lwjxy"><img src="https://avatars.githubusercontent.com/u/52726400?v=4?s=100" width="100px;" alt="lwjxy"/><br /><sub><b>lwjxy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lwjxy" title="Code">💻</a></td>
</tr>
<tr>
@@ -454,17 +454,17 @@ Thanks to these wonderful people, welcome to join us:
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ileonli"><img src="https://avatars.githubusercontent.com/u/45332412?v=4?s=100" width="100px;" alt="Leon Li"/><br /><sub><b>Leon Li</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ileonli" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://fnil.net/"><img src="https://avatars.githubusercontent.com/u/14142?v=4?s=100" width="100px;" alt="dennis zhuang"/><br /><sub><b>dennis zhuang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=killme2008" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kerwin612"><img src="https://avatars.githubusercontent.com/u/3371163?v=4?s=100" width="100px;" alt="Kerwin Bryant"/><br /><sub><b>Kerwin Bryant</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Code">💻</a> <a href="#design-kerwin612" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kerwin612"><img src="https://avatars.githubusercontent.com/u/3371163?v=4?s=100" width="100px;" alt="Kerwin Bryant"/><br /><sub><b>Kerwin Bryant</b></sub></a><br /><a href="#design-kerwin612" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Akerwin612" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ShineDevelopment"><img src="https://avatars.githubusercontent.com/u/59306780?v=4?s=100" width="100px;" alt="daixianglong"/><br /><sub><b>daixianglong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ShineDevelopment" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mchgood"><img src="https://avatars.githubusercontent.com/u/38482005?v=4?s=100" width="100px;" alt="mchgood"/><br /><sub><b>mchgood</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mchgood" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pwallk"><img src="https://avatars.githubusercontent.com/u/69385076?v=4?s=100" width="100px;" alt="kangli"/><br /><sub><b>kangli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pwallk"><img src="https://avatars.githubusercontent.com/u/69385076?v=4?s=100" width="100px;" alt="kangli"/><br /><sub><b>kangli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Apwallk" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cdphantom"><img src="https://avatars.githubusercontent.com/u/12674795?v=4?s=100" width="100px;" alt="cdphantom"/><br /><sub><b>cdphantom</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=cdphantom" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/asd108908382"><img src="https://avatars.githubusercontent.com/u/77717999?v=4?s=100" width="100px;" alt="jiawei.guo"/><br /><sub><b>jiawei.guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=asd108908382" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/QBH-insist"><img src="https://avatars.githubusercontent.com/u/39401478?v=4?s=100" width="100px;" alt="QBH-insist"/><br /><sub><b>QBH-insist</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=QBH-insist" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jiangsh-ui"><img src="https://avatars.githubusercontent.com/u/86990361?v=4?s=100" width="100px;" alt="jiangsh"/><br /><sub><b>jiangsh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jiangsh-ui" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/keaifafafa"><img src="https://avatars.githubusercontent.com/u/83876361?v=4?s=100" width="100px;" alt="Keaifa"/><br /><sub><b>Keaifa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=keaifafafa" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/keaifafafa"><img src="https://avatars.githubusercontent.com/u/83876361?v=4?s=100" width="100px;" alt="Keaifa"/><br /><sub><b>Keaifa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=keaifafafa" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Akeaifafafa" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/loong95"><img src="https://avatars.githubusercontent.com/u/16333958?v=4?s=100" width="100px;" alt="Loong"/><br /><sub><b>Loong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=loong95" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ceekay47"><img src="https://avatars.githubusercontent.com/u/104664857?v=4?s=100" width="100px;" alt="Chandrakant Vankayalapati"/><br /><sub><b>Chandrakant Vankayalapati</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ceekay47" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MRgenial"><img src="https://avatars.githubusercontent.com/u/49973336?v=4?s=100" width="100px;" alt="b_mountain"/><br /><sub><b>b_mountain</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MRgenial" title="Code">💻</a></td>
@@ -483,13 +483,13 @@ Thanks to these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starryCoder"><img src="https://avatars.githubusercontent.com/u/46510059?v=4?s=100" width="100px;" alt="starryCoder"/><br /><sub><b>starryCoder</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starryCoder" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hasimmollah"><img src="https://avatars.githubusercontent.com/u/32538599?v=4?s=100" width="100px;" alt="hasimmollah"/><br /><sub><b>hasimmollah</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hasimmollah" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ayu-v0"><img src="https://avatars.githubusercontent.com/u/127600988?v=4?s=100" width="100px;" alt="Ayu"/><br /><sub><b>Ayu</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ayu-v0" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Rancho-7"><img src="https://avatars.githubusercontent.com/u/59016860?v=4?s=100" width="100px;" alt="Nick Guo"/><br /><sub><b>Nick Guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Rancho-7"><img src="https://avatars.githubusercontent.com/u/59016860?v=4?s=100" width="100px;" alt="Nick Guo"/><br /><sub><b>Nick Guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ARancho-7" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/doveLin0818"><img src="https://avatars.githubusercontent.com/u/190927907?v=4?s=100" width="100px;" alt="doveLin"/><br /><sub><b>doveLin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=doveLin0818" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://zzrl.cc/"><img src="https://avatars.githubusercontent.com/u/91836599?v=4?s=100" width="100px;" alt="yunfan24"/><br /><sub><b>yunfan24</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://zzrl.cc/"><img src="https://avatars.githubusercontent.com/u/91836599?v=4?s=100" width="100px;" alt="yunfan24"/><br /><sub><b>yunfan24</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayunfan24" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Tests">⚠️</a> <a href="#blog-yunfan24" title="Blogposts">📝</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lctking"><img src="https://avatars.githubusercontent.com/u/168249998?v=4?s=100" width="100px;" alt="nullwli"/><br /><sub><b>nullwli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lctking" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://simonsigre.com/"><img src="https://avatars.githubusercontent.com/u/14932913?v=4?s=100" width="100px;" alt="Simon Sigré"/><br /><sub><b>Simon Sigré</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://simonsigre.com/"><img src="https://avatars.githubusercontent.com/u/14932913?v=4?s=100" width="100px;" alt="Simon Sigré"/><br /><sub><b>Simon Sigré</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.ponfee.cn/"><img src="https://avatars.githubusercontent.com/u/46117331?v=4?s=100" width="100px;" alt="ponfee"/><br /><sub><b>ponfee</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ponfee" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Vedant7789"><img src="https://avatars.githubusercontent.com/u/147625492?v=4?s=100" width="100px;" alt="Vedant7789"/><br /><sub><b>Vedant7789</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Vedant7789" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Craaaaazy77"><img src="https://avatars.githubusercontent.com/u/23025522?v=4?s=100" width="100px;" alt="Craaaaazy77"/><br /><sub><b>Craaaaazy77</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Craaaaazy77" title="Documentation">📖</a></td>
@@ -498,9 +498,9 @@ Thanks to these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/helei1030"><img src="https://avatars.githubusercontent.com/u/11839080?v=4?s=100" width="100px;" alt="helei1030"/><br /><sub><b>helei1030</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=helei1030" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Apjfanning" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MonsterChenzhuo"><img src="https://avatars.githubusercontent.com/u/60029759?v=4?s=100" width="100px;" alt="monster"/><br /><sub><b>monster</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MonsterChenzhuo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AMasamiYui" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wanhao23"><img src="https://avatars.githubusercontent.com/u/29560961?v=4?s=100" width="100px;" alt="wanhao"/><br /><sub><b>wanhao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jonasHanhan"><img src="https://avatars.githubusercontent.com/u/130035609?v=4?s=100" width="100px;" alt="jonasHanhan"/><br /><sub><b>jonasHanhan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jonasHanhan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NikhilMurugesan"><img src="https://avatars.githubusercontent.com/u/49281792?v=4?s=100" width="100px;" alt="NikhilMurugesan"/><br /><sub><b>NikhilMurugesan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=NikhilMurugesan" title="Code">💻</a></td>
@@ -508,18 +508,18 @@ Thanks to these wonderful people, welcome to join us:
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Anotbugggg" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Documentation">📖</a> <a href="#ideas-JuJinPark" title="Ideas, Planning, & Feedback">🤔</a> <a href="#blog-JuJinPark" title="Blogposts">📝</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ALL-LIN" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Abigcyy" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PengJingzhao"><img src="https://avatars.githubusercontent.com/u/97368949?v=4?s=100" width="100px;" alt="彭镜肇"/><br /><sub><b>彭镜肇</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PengJingzhao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gagaradio"><img src="https://avatars.githubusercontent.com/u/18532370?v=4?s=100" width="100px;" alt="Walter Jia"/><br /><sub><b>Walter Jia</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gagaradio" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/boyucjz"><img src="https://avatars.githubusercontent.com/u/18730041?v=4?s=100" width="100px;" alt="boyucjz"/><br /><sub><b>boyucjz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=boyucjz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KevinLLF"><img src="https://avatars.githubusercontent.com/u/85452733?v=4?s=100" width="100px;" alt="Jay丿167"/><br /><sub><b>Jay丿167</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KevinLLF" title="Code">💻</a></td>
</tr>
</tbody>
+37 -37
View File
@@ -221,7 +221,7 @@ Thanks these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="http://mroldx.xyz/"><img src="https://avatars.githubusercontent.com/u/34847828?v=4?s=100" width="100px;" alt="mroldx"/><br /><sub><b>mroldx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mroldx" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/woshiniusange"><img src="https://avatars.githubusercontent.com/u/91513022?v=4?s=100" width="100px;" alt="woshiniusange"/><br /><sub><b>woshiniusange</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=woshiniusange" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://vampireachao.github.io/"><img src="https://avatars.githubusercontent.com/u/52746628?v=4?s=100" width="100px;" alt="VampireAchao"/><br /><sub><b>VampireAchao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=VampireAchao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Ceilzcx"><img src="https://avatars.githubusercontent.com/u/48920254?v=4?s=100" width="100px;" alt="zcx"/><br /><sub><b>zcx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACeilzcx" title="Bug reports">🐛</a> <a href="#design-Ceilzcx" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Ceilzcx"><img src="https://avatars.githubusercontent.com/u/48920254?v=4?s=100" width="100px;" alt="zcx"/><br /><sub><b>zcx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACeilzcx" title="Bug reports">🐛</a> <a href="#design-Ceilzcx" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Tests">⚠️</a> <a href="#blog-Ceilzcx" title="Blogposts">📝</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CharlieXCL"><img src="https://avatars.githubusercontent.com/u/91540487?v=4?s=100" width="100px;" alt="CharlieXCL"/><br /><sub><b>CharlieXCL</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=CharlieXCL" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Privauto"><img src="https://avatars.githubusercontent.com/u/36581456?v=4?s=100" width="100px;" alt="Privauto"/><br /><sub><b>Privauto</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Privauto" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Privauto" title="Documentation">📖</a></td>
</tr>
@@ -235,7 +235,7 @@ Thanks these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://www.zhihu.com/people/kevinbauer"><img src="https://avatars.githubusercontent.com/u/28581579?v=4?s=100" width="100px;" alt="Kevin Huang"/><br /><sub><b>Kevin Huang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=kevinhuangwl" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TJxiaobao"><img src="https://avatars.githubusercontent.com/u/85919258?v=4?s=100" width="100px;" alt="铁甲小宝"/><br /><sub><b>铁甲小宝</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3ATJxiaobao" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TJxiaobao"><img src="https://avatars.githubusercontent.com/u/85919258?v=4?s=100" width="100px;" alt="铁甲小宝"/><br /><sub><b>铁甲小宝</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3ATJxiaobao" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Tests">⚠️</a> <a href="#design-TJxiaobao" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jack-123-power"><img src="https://avatars.githubusercontent.com/u/84333501?v=4?s=100" width="100px;" alt="Captain Jack"/><br /><sub><b>Captain Jack</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Jack-123-power" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/haibo-duan"><img src="https://avatars.githubusercontent.com/u/7974845?v=4?s=100" width="100px;" alt="haibo.duan"/><br /><sub><b>haibo.duan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=haibo-duan" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=haibo-duan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/assassinfym"><img src="https://avatars.githubusercontent.com/u/15188754?v=4?s=100" width="100px;" alt="assassin"/><br /><sub><b>assassin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3Aassassinfym" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=assassinfym" title="Code">💻</a></td>
@@ -277,7 +277,7 @@ Thanks these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://wjl110.xyz/"><img src="https://avatars.githubusercontent.com/u/53851034?v=4?s=100" width="100px;" alt="wjl110"/><br /><sub><b>wjl110</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wjl110" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ngyhd"><img src="https://avatars.githubusercontent.com/u/29095207?v=4?s=100" width="100px;" alt="Sean"/><br /><sub><b>Sean</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ngyhd" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Daydreamer-ia"><img src="https://avatars.githubusercontent.com/u/83362909?v=4?s=100" width="100px;" alt="chenyiqin"/><br /><sub><b>chenyiqin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Daydreamer-ia" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Daydreamer-ia" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hudongdong129"><img src="https://avatars.githubusercontent.com/u/34374227?v=4?s=100" width="100px;" alt="hudongdong129"/><br /><sub><b>hudongdong129</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hudongdong129"><img src="https://avatars.githubusercontent.com/u/34374227?v=4?s=100" width="100px;" alt="hudongdong129"/><br /><sub><b>hudongdong129</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Documentation">📖</a> <a href="#design-hudongdong129" title="Design">🎨</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TherChenYang"><img src="https://avatars.githubusercontent.com/u/124348939?v=4?s=100" width="100px;" alt="TherChenYang"/><br /><sub><b>TherChenYang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=TherChenYang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TherChenYang" title="Tests">⚠️</a></td>
@@ -293,7 +293,7 @@ Thanks these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/baiban114"><img src="https://avatars.githubusercontent.com/u/59152619?v=4?s=100" width="100px;" alt="TableRow"/><br /><sub><b>TableRow</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=baiban114" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=baiban114" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ByteIDance"><img src="https://avatars.githubusercontent.com/u/100207562?v=4?s=100" width="100px;" alt="ByteIDance"/><br /><sub><b>ByteIDance</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ByteIDance" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mangel2002"><img src="https://avatars.githubusercontent.com/u/9348020?v=4?s=100" width="100px;" alt="Jangfe"/><br /><sub><b>Jangfe</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mangel2002" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zqr10159"><img src="https://avatars.githubusercontent.com/u/30048352?v=4?s=100" width="100px;" alt="zqr10159"/><br /><sub><b>zqr10159</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zqr10159"><img src="https://avatars.githubusercontent.com/u/30048352?v=4?s=100" width="100px;" alt="zqr10159"/><br /><sub><b>zqr10159</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Code">💻</a> <a href="#blog-zqr10159" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azqr10159" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Tests">⚠️</a> <a href="#design-zqr10159" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vinci-897"><img src="https://avatars.githubusercontent.com/u/55838224?v=4?s=100" width="100px;" alt="vinci"/><br /><sub><b>vinci</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=vinci-897" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=vinci-897" title="Documentation">📖</a> <a href="#design-vinci-897" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/js110"><img src="https://avatars.githubusercontent.com/u/51191863?v=4?s=100" width="100px;" alt="js110"/><br /><sub><b>js110</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=js110" title="Code">💻</a></td>
</tr>
@@ -307,8 +307,8 @@ Thanks these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZangJuxy"><img src="https://avatars.githubusercontent.com/u/71380295?v=4?s=100" width="100px;" alt="ZangJuxy"/><br /><sub><b>ZangJuxy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZangJuxy" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/l646505418"><img src="https://avatars.githubusercontent.com/u/50475131?v=4?s=100" width="100px;" alt="l646505418"/><br /><sub><b>l646505418</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=l646505418" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.carpewang.com"><img src="https://avatars.githubusercontent.com/u/78642589?v=4?s=100" width="100px;" alt="Carpe-Wang"/><br /><sub><b>Carpe-Wang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Carpe-Wang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/l646505418"><img src="https://avatars.githubusercontent.com/u/50475131?v=4?s=100" width="100px;" alt="l646505418"/><br /><sub><b>l646505418</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=l646505418" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Al646505418" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.carpewang.com"><img src="https://avatars.githubusercontent.com/u/78642589?v=4?s=100" width="100px;" alt="Carpe-Wang"/><br /><sub><b>Carpe-Wang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Carpe-Wang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACarpe-Wang" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/moshu023"><img src="https://avatars.githubusercontent.com/u/48593205?v=4?s=100" width="100px;" alt="莫枢"/><br /><sub><b>莫枢</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=moshu023" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/huangcanda"><img src="https://avatars.githubusercontent.com/u/4470566?v=4?s=100" width="100px;" alt="huangcanda"/><br /><sub><b>huangcanda</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=huangcanda" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.zrkizzy.com"><img src="https://avatars.githubusercontent.com/u/85340613?v=4?s=100" width="100px;" alt="世纪末的架构师"/><br /><sub><b>世纪末的架构师</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Architect-Java" title="Code">💻</a></td>
@@ -358,7 +358,7 @@ Thanks these wonderful people, welcome to join us:
<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/apache/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/apache/hertzbeat/commits?author=YutingNie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Documentation">📖</a> <a href="#design-YutingNie" title="Design">🎨</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/apache/hertzbeat/commits?author=mikezzb" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Documentation">📖</a> <a href="#design-mikezzb" title="Design">🎨</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/apache/hertzbeat/commits?author=a-little-fool" 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/apache/hertzbeat/commits?author=a-little-fool" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=a-little-fool" title="Documentation">📖</a> <a href="#blog-a-little-fool" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Aa-little-fool" title="Bug reports">🐛</a> <a href="#design-a-little-fool" title="Design">🎨</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/apache/hertzbeat/commits?author=littlezhongzer" title="Code">💻</a></td>
@@ -367,7 +367,7 @@ Thanks these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/XimfengYao"><img src="https://avatars.githubusercontent.com/u/17541537?v=4?s=100" width="100px;" alt="姚贤丰"/><br /><sub><b>姚贤丰</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=XimfengYao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LINGLUOJUN"><img src="https://avatars.githubusercontent.com/u/16778977?v=4?s=100" width="100px;" alt="lingluojun"/><br /><sub><b>lingluojun</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LINGLUOJUN" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.luelueking.com"><img src="https://avatars.githubusercontent.com/u/93204032?v=4?s=100" width="100px;" alt="1ue"/><br /><sub><b>1ue</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=luelueking" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.jimmyqiao.top"><img src="https://avatars.githubusercontent.com/u/67301054?v=4?s=100" width="100px;" alt="qyaaaa"/><br /><sub><b>qyaaaa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=qyaaaa" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.jimmyqiao.top"><img src="https://avatars.githubusercontent.com/u/67301054?v=4?s=100" width="100px;" alt="qyaaaa"/><br /><sub><b>qyaaaa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=qyaaaa" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Aqyaaaa" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://novohit.top"><img src="https://avatars.githubusercontent.com/u/101090395?v=4?s=100" width="100px;" alt="novohit"/><br /><sub><b>novohit</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=novohit" title="Code">💻</a></td>
@@ -381,14 +381,14 @@ Thanks these wonderful people, welcome to join us:
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/1036664317"><img src="https://avatars.githubusercontent.com/u/7696697?v=4?s=100" width="100px;" alt="moghn"/><br /><sub><b>moghn</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=1036664317" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xiaoguolong"><img src="https://avatars.githubusercontent.com/u/33684988?v=4?s=100" width="100px;" alt="xiaoguolong"/><br /><sub><b>xiaoguolong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xiaoguolong" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Clownsw"><img src="https://avatars.githubusercontent.com/u/28394742?v=4?s=100" width="100px;" alt="Smliexx"/><br /><sub><b>Smliexx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Clownsw" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Calvin979"><img src="https://avatars.githubusercontent.com/u/131688897?v=4?s=100" width="100px;" alt="Naruse"/><br /><sub><b>Naruse</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Code">💻</a> <a href="#design-Calvin979" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Clownsw"><img src="https://avatars.githubusercontent.com/u/28394742?v=4?s=100" width="100px;" alt="Smliexx"/><br /><sub><b>Smliexx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Clownsw" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AClownsw" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Calvin979"><img src="https://avatars.githubusercontent.com/u/131688897?v=4?s=100" width="100px;" alt="Naruse"/><br /><sub><b>Naruse</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Code">💻</a> <a href="#design-Calvin979" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACalvin979" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bbelide2"><img src="https://avatars.githubusercontent.com/u/26840796?v=4?s=100" width="100px;" alt="Bala Sukesh"/><br /><sub><b>Bala Sukesh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bbelide2" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jinyaoMa"><img src="https://avatars.githubusercontent.com/u/25066570?v=4?s=100" width="100px;" alt="Jinyao Ma"/><br /><sub><b>Jinyao Ma</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jinyaoMa" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://linuxsuren.github.io/open-source-best-practice/"><img src="https://avatars.githubusercontent.com/u/1450685?v=4?s=100" width="100px;" alt="Rick"/><br /><sub><b>Rick</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LinuxSuRen" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LinuxSuRen" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZY945"><img src="https://avatars.githubusercontent.com/u/74083801?v=4?s=100" width="100px;" alt="东风"/><br /><sub><b>东风</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Code">💻</a> <a href="#design-ZY945" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZY945"><img src="https://avatars.githubusercontent.com/u/74083801?v=4?s=100" width="100px;" alt="东风"/><br /><sub><b>东风</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Code">💻</a> <a href="#design-ZY945" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AZY945" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/prolevel1"><img src="https://avatars.githubusercontent.com/u/51995525?v=4?s=100" width="100px;" alt="sonam singh"/><br /><sub><b>sonam singh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=prolevel1" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZhangZixuan1994"><img src="https://avatars.githubusercontent.com/u/20011653?v=4?s=100" width="100px;" alt="ZhangZixuan1994"/><br /><sub><b>ZhangZixuan1994</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZhangZixuan1994" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hurenjie1"><img src="https://avatars.githubusercontent.com/u/40120355?v=4?s=100" width="100px;" alt="SHIG"/><br /><sub><b>SHIG</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hurenjie1" title="Code">💻</a></td>
@@ -398,29 +398,29 @@ Thanks these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/avvCode"><img src="https://avatars.githubusercontent.com/u/113538532?v=4?s=100" width="100px;" alt="avv"/><br /><sub><b>avv</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=avvCode" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yqxxgh"><img src="https://avatars.githubusercontent.com/u/42080876?v=4?s=100" width="100px;" alt="yqxxgh"/><br /><sub><b>yqxxgh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yqxxgh"><img src="https://avatars.githubusercontent.com/u/42080876?v=4?s=100" width="100px;" alt="yqxxgh"/><br /><sub><b>yqxxgh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayqxxgh" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CharlieShi46"><img src="https://avatars.githubusercontent.com/u/149798885?v=4?s=100" width="100px;" alt="CharlieShi46"/><br /><sub><b>CharlieShi46</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=CharlieShi46" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Nctllnty"><img src="https://avatars.githubusercontent.com/u/33241818?v=4?s=100" width="100px;" alt="Nctllnty"/><br /><sub><b>Nctllnty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Nctllnty" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Wang-Yonghao"><img src="https://avatars.githubusercontent.com/u/48146606?v=4?s=100" width="100px;" alt="Wang-Yonghao"/><br /><sub><b>Wang-Yonghao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Wang-Yonghao" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.yuque.com/dudiao/yy"><img src="https://avatars.githubusercontent.com/u/38355949?v=4?s=100" width="100px;" alt="读钓"/><br /><sub><b>读钓</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dudiao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starmilkxin"><img src="https://avatars.githubusercontent.com/u/55646681?v=4?s=100" width="100px;" alt="Xin"/><br /><sub><b>Xin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starmilkxin" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starmilkxin"><img src="https://avatars.githubusercontent.com/u/55646681?v=4?s=100" width="100px;" alt="Xin"/><br /><sub><b>Xin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starmilkxin" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Astarmilkxin" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/handy-git"><img src="https://avatars.githubusercontent.com/u/32837980?v=4?s=100" width="100px;" alt="handy"/><br /><sub><b>handy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=handy-git" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LiuTianyou"><img src="https://avatars.githubusercontent.com/u/30208283?v=4?s=100" width="100px;" alt="LiuTianyou"/><br /><sub><b>LiuTianyou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LiuTianyou"><img src="https://avatars.githubusercontent.com/u/30208283?v=4?s=100" width="100px;" alt="LiuTianyou"/><br /><sub><b>LiuTianyou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ALiuTianyou" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Tests">⚠️</a> <a href="#blog-LiuTianyou" title="Blogposts">📝</a> <a href="#design-LiuTianyou" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/WinterKi1ler"><img src="https://avatars.githubusercontent.com/u/160592092?v=4?s=100" width="100px;" alt="WinterKi1ler"/><br /><sub><b>WinterKi1ler</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=WinterKi1ler" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://sharehoo.cn/"><img src="https://avatars.githubusercontent.com/u/45377370?v=4?s=100" width="100px;" alt="miki"/><br /><sub><b>miki</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=miki-hmt" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://codeflex.substack.com/"><img src="https://avatars.githubusercontent.com/u/85513042?v=4?s=100" width="100px;" alt="Keshav Carpenter"/><br /><sub><b>Keshav Carpenter</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=alpha951" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=alpha951" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/makechoicenow"><img src="https://avatars.githubusercontent.com/u/9911918?v=4?s=100" width="100px;" alt="makechoicenow"/><br /><sub><b>makechoicenow</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=makechoicenow" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gjjjj0101"><img src="https://avatars.githubusercontent.com/u/71874373?v=4?s=100" width="100px;" alt="Gao Jian"/><br /><sub><b>Gao Jian</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Code">💻</a> <a href="#design-gjjjj0101" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gjjjj0101"><img src="https://avatars.githubusercontent.com/u/71874373?v=4?s=100" width="100px;" alt="Gao Jian"/><br /><sub><b>Gao Jian</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Documentation">📖</a> <a href="#design-gjjjj0101" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Agjjjj0101" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://jangto.tistory.com/"><img src="https://avatars.githubusercontent.com/u/37864182?v=4?s=100" width="100px;" alt="Hyeon Sung"/><br /><sub><b>Hyeon Sung</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://crossoverjie.top/"><img src="https://avatars.githubusercontent.com/u/15684156?v=4?s=100" width="100px;" alt="crossoverJie"/><br /><sub><b>crossoverJie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://jangto.tistory.com/"><img src="https://avatars.githubusercontent.com/u/37864182?v=4?s=100" width="100px;" alt="Hyeon Sung"/><br /><sub><b>Hyeon Sung</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://crossoverjie.top/"><img src="https://avatars.githubusercontent.com/u/15684156?v=4?s=100" width="100px;" alt="crossoverJie"/><br /><sub><b>crossoverJie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Documentation">📖</a> <a href="#blog-crossoverJie" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Tests">⚠️</a> <a href="#design-crossoverJie" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PeixyJ"><img src="https://avatars.githubusercontent.com/u/45998593?v=4?s=100" width="100px;" alt="PeixyJ"/><br /><sub><b>PeixyJ</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PeixyJ" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Hi-Mr-Wind"><img src="https://avatars.githubusercontent.com/u/85803831?v=4?s=100" width="100px;" alt="风如歌"/><br /><sub><b>风如歌</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Hi-Mr-Wind" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MananPoojara"><img src="https://avatars.githubusercontent.com/u/104253184?v=4?s=100" width="100px;" alt="Manan Pujara"/><br /><sub><b>Manan Pujara</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MananPoojara" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xuziyang"><img src="https://avatars.githubusercontent.com/u/8465969?v=4?s=100" width="100px;" alt="xuziyang"/><br /><sub><b>xuziyang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xuziyang"><img src="https://avatars.githubusercontent.com/u/8465969?v=4?s=100" width="100px;" alt="xuziyang"/><br /><sub><b>xuziyang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Axuziyang" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lwqzz"><img src="https://avatars.githubusercontent.com/u/62584513?v=4?s=100" width="100px;" alt="lwqzz"/><br /><sub><b>lwqzz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lwqzz" title="Code">💻</a></td>
</tr>
<tr>
@@ -428,17 +428,17 @@ Thanks these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomorrowshipyltm"><img src="https://avatars.githubusercontent.com/u/61336903?v=4?s=100" width="100px;" alt="tomorrowshipyltm"/><br /><sub><b>tomorrowshipyltm</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=tomorrowshipyltm" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/15613060203"><img src="https://avatars.githubusercontent.com/u/41351615?v=4?s=100" width="100px;" alt="栗磊"/><br /><sub><b>栗磊</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=15613060203" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Alanxtl"><img src="https://avatars.githubusercontent.com/u/25652981?v=4?s=100" width="100px;" alt="Alan"/><br /><sub><b>Alan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Alanxtl" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.hadoop.wiki/"><img src="https://avatars.githubusercontent.com/u/29418975?v=4?s=100" width="100px;" alt="Jast"/><br /><sub><b>Jast</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zuobiao-zhou"><img src="https://avatars.githubusercontent.com/u/61108539?v=4?s=100" width="100px;" alt="Zhang Yuxuan"/><br /><sub><b>Zhang Yuxuan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.hadoop.wiki/"><img src="https://avatars.githubusercontent.com/u/29418975?v=4?s=100" width="100px;" alt="Jast"/><br /><sub><b>Jast</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Code">💻</a> <a href="#ideas-zhangshenghang" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Documentation">📖</a> <a href="#blog-zhangshenghang" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azhangshenghang" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Tests">⚠️</a> <a href="#design-zhangshenghang" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zuobiao-zhou"><img src="https://avatars.githubusercontent.com/u/61108539?v=4?s=100" width="100px;" alt="Zhang Yuxuan"/><br /><sub><b>Zhang Yuxuan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azuobiao-zhou" title="Bug reports">🐛</a> <a href="#blog-zuobiao-zhou" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Pzz-2021"><img src="https://avatars.githubusercontent.com/u/118056735?v=4?s=100" width="100px;" alt="P.P."/><br /><sub><b>P.P.</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Pzz-2021" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LLP2333"><img src="https://avatars.githubusercontent.com/u/61670545?v=4?s=100" width="100px;" alt="llp2333"/><br /><sub><b>llp2333</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LLP2333" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/HeartLinked"><img src="https://avatars.githubusercontent.com/u/78212101?v=4?s=100" width="100px;" alt="feiyang li"/><br /><sub><b>feiyang li</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=HeartLinked" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Aias00"><img src="https://avatars.githubusercontent.com/u/25810623?v=4?s=100" width="100px;" alt="aias00"/><br /><sub><b>aias00</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Aias00"><img src="https://avatars.githubusercontent.com/u/25810623?v=4?s=100" width="100px;" alt="aias00"/><br /><sub><b>aias00</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AAias00" title="Bug reports">🐛</a> <a href="#ideas-Aias00" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/westboy"><img src="https://avatars.githubusercontent.com/u/6385565?v=4?s=100" width="100px;" alt="Jin"/><br /><sub><b>Jin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=westboy" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://blog.csdn.net/qq_52397471"><img src="https://avatars.githubusercontent.com/u/77964041?v=4?s=100" width="100px;" alt="YuLuo"/><br /><sub><b>YuLuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Yanshuming1"><img src="https://avatars.githubusercontent.com/u/118667222?v=4?s=100" width="100px;" alt="linDong"/><br /><sub><b>linDong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://blog.csdn.net/qq_52397471"><img src="https://avatars.githubusercontent.com/u/77964041?v=4?s=100" width="100px;" alt="YuLuo"/><br /><sub><b>YuLuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayuluo-yx" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Tests">⚠️</a> <a href="#blog-yuluo-yx" title="Blogposts">📝</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Yanshuming1"><img src="https://avatars.githubusercontent.com/u/118667222?v=4?s=100" width="100px;" alt="linDong"/><br /><sub><b>linDong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AYanshuming1" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lwjxy"><img src="https://avatars.githubusercontent.com/u/52726400?v=4?s=100" width="100px;" alt="lwjxy"/><br /><sub><b>lwjxy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lwjxy" title="Code">💻</a></td>
</tr>
<tr>
@@ -453,17 +453,17 @@ Thanks these wonderful people, welcome to join us:
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ileonli"><img src="https://avatars.githubusercontent.com/u/45332412?v=4?s=100" width="100px;" alt="Leon Li"/><br /><sub><b>Leon Li</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ileonli" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://fnil.net/"><img src="https://avatars.githubusercontent.com/u/14142?v=4?s=100" width="100px;" alt="dennis zhuang"/><br /><sub><b>dennis zhuang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=killme2008" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kerwin612"><img src="https://avatars.githubusercontent.com/u/3371163?v=4?s=100" width="100px;" alt="Kerwin Bryant"/><br /><sub><b>Kerwin Bryant</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Code">💻</a> <a href="#design-kerwin612" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kerwin612"><img src="https://avatars.githubusercontent.com/u/3371163?v=4?s=100" width="100px;" alt="Kerwin Bryant"/><br /><sub><b>Kerwin Bryant</b></sub></a><br /><a href="#design-kerwin612" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Akerwin612" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ShineDevelopment"><img src="https://avatars.githubusercontent.com/u/59306780?v=4?s=100" width="100px;" alt="daixianglong"/><br /><sub><b>daixianglong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ShineDevelopment" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mchgood"><img src="https://avatars.githubusercontent.com/u/38482005?v=4?s=100" width="100px;" alt="mchgood"/><br /><sub><b>mchgood</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mchgood" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pwallk"><img src="https://avatars.githubusercontent.com/u/69385076?v=4?s=100" width="100px;" alt="kangli"/><br /><sub><b>kangli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pwallk"><img src="https://avatars.githubusercontent.com/u/69385076?v=4?s=100" width="100px;" alt="kangli"/><br /><sub><b>kangli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Apwallk" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cdphantom"><img src="https://avatars.githubusercontent.com/u/12674795?v=4?s=100" width="100px;" alt="cdphantom"/><br /><sub><b>cdphantom</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=cdphantom" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/asd108908382"><img src="https://avatars.githubusercontent.com/u/77717999?v=4?s=100" width="100px;" alt="jiawei.guo"/><br /><sub><b>jiawei.guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=asd108908382" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/QBH-insist"><img src="https://avatars.githubusercontent.com/u/39401478?v=4?s=100" width="100px;" alt="QBH-insist"/><br /><sub><b>QBH-insist</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=QBH-insist" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jiangsh-ui"><img src="https://avatars.githubusercontent.com/u/86990361?v=4?s=100" width="100px;" alt="jiangsh"/><br /><sub><b>jiangsh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jiangsh-ui" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/keaifafafa"><img src="https://avatars.githubusercontent.com/u/83876361?v=4?s=100" width="100px;" alt="Keaifa"/><br /><sub><b>Keaifa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=keaifafafa" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/keaifafafa"><img src="https://avatars.githubusercontent.com/u/83876361?v=4?s=100" width="100px;" alt="Keaifa"/><br /><sub><b>Keaifa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=keaifafafa" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Akeaifafafa" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/loong95"><img src="https://avatars.githubusercontent.com/u/16333958?v=4?s=100" width="100px;" alt="Loong"/><br /><sub><b>Loong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=loong95" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ceekay47"><img src="https://avatars.githubusercontent.com/u/104664857?v=4?s=100" width="100px;" alt="Chandrakant Vankayalapati"/><br /><sub><b>Chandrakant Vankayalapati</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ceekay47" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MRgenial"><img src="https://avatars.githubusercontent.com/u/49973336?v=4?s=100" width="100px;" alt="b_mountain"/><br /><sub><b>b_mountain</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MRgenial" title="Code">💻</a></td>
@@ -482,13 +482,13 @@ Thanks these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starryCoder"><img src="https://avatars.githubusercontent.com/u/46510059?v=4?s=100" width="100px;" alt="starryCoder"/><br /><sub><b>starryCoder</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starryCoder" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hasimmollah"><img src="https://avatars.githubusercontent.com/u/32538599?v=4?s=100" width="100px;" alt="hasimmollah"/><br /><sub><b>hasimmollah</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hasimmollah" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ayu-v0"><img src="https://avatars.githubusercontent.com/u/127600988?v=4?s=100" width="100px;" alt="Ayu"/><br /><sub><b>Ayu</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ayu-v0" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Rancho-7"><img src="https://avatars.githubusercontent.com/u/59016860?v=4?s=100" width="100px;" alt="Nick Guo"/><br /><sub><b>Nick Guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Rancho-7"><img src="https://avatars.githubusercontent.com/u/59016860?v=4?s=100" width="100px;" alt="Nick Guo"/><br /><sub><b>Nick Guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ARancho-7" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/doveLin0818"><img src="https://avatars.githubusercontent.com/u/190927907?v=4?s=100" width="100px;" alt="doveLin"/><br /><sub><b>doveLin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=doveLin0818" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://zzrl.cc/"><img src="https://avatars.githubusercontent.com/u/91836599?v=4?s=100" width="100px;" alt="yunfan24"/><br /><sub><b>yunfan24</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://zzrl.cc/"><img src="https://avatars.githubusercontent.com/u/91836599?v=4?s=100" width="100px;" alt="yunfan24"/><br /><sub><b>yunfan24</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayunfan24" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Tests">⚠️</a> <a href="#blog-yunfan24" title="Blogposts">📝</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lctking"><img src="https://avatars.githubusercontent.com/u/168249998?v=4?s=100" width="100px;" alt="nullwli"/><br /><sub><b>nullwli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lctking" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://simonsigre.com/"><img src="https://avatars.githubusercontent.com/u/14932913?v=4?s=100" width="100px;" alt="Simon Sigré"/><br /><sub><b>Simon Sigré</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://simonsigre.com/"><img src="https://avatars.githubusercontent.com/u/14932913?v=4?s=100" width="100px;" alt="Simon Sigré"/><br /><sub><b>Simon Sigré</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.ponfee.cn/"><img src="https://avatars.githubusercontent.com/u/46117331?v=4?s=100" width="100px;" alt="ponfee"/><br /><sub><b>ponfee</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ponfee" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Vedant7789"><img src="https://avatars.githubusercontent.com/u/147625492?v=4?s=100" width="100px;" alt="Vedant7789"/><br /><sub><b>Vedant7789</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Vedant7789" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Craaaaazy77"><img src="https://avatars.githubusercontent.com/u/23025522?v=4?s=100" width="100px;" alt="Craaaaazy77"/><br /><sub><b>Craaaaazy77</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Craaaaazy77" title="Documentation">📖</a></td>
@@ -497,9 +497,9 @@ Thanks these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/helei1030"><img src="https://avatars.githubusercontent.com/u/11839080?v=4?s=100" width="100px;" alt="helei1030"/><br /><sub><b>helei1030</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=helei1030" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Apjfanning" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MonsterChenzhuo"><img src="https://avatars.githubusercontent.com/u/60029759?v=4?s=100" width="100px;" alt="monster"/><br /><sub><b>monster</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MonsterChenzhuo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AMasamiYui" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wanhao23"><img src="https://avatars.githubusercontent.com/u/29560961?v=4?s=100" width="100px;" alt="wanhao"/><br /><sub><b>wanhao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jonasHanhan"><img src="https://avatars.githubusercontent.com/u/130035609?v=4?s=100" width="100px;" alt="jonasHanhan"/><br /><sub><b>jonasHanhan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jonasHanhan" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NikhilMurugesan"><img src="https://avatars.githubusercontent.com/u/49281792?v=4?s=100" width="100px;" alt="NikhilMurugesan"/><br /><sub><b>NikhilMurugesan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=NikhilMurugesan" title="Code">💻</a></td>
@@ -507,18 +507,18 @@ Thanks these wonderful people, welcome to join us:
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Anotbugggg" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Documentation">📖</a> <a href="#ideas-JuJinPark" title="Ideas, Planning, & Feedback">🤔</a> <a href="#blog-JuJinPark" title="Blogposts">📝</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ALL-LIN" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Abigcyy" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PengJingzhao"><img src="https://avatars.githubusercontent.com/u/97368949?v=4?s=100" width="100px;" alt="彭镜肇"/><br /><sub><b>彭镜肇</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PengJingzhao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gagaradio"><img src="https://avatars.githubusercontent.com/u/18532370?v=4?s=100" width="100px;" alt="Walter Jia"/><br /><sub><b>Walter Jia</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gagaradio" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/boyucjz"><img src="https://avatars.githubusercontent.com/u/18730041?v=4?s=100" width="100px;" alt="boyucjz"/><br /><sub><b>boyucjz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=boyucjz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KevinLLF"><img src="https://avatars.githubusercontent.com/u/85452733?v=4?s=100" width="100px;" alt="Jay丿167"/><br /><sub><b>Jay丿167</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KevinLLF" title="Code">💻</a></td>
</tr>
</tbody>
+39
View File
@@ -0,0 +1,39 @@
# 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.
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkakNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTkRVeU5ESTROamd3SGhjTk1qVXdOREl4TVRNME1UQTRXaGNOTXpVd05ERTVNVE0wTVRBNApXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTkRVeU5ESTROamd3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUR2U2SW5hQko4RGRiQVdmZTRNM09yS3ZTR1IvWkROS0JJV2lMejZXckcKYmh4Mm5ncHJFQjhRYThEWDZocGRCVkI1RmN3L1hwTXZjRnNCeFh3MThPNkdvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWRXOEFySENLNVQ2OFdJeG1BUnlTCkJFcHJ6N1F3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnYlhwYXV0c2RlTlJHNm4zRmhBRkkzbncvWUErYTBBdi8KUE9HZzNhYWRCSDBDSUU5NW4vQW5yOXV2Z0tyczVrakpGKzAwci96SWpFdnQxNmhkOGdabFEzb1IKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
server: https://172.29.0.11:6443
name: default
contexts:
- context:
cluster: default
user: default
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrVENDQVRlZ0F3SUJBZ0lJZm5OdzUyby9ybDB3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOelExTWpReU9EWTRNQjRYRFRJMU1EUXlNVEV6TkRFd09Gb1hEVEkyTURReQpNVEV6TkRFd09Gb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJHa3UxWDQvRGNMU1h4cXcKSzczdGhMUFd5Y3JCendrL3ZMbzkzMTZ0dXNXRWE4WnNMdytnYk5wWEVOSFNxQk8zdGcrYUFSdGhWakF3OU5GaApZWXNtaTQyalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCU1B5dEJrYlFaRmtxSEpOaHlHNjZ1K3ZqbjNhakFLQmdncWhrak9QUVFEQWdOSUFEQkYKQWlFQXZQVC9ENGs2S29tUUxaZjRNMTEvd1psT3QyZkpGTGhIY3EyOHMvUUVLcndDSUZVMWVzTGFFTnRWc2xUeAo0QTFtNkt4eEpUbmdNM0M3UXJ5OUs0Z2hzVDA0Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdFkyeHAKWlc1MExXTmhRREUzTkRVeU5ESTROamd3SGhjTk1qVXdOREl4TVRNME1UQTRXaGNOTXpVd05ERTVNVE0wTVRBNApXakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwWlc1MExXTmhRREUzTkRVeU5ESTROamd3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSeENGL2FuNVUvS0xEUk9VUlZxdllWTHFsdWxXaE8rMzJpc1lRU1hlYUgKdnRBWHo3dFE2MkhrazNIdXZudnFmditvQlBKVUlzS3ExTyt6aytLT3JWa2dvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWo4clFaRzBHUlpLaHlUWWNodXVyCnZyNDU5Mm93Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUlnWmxiRkpoZWwzQiszQ2FyZjUrdE5taDhkWkRUOC9yVDAKR01VVXlBNzlWSUVDSVFDSUQ1NkVQbmtOa0xmZExEZXloclBGWks3VWRTc2x2QWZGN2N4czF1MDBxZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUpMOGJrS1FMbEdZWUE0Ny9Lek9CRm5iZnI0NGQ5VndUWXlPT0dJdnNLOXNvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFYVM3VmZqOE53dEpmR3JBcnZlMkVzOWJKeXNIUENUKzh1ajNmWHEyNnhZUnJ4bXd2RDZCcwoybGNRMGRLb0U3ZTJENW9CRzJGV01ERDAwV0ZoaXlhTGpRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
+1
View File
@@ -0,0 +1 @@
K10800b7e6b1f6390067e433bd30dff5f925893b60074f8c36f7725bfdce6a45917::server:ca7449eb0dddd8a4bb6e5ca86e2d0cdd
+37
View File
@@ -0,0 +1,37 @@
{
"detected": true,
"monitor": {
"intervals": 10,
"tags": [],
"app": "kubernetes",
"host": "172.29.0.11",
"name": "Brave_Stingray_55yR"
},
"collector": "",
"params": [
{
"display": true,
"field": "host",
"type": 1,
"paramValue": "172.29.0.11"
},
{
"display": true,
"field": "port",
"type": 0,
"paramValue": 6443
},
{
"display": true,
"field": "authType",
"type": 1,
"paramValue": "Bearer Token"
},
{
"display": true,
"field": "token",
"type": 1,
"paramValue": "#TOKEN#"
}
]
}
+69
View File
@@ -0,0 +1,69 @@
#!api-testing
# 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.
# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-schema.json
name: hertzbeat
api: |
{{default "http://localhost:1157" (env "SERVER")}}
param:
monitorHTTP: "{{randAlpha 6}}"
monitorSiteMap: "{{randAlpha 6}}"
monitorPort: "{{randAlpha 6}}"
monitorSSL: "{{randAlpha 6}}"
monitorPing: "{{randAlpha 6}}"
monitorUDP: "{{randAlpha 6}}"
monitorWebsite: "{{randAlpha 6}}"
monitorFTP: "{{randAlpha 6}}"
tagName: "{{randAlpha 3}}"
items:
- name: login
request:
api: /api/account/auth/form
method: POST
header:
Content-type: application/json
body: |
{
"type": 0,
"identifier": "admin",
"credential": "hertzbeat"
}
expect:
bodyFieldsExpect:
code: "0"
- name: detectMonitor
request:
api: /api/monitor/detect
method: POST
header:
Authorization: Bearer {{.login.data.token}}
Content-type: application/json
bodyFromFile: data/tmp.json
expect:
statusCode: 200
- name: createMonitor
request:
api: /api/monitor
method: POST
header:
Authorization: Bearer {{.login.data.token}}
Content-type: application/json
bodyFromFile: data/tmp.json
expect:
statusCode: 200
+123
View File
@@ -0,0 +1,123 @@
# 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.
version: '3.8'
services:
init:
image: bitnami/kubectl:latest
privileged: true
depends_on:
k3s:
condition: service_healthy
entrypoint: /bin/bash
networks:
k3s_network:
ipv4_address: 172.29.0.10
volumes:
- ./data/config/k3s.yaml:/.kube/config
- ./data:/testcase
command:
- -c
- |
# Wait for k3s to start
until kubectl cluster-info; do sleep 1; done
sleep 5
# Create serviceaccount and clusterrolebinding
kubectl create serviceaccount -n kube-system cluster-admin-sa
kubectl create clusterrolebinding -n kube-system cluster-admin-binding --clusterrole=cluster-admin --serviceaccount=kube-system:cluster-admin-sa
cd /testcase
kubectl create token cluster-admin-sa -n kube-system --duration=24h
echo $(sed "s/#TOKEN#/$(echo $(kubectl create token cluster-admin-sa -n kube-system --duration=24h))/g" create_k8s_monitor_data.json)>tmp.json
# install metrics server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
sleep 30
tail -f /dev/null
k3s:
image: rancher/k3s:v1.31.0-k3s1
container_name: k3s
privileged: true
ports:
- "6443:6443"
- "80:80"
command: server --bind-address 172.29.0.11
networks:
k3s_network:
ipv4_address: 172.29.0.11
healthcheck:
test: [ "CMD-SHELL",
"kubectl cluster-info && echo $(cat /var/lib/rancher/k3s/server/node-token)>node-token.txt" ]
interval: 3s
retries: 30
start_period: 10s
timeout: 10s
volumes:
- ./data/config/k3s.yaml:/etc/rancher/k3s/k3s.yaml
- ./data/config/node-token.txt:/node-token.txt
testing:
image: ghcr.io/linuxsuren/api-testing:v0.0.17
environment:
SERVER: http://hertzbeat:1157
container_name: e2e-testing
volumes:
- ./data/:/work/data/
- ./data/testsuite.yaml:/work/testsuite.yaml
command: atest run -p /work/testsuite.yaml --level debug --thread 3
networks:
k3s_network:
ipv4_address: 172.29.0.12
depends_on:
checker:
condition: service_healthy
k3s:
condition: service_healthy
links:
- hertzbeat
- checker
- k3s
checker:
image: alpine:latest
container_name: e2e-checker
command: sh -c "apk add --update curl && tail -f /dev/null"
healthcheck:
test: [ "CMD-SHELL",
"curl -s -X POST -H \"Content-Type: application/json\" -d '{\"type\": 0,\"identifier\": \"admin\",\"credential\": \"hertzbeat\"}' http://hertzbeat:1157/api/account/auth/form | grep -q token"
]
interval: 3s
timeout: 60s
retries: 10
start_period: 10s
networks:
k3s_network:
ipv4_address: 172.29.0.13
depends_on:
- init
hertzbeat:
image: apache/hertzbeat:test
container_name: e2e-hertzbeat
volumes:
- ./logs/:/opt/hertzbeat/logs/
ports:
- "1157:1157"
networks:
k3s_network:
ipv4_address: 172.29.0.14
networks:
k3s_network:
driver: bridge
ipam:
config:
- subnet: 172.29.0.0/16
@@ -220,11 +220,11 @@ public class RealTimeAlertCalculator {
fingerPrints.putAll(commonFingerPrints);
for (int index = 0; index < valueRow.getColumnsList().size(); index++) {
String valueStr = valueRow.getColumns(index);
final CollectRep.Field field = fields.get(index);
if (CommonConstants.NULL_VALUE.equals(valueStr)) {
fieldValueMap.put(field.getName(), null);
continue;
}
final CollectRep.Field field = fields.get(index);
final int fieldType = field.getType();
if (fieldType == CommonConstants.TYPE_NUMBER) {
@@ -0,0 +1,133 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* uptime-kuma alert content entity
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UptimeKumaExternAlert {
private Heartbeat heartbeat;
private Monitor monitor;
private String msg;
/**
* Monitor information
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Heartbeat {
@JsonProperty("monitorID")
private int monitorId;
private int status;
private String time;
private String msg;
private boolean important;
private int duration;
private String timezone;
private String timezoneOffset;
private String localDateTime;
}
/**
* Monitor information
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Monitor {
private int id;
private String name;
private String description;
private String pathName;
private String parent;
@JsonProperty("childrenIDs")
private List<String> childrenIds;
private String url;
private String method;
private String hostname;
private String port;
@JsonProperty("maxretries")
private int maxRetries;
private int weight;
private boolean active;
private boolean forceInactive;
private String type;
private int timeout;
private int interval;
private int retryInterval;
private int resendInterval;
private String keyword;
private boolean invertKeyword;
private boolean expiryNotification;
private boolean ignoreTls;
private boolean upsideDown;
private int packetSize;
@JsonProperty("maxredirects")
private int maxRedirects;
@JsonProperty("accepted_statuscodes")
private List<String> acceptedStatusCodes;
private String dnsResolveType;
private String dnsResolveServer;
private String dnsLastResult;
private String dockerContainer;
private String dockerHost;
private String proxyId;
private Map<String, Object> notificationIDList;
private List<String> tags;
private boolean maintenance;
private String mqttTopic;
private String mqttSuccessMessage;
private String databaseQuery;
private String authMethod;
private String grpcUrl;
private String grpcProtobuf;
private String grpcMethod;
private String grpcServiceName;
private boolean grpcEnableTls;
private String radiusCalledStationId;
private String radiusCallingStationId;
private String game;
private boolean gamedigGivenPortOnly;
private String httpBodyEncoding;
private String jsonPath;
private String expectedValue;
private String kafkaProducerTopic;
private List<String> kafkaProducerBrokers;
private boolean kafkaProducerSsl;
private boolean kafkaProducerAllowAutoTopicCreation;
private String kafkaProducerMessage;
private String screenshot;
private boolean includeSensitiveData;
}
}
@@ -52,86 +52,84 @@ final class DbAlertStoreHandlerImpl implements AlertStoreHandler {
log.error("The Group Alerts is empty, ignore store");
return groupAlert;
}
// 1. Find existing alert group
GroupAlert existGroupAlert = groupAlertDao.findByGroupKey(groupAlert.getGroupKey());
// 2. Process individual alerts
// Process individual alerts
Set<String> alertFingerprints = new HashSet<>(8);
List<SingleAlert> originalAlerts = groupAlert.getAlerts();
List<SingleAlert> newAlerts = new ArrayList<>();
for (SingleAlert singleAlert : originalAlerts) {
SingleAlert existAlert = singleAlertDao.findByFingerprint(singleAlert.getFingerprint());
if (existAlert != null) {
// Update the existing alert with the ID and creation time from the database
singleAlert.setId(existAlert.getId());
singleAlert.setGmtCreate(existAlert.getGmtCreate());
// Status transition logic
if (CommonConstants.ALERT_STATUS_FIRING.equals(singleAlert.getStatus())) {
// If the alert is firing and the existing alert is not resolved, update the start time and trigger times
if (!CommonConstants.ALERT_STATUS_RESOLVED.equals(existAlert.getStatus())) {
synchronized (singleAlert.getFingerprint().intern()) {
SingleAlert existAlert = singleAlertDao.findByFingerprint(singleAlert.getFingerprint());
if (existAlert != null) {
// Update the existing alert with the ID and creation time from the database
singleAlert.setId(existAlert.getId());
singleAlert.setGmtCreate(existAlert.getGmtCreate());
// Status transition logic
if (CommonConstants.ALERT_STATUS_FIRING.equals(singleAlert.getStatus())) {
// If the alert is firing and the existing alert is not resolved, update the start time and trigger times
if (!CommonConstants.ALERT_STATUS_RESOLVED.equals(existAlert.getStatus())) {
singleAlert.setStartAt(existAlert.getStartAt());
int triggerTimes = Optional.ofNullable(existAlert.getTriggerTimes()).orElse(1)
+ Optional.ofNullable(singleAlert.getTriggerTimes()).orElse(1);
singleAlert.setTriggerTimes(triggerTimes);
}
} else if (CommonConstants.ALERT_STATUS_RESOLVED.equals(singleAlert.getStatus())) {
// If the alert is resolved, set the end time (if not already set) and copy other fields from the existing alert
if (singleAlert.getEndAt() == null) {
singleAlert.setEndAt(System.currentTimeMillis());
}
singleAlert.setStartAt(existAlert.getStartAt());
int triggerTimes = Optional.ofNullable(existAlert.getTriggerTimes()).orElse(1)
+ Optional.ofNullable(singleAlert.getTriggerTimes()).orElse(1);
singleAlert.setTriggerTimes(triggerTimes);
singleAlert.setActiveAt(existAlert.getActiveAt());
singleAlert.setTriggerTimes(existAlert.getTriggerTimes());
}
} else if (CommonConstants.ALERT_STATUS_RESOLVED.equals(singleAlert.getStatus())) {
// If the alert is resolved, set the end time (if not already set) and copy other fields from the existing alert
if (singleAlert.getEndAt() == null) {
singleAlert.setEndAt(System.currentTimeMillis());
}
singleAlert.setStartAt(existAlert.getStartAt());
singleAlert.setActiveAt(existAlert.getActiveAt());
singleAlert.setTriggerTimes(existAlert.getTriggerTimes());
}
SingleAlert savedSingleAlert = singleAlertDao.save(singleAlert);
newAlerts.add(savedSingleAlert);
alertFingerprints.add(savedSingleAlert.getFingerprint());
}
SingleAlert savedSingleAlert = singleAlertDao.save(singleAlert);
newAlerts.add(savedSingleAlert);
alertFingerprints.add(savedSingleAlert.getFingerprint());
}
groupAlert.setAlerts(newAlerts);
// 3. Process resolved alerts
if (existGroupAlert != null) {
List<String> existFingerprints = existGroupAlert.getAlertFingerprints();
if (existFingerprints != null) {
alertFingerprints.addAll(existFingerprints);
}
// Merge group information
groupAlert.setId(existGroupAlert.getId());
groupAlert.setGmtCreate(existGroupAlert.getGmtCreate());
// Merge other historical information to preserve
Map<String, String> existCommonLabels = existGroupAlert.getCommonLabels();
if (existCommonLabels != null) {
Map<String, String> commonLabels = groupAlert.getCommonLabels();
if (commonLabels != null) {
// filter common label in commonLabels and existCommonLabels
commonLabels = commonLabels.entrySet().stream()
.filter(entry -> existCommonLabels.containsKey(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
groupAlert.setCommonLabels(commonLabels);
// Find existing alert group
synchronized (groupAlert.getGroupKey().intern()) {
GroupAlert existGroupAlert = groupAlertDao.findByGroupKey(groupAlert.getGroupKey());
// Process resolved alerts
if (existGroupAlert != null) {
List<String> existFingerprints = existGroupAlert.getAlertFingerprints();
if (existFingerprints != null) {
alertFingerprints.addAll(existFingerprints);
}
}
Map<String, String> existCommonAnnotations = existGroupAlert.getCommonAnnotations();
if (existCommonAnnotations != null) {
Map<String, String> commonAnnotations = groupAlert.getCommonAnnotations();
if (commonAnnotations != null) {
// filter common annotation in commonAnnotations and existCommonAnnotations
commonAnnotations = commonAnnotations.entrySet().stream()
.filter(entry -> existCommonAnnotations.containsKey(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
groupAlert.setCommonAnnotations(commonAnnotations);
// Merge group information
groupAlert.setId(existGroupAlert.getId());
groupAlert.setGmtCreate(existGroupAlert.getGmtCreate());
// Merge other historical information to preserve
Map<String, String> existCommonLabels = existGroupAlert.getCommonLabels();
if (existCommonLabels != null) {
Map<String, String> commonLabels = groupAlert.getCommonLabels();
if (commonLabels != null) {
// filter common label in commonLabels and existCommonLabels
commonLabels = commonLabels.entrySet().stream()
.filter(entry -> existCommonLabels.containsKey(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
groupAlert.setCommonLabels(commonLabels);
}
}
Map<String, String> existCommonAnnotations = existGroupAlert.getCommonAnnotations();
if (existCommonAnnotations != null) {
Map<String, String> commonAnnotations = groupAlert.getCommonAnnotations();
if (commonAnnotations != null) {
// filter common annotation in commonAnnotations and existCommonAnnotations
commonAnnotations = commonAnnotations.entrySet().stream()
.filter(entry -> existCommonAnnotations.containsKey(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
groupAlert.setCommonAnnotations(commonAnnotations);
}
}
}
// Save alert group
groupAlert.setAlertFingerprints(alertFingerprints.stream().toList());
GroupAlert savedGroupAlert = groupAlertDao.save(groupAlert);
savedGroupAlert.setAlerts(groupAlert.getAlerts());
return savedGroupAlert;
}
// 4. Save alert group
groupAlert.setAlertFingerprints(alertFingerprints.stream().toList());
GroupAlert savedGroupAlert = groupAlertDao.save(groupAlert);
savedGroupAlert.setAlerts(groupAlert.getAlerts());
return savedGroupAlert;
}
}
@@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dto.UptimeKumaExternAlert;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.ExternAlertService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
/**
* uptime-kuma external alarm service impl
*/
@Slf4j
@Service
public class UptimeKumaExternAlertServiceImpl implements ExternAlertService {
@Autowired
private AlarmCommonReduce alarmCommonReduce;
@Override
public void addExternAlert(String content) {
UptimeKumaExternAlert alert = JsonUtil.fromJson(content, UptimeKumaExternAlert.class);
if (alert == null) {
log.warn("parse extern alert content failed! content: {}", content);
return;
}
SingleAlert singleAlert = new UptimeKumaAlertConverter().convert(alert);
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
}
/**
* Converter: UptimeKuma alert to SingleAlert
*/
public static class UptimeKumaAlertConverter {
/**
* Convert UptimeKuma alert to SingleAlert
*/
public SingleAlert convert(UptimeKumaExternAlert alert) {
// build basic info
SingleAlert.SingleAlertBuilder builder = SingleAlert.builder()
.status(convertStatus(alert.getHeartbeat().getStatus()))
.startAt(parseTime(alert.getHeartbeat().getTime()))
.activeAt(parseTime(alert.getHeartbeat().getTime()))
.triggerTimes(1);
// build labels
Map<String, String> labels = new HashMap<>();
labels.put("__source__", "uptime_kuma");
labels.put("monitor_id", String.valueOf(alert.getMonitor().getId()));
labels.put("monitor_name", alert.getMonitor().getName());
// build annotations
Map<String, String> annotations = new HashMap<>();
annotations.put("description", alert.getMonitor().getDescription());
annotations.put("message", alert.getHeartbeat().getMsg());
annotations.put("important", String.valueOf(alert.getHeartbeat().isImportant()));
return builder
.labels(labels)
.annotations(annotations)
.content(buildContent(alert))
.build();
}
private String buildContent(UptimeKumaExternAlert alert) {
return String.format("Monitor [%s] %s: %s",
alert.getMonitor().getName(),
alert.getMonitor().getDescription(),
alert.getHeartbeat().getMsg());
}
private String convertStatus(int status) {
// uptime kuma status: 1-up, 0-down, 2-pending
return status == 1 ? CommonConstants.ALERT_STATUS_RESOLVED : CommonConstants.ALERT_STATUS_FIRING;
}
private Long parseTime(String timeStr) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(timeStr).getTime();
} catch (ParseException e) {
log.error("Failed to parse time: {}", timeStr);
throw new IllegalArgumentException("Failed to parse time: " + timeStr, e);
}
}
}
@Override
public String supportSource() {
return "uptime-kuma";
}
}
@@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.ExternAlertService;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* zabbix external alarm service impl
*/
@Slf4j
@Service
public class ZabbixExternAlertServiceImpl implements ExternAlertService {
@Autowired
private AlarmCommonReduce alarmCommonReduce;
@Override
public void addExternAlert(String content) {
SingleAlert alert = JsonUtil.fromJson(content, SingleAlert.class);
if (alert == null) {
log.warn("parse extern alert content failed! content: {}", content);
return;
}
alarmCommonReduce.reduceAndSendAlarm(alert);
}
@Override
public String supportSource() {
return "zabbix";
}
}
@@ -21,6 +21,7 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
/**
* Alarm template keyword matching replacement engine tool
@@ -38,11 +39,11 @@ public final class AlertTemplateUtil {
private static final Pattern PATTERN = Pattern.compile("\\$\\{(\\w+)\\}");
public static String render(String template, Map<String, Object> replaceData) {
if (template == null) {
return null;
if (!StringUtils.hasText(template)) {
return template;
}
if (replaceData == null) {
log.warn("The replaceData is null.");
log.warn("The render replace data is null.");
return template;
}
try {
@@ -50,7 +51,7 @@ public final class AlertTemplateUtil {
StringBuilder builder = new StringBuilder();
while (matcher.find()) {
Object objectValue = replaceData.getOrDefault(matcher.group(1), "NullValue");
String value = objectValue.toString();
String value = objectValue != null ? objectValue.toString() : "NullValue";
matcher.appendReplacement(builder, Matcher.quoteReplacement(value));
}
matcher.appendTail(builder);
@@ -0,0 +1,154 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.calculate;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.DataSourceService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
/**
* Test case for {@link PeriodicAlertCalculator}
*/
@ExtendWith(MockitoExtension.class)
class PeriodicAlertCalculatorTest {
@Mock
private DataSourceService dataSourceService;
@Mock
private AlarmCommonReduce alarmCommonReduce;
@Mock
private AlarmCacheManager alarmCacheManager;
@InjectMocks
private PeriodicAlertCalculator periodicAlertCalculator;
private AlertDefine rule;
@BeforeEach
void setUp() {
rule = AlertDefine.builder()
.id(1L)
.name("high_cpu_usage")
.type("periodic")
.expr("usage>90")
.times(1)
.labels(Map.of("team", "test-team", "priority", "P1"))
.annotations(Map.of("summary", "High CPU usage detected"))
.template("High CPU usage (${__value__}%)")
.datasource("PROMETHEUS")
.enable(true)
.period(300)
.build();
}
@Test
void testTriggerAlertWhenMatchThreshold() {
reset(alarmCacheManager);
Map<String, Object> result = new HashMap<>();
result.put("__value__", 95.0); // Non-null, matched with threshold
result.put("__timestamp__", System.currentTimeMillis());
when(dataSourceService.calculate(anyString(), anyString())).thenReturn(List.of(result));
when(alarmCacheManager.getPending(anyString())).thenReturn(null);
periodicAlertCalculator.calculate(rule);
// Verify that putFiring is called
ArgumentCaptor<String> idCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<SingleAlert> alertCaptor = ArgumentCaptor.forClass(SingleAlert.class);
verify(alarmCacheManager).putFiring(idCaptor.capture(), alertCaptor.capture());
// Assertion alarm status and content
SingleAlert alert = alertCaptor.getValue();
assertAll(() -> assertEquals(CommonConstants.ALERT_STATUS_FIRING, alert.getStatus()),
() -> assertEquals("High CPU usage (95.0%)", alert.getContent()));
}
@Test
void testNoTriggerAlertWhenNotMatchThreshold() {
reset(alarmCacheManager);
Map<String, Object> result = new HashMap<>();
result.put("__value__", null); // null, not matched with threshold
result.put("__timestamp__", System.currentTimeMillis());
when(dataSourceService.calculate(anyString(), anyString())).thenReturn(List.of(result));
periodicAlertCalculator.calculate(rule);
verify(alarmCacheManager, times(0)).putFiring(any(), any());
}
@Test
void testResolveFiringAlert() {
reset(alarmCacheManager);
Map<String, Object> result = new HashMap<>();
result.put("__value__", null); // null, not matched with threshold
result.put("__timestamp__", System.currentTimeMillis());
SingleAlert pendingAlert = SingleAlert.builder()
.status(CommonConstants.ALERT_STATUS_FIRING)
.triggerTimes(2).startAt(System.currentTimeMillis() - 60000)
.activeAt(System.currentTimeMillis() - 30000)
.build();
when(alarmCacheManager.removeFiring(anyString())).thenReturn(pendingAlert);
when(dataSourceService.calculate(anyString(), anyString())).thenReturn(List.of(result));
periodicAlertCalculator.calculate(rule);
ArgumentCaptor<SingleAlert> resolvedCaptor = ArgumentCaptor.forClass(SingleAlert.class);
verify(alarmCommonReduce).reduceAndSendAlarm(resolvedCaptor.capture());
SingleAlert resolvedAlert = resolvedCaptor.getValue();
assertAll(() -> assertEquals(CommonConstants.ALERT_STATUS_RESOLVED, resolvedAlert.getStatus()),
() -> assertNotNull(resolvedAlert.getEndAt()),
() -> assertTrue(resolvedAlert.getEndAt() > resolvedAlert.getStartAt()));
}
@ParameterizedTest
@NullAndEmptySource
void testSkipWhenDataSourceResultInvalid(List<Map<String, Object>> results) {
when(dataSourceService.calculate(anyString(), anyString())).thenReturn(results);
periodicAlertCalculator.calculate(rule);
verifyNoInteractions(alarmCacheManager);
}
@Test
void testHandleDisabledRule() {
rule.setEnable(false);
periodicAlertCalculator.calculate(rule);
verifyNoInteractions(dataSourceService);
}
}
@@ -57,6 +57,7 @@ class DbAlertStoreHandlerImplTest {
public void setUp() {
groupAlert = new GroupAlert();
singleAlert = new SingleAlert();
singleAlert.setFingerprint("test-fingerprint");
List<SingleAlert> alerts = new ArrayList<>();
alerts.add(singleAlert);
groupAlert.setAlerts(alerts);
@@ -77,11 +78,13 @@ class DbAlertStoreHandlerImplTest {
when(groupAlertDao.findByGroupKey(groupKey)).thenReturn(null);
SingleAlert savedSingleAlert = new SingleAlert();
savedSingleAlert.setFingerprint("test-finger");
when(singleAlertDao.save(any(SingleAlert.class))).thenReturn(savedSingleAlert);
GroupAlert savedGroupAlert = new GroupAlert();
savedGroupAlert.setGroupKey(groupKey);
when(groupAlertDao.save(any(GroupAlert.class))).thenReturn(savedGroupAlert);
dbAlertStoreHandler.store(groupAlert);
verify(singleAlertDao).save(any(SingleAlert.class));
@@ -383,6 +383,8 @@ public class JdbcCommonCollect extends AbstractCollect {
case "oracle" -> "jdbc:oracle:thin:@" + host + ":" + port
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
case "dm" -> "jdbc:dm://" + host + ":" + port;
case "testcontainers" -> "jdbc:tc:" + host + ":" + port
+ ":///" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase()) + "?user=root&password=root";
default -> throw new IllegalArgumentException("Not support database platform: " + jdbcProtocol.getPlatform());
};
}
@@ -52,7 +52,7 @@ public class HttpSdCollectImpl extends AbstractCollect {
@Override
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
List<ConnectionConfig> configList = Lists.newArrayList();
HttpUriRequest request = RequestBuilder.get().setUri(metrics.getSdProtocol().getSdSource()).build();
HttpUriRequest request = RequestBuilder.get().setUri(metrics.getHttp_sd().getUrl()).build();
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request)) {
int statusCode = response.getStatusLine().getStatusCode();
@@ -281,7 +281,8 @@ public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatc
}
// If it is an asynchronous periodic cyclic task, directly response the collected data
if (job.isSd()) {
commonDataQueue.sendServiceDiscoveryData(metricsData);
CollectRep.MetricsData sdMetricsData = CollectRep.MetricsData.newBuilder(metricsData).build();
commonDataQueue.sendServiceDiscoveryData(sdMetricsData);
}
commonDataQueue.sendMetricsData(metricsData);
} else {
@@ -126,7 +126,7 @@ public interface DispatchConstants {
/**
* protocol http sd
*/
String PROTOCOL_HTTP_SD = "httpsd";
String PROTOCOL_HTTP_SD = "http_sd";
/**
* protocol redfish
*/
@@ -17,10 +17,10 @@
package org.apache.hertzbeat.collector.dispatch.timer;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.extern.slf4j.Slf4j;
@@ -87,7 +87,7 @@ public class TimerDispatcher implements TimerDispatch, DisposableBean {
for (Metrics metric : addJob.getMetrics()) {
metric.setInterval(0L);
}
addJob.setIntervals(new LinkedList<>(List.of(0L)));
addJob.setIntervals(new ConcurrentLinkedDeque<>(List.of(0L)));
Timeout timeout = wheelTimer.newTimeout(timerJob, addJob.getInterval(), TimeUnit.SECONDS);
currentTempTaskMap.put(addJob.getId(), timeout);
eventListeners.put(addJob.getId(), eventListener);
@@ -73,14 +73,14 @@ public interface CommonConstants {
byte MONITOR_DOWN_CODE = 0x02;
/**
* Monitor bind type, 0: monitor that auto-created by sd
* scrape type static
*/
byte MONITOR_BIND_TYPE_SD_SUB_MONITOR = 0x00;
String SCRAPE_STATIC = "static";
/**
* Monitor bind type, 1: the main monitor of sd
* scrape type
*/
byte MONITOR_BIND_TYPE_SD_MAIN_MONITOR = 0x01;
String SCRAPE_HTTP_SD = "http_sd";
/**
* label key: instance
@@ -192,11 +192,6 @@ public interface CommonConstants {
*/
String PROM_VALUE = "value";
/**
* Monitor total availability metrics
*/
String AVAILABILITY = "availability";
/**
* Parameter Type Number
*/
@@ -222,104 +217,6 @@ public interface CommonConstants {
*/
byte PARAM_TYPE_ARRAY = 4;
/**
* Authentication type Account password
*/
byte AUTH_TYPE_PASSWORD = 1;
/**
* Authentication type GitHub three-party login
*/
byte AUTH_TYPE_GITHUB = 2;
/**
* Authentication type WeChat three-party login
*/
byte AUTH_TYPE_WEIXIN = 3;
/**
* Authentication type GITEE three-party login
*/
byte AUTH_TYPE_GITEE = 5;
/**
* inner default label key __instance__
*/
String LABEL_INNER_KEY_INSTANCE = "__instance__";
/**
* Inside the tag: monitorId Monitor task ID
*/
String TAG_MONITOR_ID = "monitorId";
/**
* Inside the tag: monitorName Task name
*/
String TAG_MONITOR_NAME = "monitorName";
/**
* Inside the tag: indicate that this monitor is auto-created by main monitor
*/
String TAG_AUTO_CREATED = "autoCreated";
/**
* Inside the tag: indicate that this monitor is a main monitor which provides service discovery
*/
String TAG_SD_MAIN_MONITOR = "sdMainMonitor";
/**
* Inside the tag: monitorHost Task host
*/
String TAG_MONITOR_HOST = "monitorHost";
/**
* Inside the tag: policyId Alarm threshold rule ID
*/
String TAG_THRESHOLD_ID = "thresholdId";
/**
* Inside the tag: app Type of monitoring
*/
String TAG_MONITOR_APP = "app";
/**
* Inside the tag: metrics
*/
String TAG_METRICS = "metrics";
/**
* Inside the tag: metric
*/
String TAG_METRIC = "metric";
/**
* Inside the tag: code
*/
String TAG_CODE = "code";
/**
* Tag Type: Auto-generated
*/
byte TAG_TYPE_AUTO_GENERATE = 0;
/**
* Tag Type: User-generated
*/
byte TAG_TYPE_USER_GENERATE = 1;
/**
* Tag Type: System preset
*/
byte TAG_TYPE_SYSTEM_PRESET = 2;
/**
* notice_period type Type field, daily type
*/
int NOTICE_PERIOD_DAILY = 0;
/**
* key is receiver.id, value is noticePeriod cache key prefix
*/
String RECEIVER_NOTICE_PERIOD_CACHE_PREFIX = "receiver_notice_period:";
/**
* cache key notice_rule
*/
@@ -86,7 +86,7 @@ public class GroupAlert {
@Schema(title = "Alert Fingerprints", example = "[\"dxsdfdsf\"]")
@Convert(converter = JsonStringListAttributeConverter.class)
@Column(length = 2048)
@Column(length = 8192)
private List<String> alertFingerprints;
@Schema(title = "The creator of this record", example = "tom")
@@ -28,6 +28,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -114,7 +115,7 @@ public class Job {
/**
* Refresh time list for one cycle of the job
*/
private LinkedList<Long> intervals;
private ConcurrentLinkedDeque<Long> intervals;
/**
* Whether it is a recurring periodic task true is yes, false is no
*/
@@ -328,7 +329,7 @@ public class Job {
* @param metricsIntervals A unique list composed of intervals for all metrics
* Generate a list of refresh intervals for metric collection
*/
public void generateMetricsIntervals(List<Long> metricsIntervals) {
public synchronized void generateMetricsIntervals(List<Long> metricsIntervals) {
// 1. To find the least common multiple (LCM) of all metric refresh intervals
long lcm = lcm(metricsIntervals);
List<Long> refreshTimes = new LinkedList<>();
@@ -348,14 +349,16 @@ public class Job {
for (int i = 1; i < refreshTimes.size(); i++) {
intervals.add(refreshTimes.get(i) - refreshTimes.get(i - 1));
}
setIntervals(intervals);
setIntervals(new ConcurrentLinkedDeque<>(intervals));
}
public long getInterval() {
if (!CollectionUtils.isEmpty(getIntervals())) {
long interval = getIntervals().remove();
getIntervals().add(interval);
return interval;
public synchronized long getInterval() {
if (!CollectionUtils.isEmpty(this.intervals)) {
Long interval = this.intervals.removeFirst();
if (interval != null) {
this.intervals.addLast(interval);
return interval;
}
}
return getDefaultInterval();
}
@@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.job.protocol.DnsProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.FtpProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.HttpSdProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.ModbusProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.RegistryProtocol;
@@ -63,7 +64,6 @@ import org.apache.hertzbeat.common.entity.job.protocol.TelnetProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.UdpProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.WebsocketProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryProtocol;
/**
* Details of the monitoring metrics collected
@@ -74,6 +74,7 @@ import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryProtocol;
@NoArgsConstructor
@Builder
@Slf4j
@SuppressWarnings("checkstyle:MemberName")
public class Metrics {
/**
@@ -260,9 +261,9 @@ public class Metrics {
*/
private KafkaProtocol kclient;
/**
* Collect sd data protocol
* http sd protocol
*/
private ServiceDiscoveryProtocol sdProtocol;
private HttpSdProtocol http_sd;
/**
* Monitoring configuration information using the public plc protocol
*/
@@ -21,7 +21,8 @@ package org.apache.hertzbeat.common.entity.job.protocol;
* Define common field method for each protocol in {@link org.apache.hertzbeat.common.entity.job.Metrics}
*/
public interface CommonRequestProtocol {
void setHost(String host);
void setPort(String port);
}
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.manager;
package org.apache.hertzbeat.common.entity.job.protocol;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -23,15 +23,13 @@ import lombok.Data;
import lombok.NoArgsConstructor;
/**
* sd monitor param
* Dns protocol
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SdMonitorParam {
private String detectedHost;
private String detectedPort;
private Param sdParam;
private Long bizId;
}
public class HttpSdProtocol implements Protocol {
private String url;
}
@@ -77,6 +77,10 @@ public class Monitor {
@Size(max = 100)
private String app;
@Schema(title = "Scrape type: static | http_sd", example = "static", accessMode = READ_WRITE)
@Size(max = 100)
private String scrape;
@Schema(title = "peer host: ipv4, ipv6, domain name", example = "192.167.25.11", accessMode = READ_WRITE)
@Size(max = 100)
@HostValid
@@ -17,7 +17,6 @@
package org.apache.hertzbeat.common.entity.manager;
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@@ -27,8 +26,6 @@ import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -60,18 +57,16 @@ public class MonitorBind {
@Schema(title = "primary id", example = "23")
private Long id;
@Schema(title = "collector name", example = "87432674384")
@Schema(title = "key string: ip:port")
private String keyStr;
@Schema(title = "connect bind id", example = "87432674384")
private Long bizId;
@Schema(title = "monitor ID", example = "87432674336")
@Column(name = "monitor_id")
private Long monitorId;
@Schema(title = "Bind type 0: sd", accessMode = READ_WRITE)
@Min(0)
@Max(3)
private byte type;
@Schema(title = "The creator of this record", example = "tom")
@CreatedBy
private String creator;
@@ -87,4 +82,4 @@ public class MonitorBind {
@Schema(title = "Record the latest modification time (timestamp in milliseconds)")
@LastModifiedDate
private LocalDateTime gmtUpdate;
}
}
@@ -454,8 +454,9 @@ public final class CollectRep {
return new MetricsData(new ArrowTable(root));
} catch (Exception e1) {
log.error(e1.getMessage(), e1);
root.close();
throw e1;
} finally {
root.close();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
@@ -1,67 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.sd;
import java.util.Arrays;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* service discovery common field
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ServiceDiscoveryProtocol {
private Long id;
private List<Long> jobIdList;
/**
* sd type
*/
private Type type;
/**
* Target sd sources by which collector updates SDCache.
*/
private String sdSource;
/**
* sd Type
*/
public enum Type {
HTTP_SD("httpsd"),
;
private final String protocolName;
Type(String protocolName) {
this.protocolName = protocolName;
}
public static Type getType(String str) {
return Arrays.stream(Type.values()).filter(t -> t.toString().equalsIgnoreCase(str)).findFirst().orElse(null);
}
public String getProtocolName() {
return protocolName;
}
}
}
@@ -20,6 +20,7 @@ package org.apache.hertzbeat.common.support.valid;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.apache.hertzbeat.common.util.IpDomainUtil;
import org.springframework.util.StringUtils;
/**
* Host Param Validator
@@ -33,10 +34,13 @@ public class HostParamValidator implements ConstraintValidator<HostValid, String
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value != null && value.toLowerCase().contains(HTTP)){
if (!StringUtils.hasText(value)) {
return true;
}
if (value.toLowerCase().contains(HTTP)){
value = value.replaceFirst(PATTERN_HTTP, BLANK);
}
if (value != null && value.toLowerCase().contains(HTTPS)){
if (value.toLowerCase().contains(HTTPS)){
value = value.replaceFirst(PATTERN_HTTPS, BLANK);
}
@@ -64,6 +64,7 @@ public final class ArrowUtil {
writer.writeBatch();
writer.end();
writer.close();
root.close();
}
return out.toByteArray();
} catch (IOException e) {
@@ -1,151 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.common.util;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.job.Configmap;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.manager.MonitorBind;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.common.entity.manager.SdMonitorParam;
import org.apache.hertzbeat.common.entity.manager.Label;
import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryProtocol;
/**
* Operator for service discovery
*/
public class SdMonitorOperator {
public static Optional<Param> getSdParam(List<Param> params) {
return params.stream()
.filter(param -> StringUtils.isNoneBlank(param.getField(), param.getParamValue()))
.filter(param -> Objects.nonNull(ServiceDiscoveryProtocol.Type.getType(param.getField())))
.findFirst();
}
public static List<Param> removeSdParam(List<Param> params) {
return params.stream()
.filter(param -> Objects.isNull(ServiceDiscoveryProtocol.Type.getType(param.getField())))
.collect(Collectors.toList());
}
public static List<Param> cloneParamList(List<Param> params) {
return params.stream()
.map(Param::clone)
.peek(param -> param.setId(null))
.collect(Collectors.toList());
}
public static MonitorBind buildSdSubMonitorBind(SdMonitorParam sdMonitorParam, long monitorId, Map<String, String> labels) {
if (Objects.isNull(sdMonitorParam.getBizId())) {
return null;
}
labels.put(CommonConstants.TAG_AUTO_CREATED, String.valueOf(sdMonitorParam.getBizId()));
return MonitorBind.builder()
.id(SnowFlakeIdGenerator.generateId())
.bizId(sdMonitorParam.getBizId())
.monitorId(monitorId)
.type(CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR)
.build();
}
public static MonitorBind buildSdMainMonitorBind(SdMonitorParam sdMonitorParam, long monitorId) {
if (Objects.isNull(sdMonitorParam.getSdParam())) {
return null;
}
return MonitorBind.builder()
.id(SnowFlakeIdGenerator.generateId())
.bizId(sdMonitorParam.getBizId())
.monitorId(monitorId)
.type(CommonConstants.MONITOR_BIND_TYPE_SD_MAIN_MONITOR)
.build();
}
public static Job constructSdJobAndTag(SdMonitorParam sdMonitorParam, Map<String, String> labels, Job appDefine) {
if (Objects.isNull(sdMonitorParam.getSdParam())) {
return appDefine;
}
labels.put(CommonConstants.TAG_SD_MAIN_MONITOR, ServiceDiscoveryProtocol.Type.getType(sdMonitorParam.getSdParam().getField()).name());
return constructSdJob(appDefine, sdMonitorParam.getSdParam());
}
public static Job constructSdJob(Job appDefine, Param sdParam) {
final Job sdJob = appDefine.clone();
final ServiceDiscoveryProtocol.Type sdType = ServiceDiscoveryProtocol.Type.getType(sdParam.getField());
List<Metrics> metricsList = Lists.newArrayList();
Map<String, String> i18n = Maps.newHashMap();
i18n.put("zh-CN", "监控目标");
i18n.put("en-US", "Monitor Target");
List<Metrics.Field> fields = Lists.newArrayList();
fields.add(Metrics.Field.builder()
.field("host")
.type(CommonConstants.TYPE_STRING)
.i18n(constructSdFieldI18n("主机", "host"))
.build());
fields.add(Metrics.Field.builder()
.field("port")
.type(CommonConstants.TYPE_STRING)
.i18n(constructSdFieldI18n("端口", "port"))
.build());
metricsList.add(Metrics.builder()
.name("target")
.fields(fields)
.i18n(i18n)
.protocol(sdType.getProtocolName())
.sdProtocol(ServiceDiscoveryProtocol.builder()
.sdSource(sdParam.getParamValue())
.type(sdType)
.build())
.build());
sdJob.setSd(Boolean.TRUE);
sdJob.setMetrics(metricsList);
sdJob.setConfigmap(Lists.newArrayList(new Configmap(sdParam.getField(), sdParam.getParamValue(), sdParam.getType())));
return sdJob;
}
public static List<Label> addMainMonitorTag(List<Label> tagList, Param sdParam) {
tagList = tagList.stream()
.filter(tag -> !StringUtils.equals(tag.getName(), CommonConstants.TAG_SD_MAIN_MONITOR))
.filter(tag -> !StringUtils.equals(tag.getName(), CommonConstants.TAG_AUTO_CREATED))
.collect(Collectors.toList());
tagList.add(Label.builder().name(CommonConstants.TAG_SD_MAIN_MONITOR)
.tagValue(ServiceDiscoveryProtocol.Type.getType(sdParam.getField()).name())
.type(CommonConstants.TAG_TYPE_AUTO_GENERATE)
.build());
return tagList;
}
private static Map<String, String> constructSdFieldI18n(String zh, String en) {
Map<String, String> i18n = Maps.newHashMap();
i18n.put("zh-CN", zh);
i18n.put("en-US", en);
return i18n;
}
}
@@ -53,13 +53,13 @@ class HostParamValidatorTest {
@Test
public void testIsValid() {
boolean result = hostParamValidator.isValid(null, context);
assertFalse(result);
assertTrue(result);
result = hostParamValidator.isValid("", context);
assertFalse(result);
assertTrue(result);
result = hostParamValidator.isValid(" ", context);
assertFalse(result);
assertTrue(result);
result = hostParamValidator.isValid("192.168.1.1", context);
assertTrue(result);
@@ -31,6 +31,8 @@
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mysql.version>8.0.30</mysql.version>
<postgresql.version>42.5.5</postgresql.version>
</properties>
<dependencies>
@@ -53,10 +55,46 @@
<version>${hertzbeat.version}</version>
<scope>test</scope>
</dependency>
<!-- Testcontainers Jupiter / JUnit 5 -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- Testcontainers JDBC Support -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>jdbc</artifactId>
<scope>test</scope>
</dependency>
<!-- Testcontainers Database Modules -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<!-- JDBC Drivers -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>
</project>
@@ -0,0 +1,72 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.collector.collect.basic.database;
/**
* Database type (image mame) and image tag enumeration class
*/
public enum DatabaseImagesEnum {
MYSQL("mysql", "8.0.36"),
POSTGRESQL("postgresql", "15");
private final String imageName;
private final String defaultTag;
DatabaseImagesEnum(String imageName, String defaultTag) {
this.imageName = imageName;
this.defaultTag = defaultTag;
}
public String getImageName() {
return imageName;
}
public String getDefaultTag() {
return defaultTag;
}
public String getFullImageName() {
return imageName + ":" + defaultTag;
}
public static DatabaseImagesEnum fromImageName(String imageName) {
for (DatabaseImagesEnum value : values()) {
if (value.getImageName().equalsIgnoreCase(imageName)) {
return value;
}
}
throw new IllegalArgumentException("Unknown database image name: " + imageName);
}
public static DatabaseImagesEnum fromFullImageName(String fullImageName) {
for (DatabaseImagesEnum value : values()) {
if (value.getFullImageName().equalsIgnoreCase(fullImageName)) {
return value;
}
}
throw new IllegalArgumentException("Unknown full database image name: " + fullImageName);
}
@Override
public String toString() {
return "DatabaseImagesEnum{"
+ "imageName='" + imageName + '\''
+ ", defaultTag='" + defaultTag + '\''
+ '}';
}
}
@@ -0,0 +1,99 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.collector.collect.basic.database;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
import org.apache.hertzbeat.collector.collect.database.JdbcCommonCollect;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.common.entity.job.Configmap;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.JdbcProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* E2e test monitored by Jdbc Common
*/
@Slf4j
@ExtendWith(MockitoExtension.class)
public class JdbcCommonCollectE2eTest extends AbstractCollectE2eTest {
private static final DatabaseImagesEnum databaseImage = DatabaseImagesEnum.fromImageName("postgresql");
private static final String DATABASE_IMAGE_NAME = databaseImage.getImageName();
private static final String DATABASE_IMAGE_TAG = databaseImage.getDefaultTag();
@BeforeEach
public void setUp() throws Exception {
super.setUp();
collect = new JdbcCommonCollect();
metrics = new Metrics();
}
@Override
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
JdbcProtocol jdbcProtocol = (JdbcProtocol) buildProtocol(metricsDef);
metrics.setJdbc(jdbcProtocol);
CollectRep.MetricsData.Builder metricsData = CollectRep.MetricsData.newBuilder();
metricsData.setApp(DATABASE_IMAGE_NAME);
metrics.setAliasFields(metricsDef.getAliasFields());
return collectMetricsData(metrics, metricsDef, metricsData);
}
@Override
protected Protocol buildProtocol(Metrics metricsDef) {
JdbcProtocol jdbcProtocol = metricsDef.getJdbc();
jdbcProtocol.setHost(DATABASE_IMAGE_NAME);
jdbcProtocol.setPort(DATABASE_IMAGE_TAG);
jdbcProtocol.setDatabase("test");
jdbcProtocol.setPlatform("testcontainers");
return jdbcProtocol;
}
@Test
public void testWithJdbcTcUrl() {
Job dockerJob = appService.getAppDefine(DATABASE_IMAGE_NAME);
List<Map<String, Configmap>> configmapFromPreCollectData = new LinkedList<>();
for (Metrics metricsDef : dockerJob.getMetrics()) {
metricsDef = CollectUtil.replaceCryPlaceholderToMetrics(metricsDef, configmapFromPreCollectData.size() > 0 ? configmapFromPreCollectData.get(0) : new HashMap<>());
String metricName = metricsDef.getName();
if ("slow_sql".equals(metricName)) {
Metrics finalMetricsDef = metricsDef;
assertDoesNotThrow(() -> collectMetrics(finalMetricsDef),
String.format("%s failed to collect metrics)", metricName));
log.info("{} metrics validation passed", metricName);
continue; // skip slow_sql, extra extensions
}
CollectRep.MetricsData metricsData = validateMetricsCollection(metricsDef, metricName, true);
CollectUtil.getConfigmapFromPreCollectData(metricsData);
}
}
}
@@ -0,0 +1,126 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.collector.collect.basic.redis;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
import org.apache.hertzbeat.collector.collect.redis.RedisCommonCollectImpl;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
import org.apache.hertzbeat.common.entity.job.protocol.RedisProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.lifecycle.Startables;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;
/**
* End-to-end test class for redis collector
*/
@Slf4j
@ExtendWith(MockitoExtension.class)
public class RedisCommonCollectE2eTest extends AbstractCollectE2eTest {
private static final String REDIS_IMAGE = "redis:7.4.2";
private static final String HOST = "127.0.0.1";
private static final int REDIS_PORT = 6379;
private static final String REDIS_PATTERN = "1";
private static final List<String> ALLOW_EMPTY_WHITE_LIST = Arrays.asList("server", "errorstats", "commandstats", "keyspace");
private static GenericContainer<?> redisContainer;
private Integer mappedPort;
@AfterAll
public static void tearDown() throws InterruptedException {
if (redisContainer != null) {
redisContainer.stop();
}
}
@BeforeEach
public void setUp() throws Exception {
super.setUp();
collect = new RedisCommonCollectImpl();
// Set up and start container
setupAndStartContainer();
mappedPort = redisContainer.getMappedPort(REDIS_PORT);
log.info("Container started successfully with mapped port: {}", mappedPort);
}
@Test
public void testRedisCollect() throws ExecutionException, InterruptedException, TimeoutException {
Assertions.assertTrue(redisContainer.isRunning(), "redis container should be running");
Job redisJob = appService.getAppDefine("redis");
redisJob.getMetrics().forEach(metricsDef -> {
if (ALLOW_EMPTY_WHITE_LIST.contains(metricsDef.getName())) {
validateMetricsCollection(metricsDef, metricsDef.getName(), true);
} else {
validateMetricsCollection(metricsDef, metricsDef.getName());
}
});
}
@Override
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
//Build redis protocol configuration
RedisProtocol redisProtocol = (RedisProtocol) buildProtocol(metricsDef);
metrics.setRedis(redisProtocol);
metrics.setName(metricsDef.getName());
metrics.setFields(metricsDef.getFields());
return collectMetricsData(metrics, metricsDef);
}
@Override
protected Protocol buildProtocol(Metrics metricsDef) {
RedisProtocol redisProtocol = new RedisProtocol();
redisProtocol.setHost(HOST);
redisProtocol.setPort(mappedPort.toString());
redisProtocol.setPattern(REDIS_PATTERN);
return redisProtocol;
}
public void setupAndStartContainer() {
Network network = Network.builder().build();
redisContainer = new GenericContainer<>(DockerImageName.parse(REDIS_IMAGE))
.withExposedPorts(REDIS_PORT)
.withNetwork(network)
.withNetworkAliases("redis")
.waitingFor(Wait.forListeningPort())
.withStartupTimeout(Duration.ofSeconds(30));
Startables.deepStart(Stream.of(redisContainer)).join();
}
}
@@ -27,6 +27,7 @@ import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.manager.dao.DefineDao;
import org.apache.hertzbeat.manager.service.impl.AppServiceImpl;
import org.apache.hertzbeat.manager.service.impl.ObjectStoreConfigServiceImpl;
import org.junit.jupiter.api.Assertions;
@@ -34,6 +35,7 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -63,10 +65,13 @@ public abstract class AbstractCollectE2eTest {
private Timeout timeout;
@Mock
private Job job;
@Mock
private DefineDao defineDao;
public void setUp() throws Exception {
// Initialize mocks
MockitoAnnotations.openMocks(this);
when(defineDao.findAll()).thenReturn(new ArrayList<>());
when(timeout.task()).thenReturn(timerJob);
when(timerJob.getJob()).thenReturn(job);
metricsCollect = new MetricsCollect(mock(Metrics.class), timeout, mock(CollectDataDispatch.class), null, List.of());
@@ -78,6 +83,7 @@ public abstract class AbstractCollectE2eTest {
/**
* Validate metrics collection, check if the metrics values are not empty <br/>
*
* @param metricsDef metrics definition
* @param metricName metric name
* @return metrics data
+14 -1
View File
@@ -74,4 +74,17 @@
<scope>test</scope>
</dependency>
</dependencies>
</project>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
@@ -1,117 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.hertzbeat.log.config;
import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
import io.opentelemetry.sdk.resources.Resource;
import jakarta.annotation.PostConstruct;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
/**
* OpenTelemetryConfig is responsible for initializing OpenTelemetry with the specified service name and GrepTimeDB endpoint.
* It ensures that the initialization is done in a thread-safe manner and includes authentication for GrepTimeDB.
*/
@Configuration
@Slf4j
public class OpenTelemetryConfig {
@Autowired
private GreptimeProperties greptimeProperties;
/**
* Initializes OpenTelemetry with the given service name and GrepTimeDB endpoint.
* Includes authentication if configured in GreptimeProperties.
*/
@PostConstruct
public void initializeOpenTelemetry() {
if (greptimeProperties == null || !greptimeProperties.enabled()) {
log.info("GrepTimeDB logging is disabled, skipping OpenTelemetry configuration.");
return;
}
try {
Resource resource = Resource.getDefault()
.merge(Resource.builder()
.put(SERVICE_NAME, "HertzBeat")
.build());
Map<String, String> headers = new HashMap<>();
headers.put("X-Greptime-DB-Name", "public");
headers.put("X-Greptime-Log-Table-Name", "hzb_log");
addAuthenticationHeaders(headers);
OtlpHttpLogRecordExporter logExporter = OtlpHttpLogRecordExporter.builder()
.setEndpoint(greptimeProperties.httpEndpoint() + "/v1/otlp/v1/logs")
.setHeaders(()-> headers)
.setTimeout(10, TimeUnit.SECONDS)
.build();
SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder()
.setResource(resource)
.addLogRecordProcessor(
BatchLogRecordProcessor.builder(logExporter)
.setScheduleDelay(1000, TimeUnit.MILLISECONDS)
.setMaxExportBatchSize(512)
.build())
.build();
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
.setLoggerProvider(loggerProvider)
.build();
OpenTelemetryAppender.install(openTelemetry);
log.info("OpenTelemetry successfully configured with GrepTimeDB exporter.");
} catch (Exception e) {
log.error("Failed to initialize OpenTelemetry with GrepTimeDB", e);
}
}
/**
* Adds authentication headers to the provided map if username and password are configured.
*
* @param headers the map to which authentication headers will be added
*/
private void addAuthenticationHeaders(Map<String, String> headers) {
if (StringUtils.isNotBlank(greptimeProperties.username())
&& StringUtils.isNotBlank(greptimeProperties.password())) {
String credentials = greptimeProperties.username() + ":" + greptimeProperties.password();
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
headers.put("Authorization", "Basic " + encodedCredentials);
}
}
}
+1 -1
View File
@@ -92,7 +92,7 @@
<!-- log -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-log</artifactId>
<artifactId>hertzbeat-otel</artifactId>
</dependency>
<!-- spring -->
<dependency>
@@ -18,25 +18,22 @@
package org.apache.hertzbeat.manager.component.sd;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.HashSet;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.arrow.RowWrapper;
import org.apache.hertzbeat.common.entity.manager.CollectorMonitorBind;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.MonitorBind;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.common.entity.manager.SdMonitorParam;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.queue.CommonDataQueue;
import org.apache.hertzbeat.common.util.SdMonitorOperator;
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
import org.apache.hertzbeat.manager.dao.MonitorBindDao;
import org.apache.hertzbeat.manager.dao.MonitorDao;
@@ -45,7 +42,6 @@ import org.apache.hertzbeat.manager.scheduler.ManagerWorkerPool;
import org.apache.hertzbeat.manager.service.MonitorService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
/**
* Service Discovery Worker
@@ -53,6 +49,9 @@ import org.springframework.util.CollectionUtils;
@Slf4j
@Component
public class ServiceDiscoveryWorker implements InitializingBean {
private static final String FILED_HOST = "host";
private static final String FILED_PORT = "port";
private final MonitorService monitorService;
private final ParamDao paramDao;
private final MonitorDao monitorDao;
@@ -83,74 +82,73 @@ public class ServiceDiscoveryWorker implements InitializingBean {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try (final CollectRep.MetricsData metricsData = dataQueue.pollServiceDiscoveryData()) {
Long monitorId = metricsData.getId();
final Monitor mainMonitor = monitorDao.findMonitorsByIdIn(Sets.newHashSet(monitorId)).get(0);
mainMonitor.getLabels().remove(CommonConstants.TAG_SD_MAIN_MONITOR);
final Monitor mainMonitor = monitorDao.findById(monitorId).orElse(null);
if (mainMonitor == null) {
log.warn("No monitor found for id {}", monitorId);
continue;
}
// collector
final Optional<CollectorMonitorBind> collectorBind = collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(mainMonitor.getId());
final Optional<CollectorMonitorBind> collectorBind = collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(monitorId);
String collector = collectorBind.map(CollectorMonitorBind::getCollector).orElse(null);
// param
List<Param> mainMonitorParamList = paramDao.findParamsByMonitorId(mainMonitor.getId());
mainMonitorParamList = SdMonitorOperator.removeSdParam(mainMonitorParamList);
final Set<Long> subMonitorIdSet = monitorBindDao.findMonitorBindByBizIdAndType(monitorId, CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR)
.stream()
.map(MonitorBind::getMonitorId)
.collect(Collectors.toSet());
final Map<String, List<Monitor>> hostMonitorMap = CollectionUtils.isEmpty(subMonitorIdSet)
? Maps.newHashMap()
: monitorDao.findMonitorsByIdIn(subMonitorIdSet).stream().collect(Collectors.groupingBy(Monitor::getHost));
// params
List<Param> mainMonitorParams = paramDao.findParamsByMonitorId(monitorId);
final Map<String, MonitorBind> subMonitorBindMap = monitorBindDao.findMonitorBindsByBizId(monitorId)
.stream().collect(Collectors.toMap(MonitorBind::getKeyStr, item -> item));
RowWrapper rowWrapper = metricsData.readRow();
Map<String, String> fieldsValue = Maps.newHashMapWithExpectedSize(8);
while (rowWrapper.hasNextRow()) {
rowWrapper = rowWrapper.nextRow();
final String host = rowWrapper.nextCell().getValue();
final String port = rowWrapper.nextCell().getValue();
final List<Monitor> monitorList = hostMonitorMap.get(host);
if (CollectionUtils.isEmpty(monitorList)) {
monitorService.addAndSaveMonitorJob(mainMonitor.clone(), SdMonitorOperator.cloneParamList(mainMonitorParamList), collector,
SdMonitorParam.builder()
.detectedHost(host)
.detectedPort(port)
.bizId(mainMonitor.getId())
.build(), null);
return;
fieldsValue.clear();
rowWrapper.cellStream().forEach(cell -> {
String value = cell.getValue();
fieldsValue.put(cell.getField().getName(), value);
});
final String host = fieldsValue.get(FILED_HOST);
final String port = fieldsValue.get(FILED_PORT);
final String keyStr = host + ":" + port;
if (subMonitorBindMap.containsKey(keyStr)) {
subMonitorBindMap.remove(keyStr);
continue;
}
for (Monitor monitor : monitorList) {
// make sure monitor that has the same host and port is not existed.
final Optional<Param> samePortParam = paramDao.findParamsByMonitorId(monitor.getId()).stream()
.filter(param -> StringUtils.equals(param.getField(), "port"))
.filter(param -> StringUtils.equals(param.getParamValue(), port))
.findFirst();
if (samePortParam.isEmpty()) {
monitorService.addAndSaveMonitorJob(mainMonitor.clone(), SdMonitorOperator.cloneParamList(mainMonitorParamList), collector,
SdMonitorParam.builder()
.detectedHost(host)
.detectedPort(port)
.bizId(mainMonitor.getId())
.build(), null);
} else {
monitorService.enableManageMonitors(Sets.newHashSet(monitor.getId()));
Monitor newMonitor = mainMonitor.clone();
newMonitor.setId(null);
newMonitor.setHost(host);
newMonitor.setName(newMonitor.getName() + "-" + host + ":" + port);
newMonitor.setScrape(CommonConstants.SCRAPE_STATIC);
newMonitor.setGmtCreate(LocalDateTime.now());
newMonitor.setGmtUpdate(LocalDateTime.now());
// replace host port
List<Param> newParams = new LinkedList<>();
for (Param param : mainMonitorParams) {
Param newParam = param.clone();
newParam.setId(null);
newParam.setGmtUpdate(null);
newParam.setGmtCreate(null);
if (FILED_HOST.equals(newParam.getField())) {
newParam.setParamValue(host);
} else if (FILED_PORT.equals(newParam.getField())) {
newParam.setParamValue(port);
}
newParams.add(newParam);
}
// make sure hostMonitorMap contains monitors that have not judged yet.
hostMonitorMap.remove(host);
monitorService.addMonitor(newMonitor, newParams, collector, null);
MonitorBind monitorBind = MonitorBind.builder()
.bizId(monitorId)
.monitorId(newMonitor.getId())
.keyStr(keyStr)
.build();
monitorBindDao.save(monitorBind);
}
// hostMonitorMap only contains monitors which are already existed but not in service discovery data
// due to monitors that coincide with service discovery data are removed.
// Thus, all monitors still in hostMonitorMap need to be cancelled.
final HashSet<Long> needCancelMonitorIdSet = Sets.newHashSet();
hostMonitorMap.forEach((key, value) -> needCancelMonitorIdSet.addAll(value.stream()
.map(Monitor::getId)
.collect(Collectors.toSet())));
final Set<Long> needCancelMonitorIdSet = subMonitorBindMap.values().stream()
.map(MonitorBind::getMonitorId).collect(Collectors.toSet());
monitorService.cancelManageMonitors(needCancelMonitorIdSet);
for (Long id : needCancelMonitorIdSet) {
monitorBindDao.deleteMonitorBindByBizIdAndMonitorId(monitorId, id);
}
} catch (Exception exception) {
log.error(exception.getMessage(), exception);
}
@@ -35,7 +35,7 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return builder -> {
JavaTimeModule javaTimeModule = new JavaTimeModule();
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
@@ -127,16 +127,4 @@ public class MonitorsController {
monitorService.importConfig(file);
return ResponseEntity.ok(Message.success("Import success"));
}
@PostMapping("/copy")
@Operation(summary = "copy monitors by ids", description = "copy monitors by ids")
public ResponseEntity<Message<Void>> duplicateMonitors(
@Parameter(description = "Monitor ID List", example = "6565463543") @RequestParam List<Long> ids
) {
if (ids != null && !ids.isEmpty()) {
monitorService.copyMonitors(ids);
}
return ResponseEntity.ok(Message.success("copy success"));
}
}
@@ -18,22 +18,20 @@
package org.apache.hertzbeat.manager.dao;
import java.util.List;
import java.util.Set;
import org.apache.hertzbeat.common.entity.manager.MonitorBind;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
/**
* AuthResources database operation
* MonitorBind database operation
*/
public interface MonitorBindDao extends JpaRepository<MonitorBind, Long>, JpaSpecificationExecutor<MonitorBind> {
List<MonitorBind> findAllByBizIdInAndType(Set<Long> bizIdList, byte type);
List<MonitorBind> findMonitorBindByBizIdAndType(Long bizId, byte type);
MonitorBind findMonitorBindByBizIdAndMonitorIdAndType(Long bizId, Long monitorId, byte type);
List<MonitorBind> findAllByType(byte type);
List<MonitorBind> findMonitorBindsByBizId(Long bizId);
void deleteByMonitorId(Long monitorId);
}
@Modifying
void deleteMonitorBindByBizIdAndMonitorId(Long bizId, Long monitorId);
}
@@ -36,12 +36,12 @@ import org.apache.hertzbeat.common.entity.manager.Param;
@Schema(description = "Monitoring information entities")
public class MonitorDto {
@Schema(description = "Monitor Content", accessMode = READ_WRITE)
@Schema(description = "monitor content", accessMode = READ_WRITE)
@NotNull
@Valid
private Monitor monitor;
@Schema(description = "Monitor Params", accessMode = READ_WRITE)
@Schema(description = "monitor params", accessMode = READ_WRITE)
@NotEmpty
@Valid
private List<Param> params;
@@ -19,10 +19,8 @@ package org.apache.hertzbeat.manager.scheduler;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.dto.CollectorInfo;
import org.apache.hertzbeat.common.entity.job.Configmap;
@@ -30,13 +28,10 @@ import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.manager.Collector;
import org.apache.hertzbeat.common.entity.manager.CollectorMonitorBind;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.MonitorBind;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
import org.apache.hertzbeat.common.util.SdMonitorOperator;
import org.apache.hertzbeat.manager.dao.CollectorDao;
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
import org.apache.hertzbeat.manager.dao.MonitorBindDao;
import org.apache.hertzbeat.manager.dao.MonitorDao;
import org.apache.hertzbeat.manager.dao.ParamDao;
import org.apache.hertzbeat.manager.service.AppService;
@@ -45,6 +40,7 @@ import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;
/**
* scheduler init
@@ -77,9 +73,6 @@ public class SchedulerInit implements CommandLineRunner {
@Autowired
private CollectorMonitorBindDao collectorMonitorBindDao;
@Autowired
private MonitorBindDao monitorBindDao;
@Override
public void run(String... args) throws Exception {
@@ -98,20 +91,18 @@ public class SchedulerInit implements CommandLineRunner {
// init jobs
List<Monitor> monitors = monitorDao.findMonitorsByStatusNotInAndJobIdNotNull(List.of(CommonConstants.MONITOR_PAUSED_CODE));
List<CollectorMonitorBind> monitorBinds = collectorMonitorBindDao.findAll();
final Set<Long> sdMonitorIds = monitorBindDao.findAllByType(CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR).stream()
.map(MonitorBind::getBizId)
.collect(Collectors.toSet());
Map<Long, String> monitorIdCollectorMap = monitorBinds.stream().collect(
Collectors.toMap(CollectorMonitorBind::getMonitorId, CollectorMonitorBind::getCollector));
for (Monitor monitor : monitors) {
try {
// build collect job entity
Job appDefine = appService.getAppDefine(monitor.getApp());
List<Param> params = paramDao.findParamsByMonitorId(monitor.getId());
if (sdMonitorIds.contains(monitor.getId())) {
appDefine = SdMonitorOperator.constructSdJob(appDefine, SdMonitorOperator.getSdParam(params).get());
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
String app = isStatic ? monitor.getApp() : monitor.getScrape();
Job appDefine = appService.getAppDefine(app);
if (!isStatic) {
appDefine.setSd(true);
}
List<Param> params = paramDao.findParamsByMonitorId(monitor.getId());
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
}
@@ -129,7 +120,7 @@ public class SchedulerInit implements CommandLineRunner {
.map(param -> new Configmap(param.getField(), param.getParamValue(),
param.getType())).collect(Collectors.toList());
List<ParamDefine> paramDefaultValue = appDefine.getParams().stream()
.filter(item -> StringUtils.isNotBlank(item.getDefaultValue()))
.filter(item -> StringUtils.hasText(item.getDefaultValue()))
.toList();
paramDefaultValue.forEach(defaultVar -> {
if (configmaps.stream().noneMatch(item -> item.getKey().equals(defaultVar.getField()))) {
@@ -22,14 +22,12 @@ import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.common.entity.manager.SdMonitorParam;
import org.apache.hertzbeat.manager.pojo.dto.AppCount;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.support.exception.MonitorDetectException;
import org.springframework.data.domain.Page;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -125,14 +123,14 @@ public interface MonitorService {
*
* @param ids Monitoring ID List
*/
void cancelManageMonitors(HashSet<Long> ids);
void cancelManageMonitors(Set<Long> ids);
/**
* Start the managed monitoring items in batches according to the monitoring ID list
*
* @param ids Monitoring ID List
*/
void enableManageMonitors(HashSet<Long> ids);
void enableManageMonitors(Set<Long> ids);
/**
* Query the monitoring category and its corresponding monitoring quantity
@@ -183,22 +181,13 @@ public interface MonitorService {
*/
void importConfig(MultipartFile file) throws Exception;
/**
* Copy monitor in batches based on the id
*
* @param ids monitor id
*/
void copyMonitors(List<Long> ids);
/**
* update app collect job by app
*
* @param job job content
*/
void updateAppCollectJob(Job job);
void addAndSaveMonitorJob(Monitor monitor, List<Param> params, String collector, SdMonitorParam sdMonitorParam, GrafanaDashboard grafanaDashboard);
/**
* Copy monitor by id
*
@@ -270,10 +270,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
public List<Hierarchy> getAllAppHierarchy(String lang) {
LinkedList<Hierarchy> hierarchies = new LinkedList<>();
for (var job : appDefines.values()) {
// TODO temporarily filter out push to solve the front-end problem, and open it after the subsequent design optimization
if (DispatchConstants.PROTOCOL_PUSH.equalsIgnoreCase(job.getApp())) {
continue;
}
queryAppHierarchy(lang, hierarchies, job);
}
return hierarchies;
@@ -518,7 +514,7 @@ public class AppServiceImpl implements AppService, InitializingBean {
*/
private void refreshStore(ObjectStoreDTO<?> objectStoreConfig) {
if (objectStoreConfig == null) {
appDefineStore = new LocalFileAppDefineStoreImpl();
appDefineStore = new DatabaseAppDefineStoreImpl();
} else {
if (objectStoreConfig.getType() == ObjectStoreDTO.Type.OBS) {
appDefineStore = new ObjectStoreAppDefineStoreImpl();
@@ -23,7 +23,6 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.alert.dao.AlertDefineBindDao;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.common.constants.CommonConstants;
@@ -34,23 +33,19 @@ import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
import org.apache.hertzbeat.common.entity.job.Configmap;
import org.apache.hertzbeat.common.entity.job.Job;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.CommonRequestProtocol;
import org.apache.hertzbeat.common.entity.manager.Collector;
import org.apache.hertzbeat.common.entity.manager.CollectorMonitorBind;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.MonitorBind;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
import org.apache.hertzbeat.common.entity.manager.SdMonitorParam;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryProtocol;
import org.apache.hertzbeat.common.support.event.MonitorDeletedEvent;
import org.apache.hertzbeat.common.util.AesUtil;
import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.hertzbeat.common.util.FileUtil;
import org.apache.hertzbeat.common.util.IntervalExpressionUtil;
import org.apache.hertzbeat.common.util.IpDomainUtil;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.util.SdMonitorOperator;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.apache.hertzbeat.grafana.service.DashboardService;
import org.apache.hertzbeat.manager.config.ManagerSseManager;
@@ -65,7 +60,6 @@ import org.apache.hertzbeat.manager.scheduler.CollectJobScheduling;
import org.apache.hertzbeat.manager.service.AppService;
import org.apache.hertzbeat.manager.service.ImExportService;
import org.apache.hertzbeat.manager.service.MonitorService;
import org.apache.hertzbeat.manager.service.LabelService;
import org.apache.hertzbeat.manager.support.exception.MonitorDatabaseException;
import org.apache.hertzbeat.manager.support.exception.MonitorDetectException;
import org.apache.hertzbeat.warehouse.service.WarehouseService;
@@ -81,15 +75,14 @@ import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -109,15 +102,11 @@ public class MonitorServiceImpl implements MonitorService {
private static final Long MONITOR_ID_TMP = 1000000000L;
private static final byte ALL_MONITOR_STATUS = 9;
private static final int TAG_LENGTH = 2;
private static final String CONTENT_VALUE = MediaType.APPLICATION_OCTET_STREAM_VALUE + SignConstants.SINGLE_MARK + "charset=" + StandardCharsets.UTF_8;
private final Map<String, ImExportService> imExportServiceMap = new HashMap<>();
@Autowired
private AppService appService;
@Autowired
private LabelService tagService;
@Autowired
private CollectJobScheduling collectJobScheduling;
@Autowired
private MonitorDao monitorDao;
@@ -147,25 +136,74 @@ public class MonitorServiceImpl implements MonitorService {
@Override
@Transactional(readOnly = true)
public void detectMonitor(Monitor monitor, List<Param> params, String collector) throws MonitorDetectException {
final Optional<Param> sdParam = SdMonitorOperator.getSdParam(params);
if (sdParam.isPresent()) {
collectOneTimeSdData(monitor, collector, sdParam.get());
} else {
if (CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape())) {
detectMonitorDirectly(monitor, params, collector);
} else {
detectSdMonitor(monitor, params, collector);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void addMonitor(Monitor monitor, List<Param> params, String collector, GrafanaDashboard grafanaDashboard) throws RuntimeException {
final Optional<Param> sdParam = SdMonitorOperator.getSdParam(params);
if (sdParam.isPresent()) {
addAndSaveMonitorJob(monitor.clone(), params, collector, SdMonitorParam.builder().sdParam(sdParam.get()).build(), grafanaDashboard);
return;
// Apply for monitor id
long monitorId = SnowFlakeIdGenerator.generateId();
Map<String, String> labels = monitor.getLabels();
if (labels == null) {
labels = new HashMap<>(8);
monitor.setLabels(labels);
}
// Construct the collection task Job entity
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
String app = isStatic ? monitor.getApp() : monitor.getScrape();
Job appDefine = appService.getAppDefine(app);
if (!isStatic) {
appDefine.setSd(true);
}
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
}
appDefine.setMonitorId(monitorId);
appDefine.setDefaultInterval(monitor.getIntervals());
appDefine.setCyclic(true);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(monitor.getLabels());
appDefine.setAnnotations(monitor.getAnnotations());
List<Configmap> configmaps = params.stream().map(param -> {
param.setMonitorId(monitorId);
return new Configmap(param.getField(), param.getParamValue(), param.getType());
}).collect(Collectors.toList());
appDefine.setConfigmap(configmaps);
long jobId = collector == null ? collectJobScheduling.addAsyncCollectJob(appDefine, null) :
collectJobScheduling.addAsyncCollectJob(appDefine, collector);
try {
detectMonitor(monitor, params, collector);
} catch (Exception ignored) {}
addAndSaveMonitorJob(monitor, params, collector, null, grafanaDashboard);
try {
if (collector != null) {
CollectorMonitorBind collectorMonitorBind = CollectorMonitorBind.builder()
.collector(collector)
.monitorId(monitorId)
.build();
collectorMonitorBindDao.save(collectorMonitorBind);
}
monitor.setId(monitorId);
monitor.setJobId(jobId);
// create grafana dashboard
if (monitor.getApp().equals(CommonConstants.PROMETHEUS) && grafanaDashboard != null && grafanaDashboard.isEnabled()) {
dashboardService.createOrUpdateDashboard(grafanaDashboard.getTemplate(), monitorId);
}
monitorDao.save(monitor);
paramDao.saveAll(params);
} catch (Exception e) {
log.error("Error while adding monitor: {}", e.getMessage(), e);
collectJobScheduling.cancelAsyncCollectJob(jobId);
throw new MonitorDatabaseException(e.getMessage());
}
}
@Override
@@ -233,7 +271,7 @@ public class MonitorServiceImpl implements MonitorService {
}
}
// the dispatch collector must exist if pin
if (StringUtils.isNotBlank(monitorDto.getCollector())) {
if (StringUtils.hasText(monitorDto.getCollector())) {
Optional<Collector> optionalCollector = collectorDao.findCollectorByName(monitorDto.getCollector());
if (optionalCollector.isEmpty()) {
throw new IllegalArgumentException("The pinned collector does not exist.");
@@ -250,7 +288,7 @@ public class MonitorServiceImpl implements MonitorService {
if (paramDefine.isRequired() && (param == null || param.getParamValue() == null)) {
throw new IllegalArgumentException("Params field " + field + " is required.");
}
if (param != null && param.getParamValue() != null && StringUtils.isNotBlank(param.getParamValue())) {
if (param != null && StringUtils.hasText(param.getParamValue())) {
switch (paramDefine.getType()) {
case "number":
double doubleValue;
@@ -376,7 +414,6 @@ public class MonitorServiceImpl implements MonitorService {
@Override
@Transactional(rollbackFor = Exception.class)
public void modifyMonitor(Monitor monitor, List<Param> params, String collector, GrafanaDashboard grafanaDashboard) throws RuntimeException {
final Optional<Param> sdParam = SdMonitorOperator.getSdParam(params);
long monitorId = monitor.getId();
// Check to determine whether the monitor corresponding to the monitor id exists
Optional<Monitor> queryOption = monitorDao.findById(monitorId);
@@ -393,36 +430,14 @@ public class MonitorServiceImpl implements MonitorService {
labels = new HashMap<>(8);
monitor.setLabels(labels);
}
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
if (preMonitor.getStatus() != CommonConstants.MONITOR_PAUSED_CODE) {
// Construct the collection task Job entity
Job appDefine = appService.getAppDefine(monitor.getApp());
if (sdParam.isPresent()) {
// not allow to modify a sub monitor to main monitor
labels.entrySet()
.stream()
.filter(label -> org.apache.commons.lang3.StringUtils.equals(label.getKey(), CommonConstants.TAG_AUTO_CREATED))
.findFirst()
.ifPresent(label -> {
final MonitorBind isSubMonitorBefore = monitorBindDao.findMonitorBindByBizIdAndMonitorIdAndType(Long.valueOf(label.getValue()),
monitorId, CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR);
if (Objects.nonNull(isSubMonitorBefore)) {
throw new UnsupportedOperationException("Can not change a auto-created monitor to sd! ");
}
});
// create main monitor bind
final List<MonitorBind> mainMonitorBind = monitorBindDao.findMonitorBindByBizIdAndType(monitorId, CommonConstants.MONITOR_BIND_TYPE_SD_MAIN_MONITOR);
if (CollectionUtils.isEmpty(mainMonitorBind)) {
monitorBindDao.save(Objects.requireNonNull(
SdMonitorOperator.buildSdMainMonitorBind(
SdMonitorParam.builder().sdParam(sdParam.get()).build(), monitorId
)));
}
appDefine = SdMonitorOperator.constructSdJob(appDefine, sdParam.get());
labels.put(CommonConstants.TAG_SD_MAIN_MONITOR, ServiceDiscoveryProtocol.Type.getType(sdParam.get().getField()).name());
String app = isStatic ? monitor.getApp() : monitor.getScrape();
Job appDefine = appService.getAppDefine(app);
if (!isStatic) {
appDefine.setSd(true);
}
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
}
@@ -450,8 +465,7 @@ public class MonitorServiceImpl implements MonitorService {
// execute only in non paused status
try {
detectMonitor(monitor, params, collector);
} catch (Exception ignored) {
}
} catch (Exception ignored) {}
}
// After the update is successfully released, refresh the database
@@ -474,12 +488,6 @@ public class MonitorServiceImpl implements MonitorService {
}
}
monitorDao.save(monitor);
// delete all sub monitor if sd param is removed
final List<MonitorBind> subMonitorBindList = monitorBindDao.findMonitorBindByBizIdAndType(monitorId, CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR);
if (!CollectionUtils.isEmpty(subMonitorBindList) && sdParam.isEmpty()) {
deleteMonitors(subMonitorBindList.stream().map(MonitorBind::getMonitorId).collect(Collectors.toSet()));
}
paramDao.saveAll(params);
} catch (Exception e) {
log.error(e.getMessage(), e);
@@ -501,12 +509,6 @@ public class MonitorServiceImpl implements MonitorService {
if (CollectionUtils.isEmpty(ids)) {
return;
}
final List<MonitorBind> allSubMonitorBind = monitorBindDao.findAllByBizIdInAndType(ids, CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR);
if (!CollectionUtils.isEmpty(allSubMonitorBind)) {
ids.addAll(allSubMonitorBind.stream().map(MonitorBind::getMonitorId).collect(Collectors.toSet()));
}
List<Monitor> monitors = monitorDao.findMonitorsByIdIn(ids);
if (!monitors.isEmpty()) {
monitorDao.deleteAll(monitors);
@@ -537,7 +539,9 @@ public class MonitorServiceImpl implements MonitorService {
monitorDto.setMetrics(metrics);
monitorDto.setGrafanaDashboard(dashboardService.getDashboardByMonitorId(id));
} else {
Job job = appService.getAppDefine(monitor.getApp());
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
String type = isStatic ? monitor.getApp() : monitor.getScrape();
Job job = appService.getAppDefine(type);
List<String> metrics = job.getMetrics().stream()
.filter(Metrics::isVisible)
.map(Metrics::getName).collect(Collectors.toList());
@@ -563,7 +567,7 @@ public class MonitorServiceImpl implements MonitorService {
}
andList.add(inPredicate);
}
if (StringUtils.isNotBlank(app)) {
if (StringUtils.hasText(app)) {
Predicate predicateApp = criteriaBuilder.equal(root.get("app"), app);
andList.add(predicateApp);
}
@@ -575,17 +579,17 @@ public class MonitorServiceImpl implements MonitorService {
Predicate andPredicate = criteriaBuilder.and(andList.toArray(andPredicates));
List<Predicate> orList = new ArrayList<>();
if (StringUtils.isNotBlank(search)) {
if (StringUtils.hasText(search)) {
Predicate predicateHost = criteriaBuilder.like(root.get("host"), "%" + search + "%");
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + search.toLowerCase() + "%");
if (StringUtils.isNumeric(search)){
if (CommonUtil.isNumeric(search)){
Predicate predicateId = criteriaBuilder.equal(root.get("id"), Long.parseLong(search));
orList.add(predicateId);
}
orList.add(predicateHost);
orList.add(predicateName);
}
if (StringUtils.isNotBlank(labels)) {
if (StringUtils.hasText(labels)) {
String[] labelAres = labels.split(",");
for (String label : labelAres) {
String[] labelArr = label.split(":");
@@ -620,18 +624,10 @@ public class MonitorServiceImpl implements MonitorService {
}
@Override
public void cancelManageMonitors(HashSet<Long> ids) {
public void cancelManageMonitors(Set<Long> ids) {
if (CollectionUtils.isEmpty(ids)) {
return;
}
final Set<Long> subMonitorIds = monitorBindDao.findAllByBizIdInAndType(ids, CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR).stream()
.map(MonitorBind::getMonitorId)
.collect(Collectors.toSet());
if (!CollectionUtils.isEmpty(subMonitorIds)) {
ids.addAll(subMonitorIds);
}
// Update monitoring status Delete corresponding monitoring periodic task
// The jobId is not deleted, and the jobId is reused again after the management is started.
List<Monitor> managedMonitors = monitorDao.findMonitorsByIdIn(ids)
@@ -648,7 +644,7 @@ public class MonitorServiceImpl implements MonitorService {
}
@Override
public void enableManageMonitors(HashSet<Long> ids) {
public void enableManageMonitors(Set<Long> ids) {
// Update monitoring status Add corresponding monitoring periodic task
List<Monitor> unManagedMonitors = monitorDao.findMonitorsByIdIn(ids)
.stream().filter(monitor ->
@@ -662,20 +658,12 @@ public class MonitorServiceImpl implements MonitorService {
for (Monitor monitor : unManagedMonitors) {
// Construct the collection task Job entity
List<Param> params = paramDao.findParamsByMonitorId(monitor.getId());
final Optional<Param> sdParam = SdMonitorOperator.getSdParam(params);
Job appDefine = appService.getAppDefine(monitor.getApp());
if (sdParam.isPresent()) {
final List<MonitorBind> mainMonitorBind = monitorBindDao.findMonitorBindByBizIdAndType(monitor.getId(), CommonConstants.MONITOR_BIND_TYPE_SD_MAIN_MONITOR);
if (CollectionUtils.isEmpty(mainMonitorBind)) {
monitorBindDao.save(Objects.requireNonNull(
SdMonitorOperator.buildSdMainMonitorBind(
SdMonitorParam.builder().sdParam(sdParam.get()).build(), monitor.getId()
)));
}
appDefine = SdMonitorOperator.constructSdJob(appDefine, sdParam.get());
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
String app = isStatic ? monitor.getApp() : monitor.getScrape();
Job appDefine = appService.getAppDefine(app);
if (!isStatic) {
appDefine.setSd(true);
}
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
}
@@ -691,7 +679,7 @@ public class MonitorServiceImpl implements MonitorService {
List<Configmap> configmaps = params.stream().map(param ->
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
List<ParamDefine> paramDefaultValue = appDefine.getParams().stream()
.filter(item -> StringUtils.isNotBlank(item.getDefaultValue()))
.filter(item -> StringUtils.hasText(item.getDefaultValue()))
.toList();
paramDefaultValue.forEach(defaultVar -> {
if (configmaps.stream().noneMatch(item -> item.getKey().equals(defaultVar.getField()))) {
@@ -752,25 +740,6 @@ public class MonitorServiceImpl implements MonitorService {
}).filter(Objects::nonNull).collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void copyMonitors(List<Long> ids) {
ids.stream().parallel().forEach(id -> {
// get monitor and Params according id
Optional<Monitor> monitorOpt = monitorDao.findById(id);
List<Param> params = paramDao.findParamsByMonitorId(id);
monitorOpt.ifPresentOrElse(monitor -> {
// deep copy original monitor to achieve persist in JPA
Monitor newMonitor = JsonUtil.fromJson(JsonUtil.toJson(monitor), Monitor.class);
if (newMonitor != null) {
copyMonitor(newMonitor, params);
}
}, () -> log.warn("can not find the monitor for id {}", id));
});
}
@Override
public void updateAppCollectJob(Job job) {
List<Monitor> monitors = monitorDao.findMonitorsByAppEquals(job.getApp())
@@ -807,7 +776,7 @@ public class MonitorServiceImpl implements MonitorService {
List<Configmap> configmaps = params.stream().map(param -> new Configmap(param.getField(),
param.getParamValue(), param.getType())).collect(Collectors.toList());
List<ParamDefine> paramDefaultValue = appDefine.getParams().stream()
.filter(item -> StringUtils.isNotBlank(item.getDefaultValue()))
.filter(item -> StringUtils.hasText(item.getDefaultValue()))
.toList();
paramDefaultValue.forEach(defaultVar -> {
if (configmaps.stream().noneMatch(item -> item.getKey().equals(defaultVar.getField()))) {
@@ -827,80 +796,7 @@ public class MonitorServiceImpl implements MonitorService {
}
}
}
@Override
public void addAndSaveMonitorJob(Monitor monitor, List<Param> params, String collector, SdMonitorParam sdMonitorParam, GrafanaDashboard grafanaDashboard) {
// Apply for monitor id
long monitorId = SnowFlakeIdGenerator.generateId();
Map<String, String> labels = monitor.getLabels();
if (labels == null) {
labels = new HashMap<>(8);
monitor.setLabels(labels);
}
MonitorBind monitorBind = null;
// Construct the collection task Job entity
Job appDefine = appService.getAppDefine(monitor.getApp());
if (Objects.nonNull(sdMonitorParam)) {
monitorBind = Objects.isNull(sdMonitorParam.getSdParam())
? SdMonitorOperator.buildSdSubMonitorBind(sdMonitorParam, monitorId, labels)
: SdMonitorOperator.buildSdMainMonitorBind(sdMonitorParam, monitorId);
appDefine = SdMonitorOperator.constructSdJobAndTag(sdMonitorParam, labels, appDefine);
}
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
}
appDefine.setMonitorId(monitorId);
appDefine.setDefaultInterval(monitor.getIntervals());
appDefine.setCyclic(true);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(monitor.getLabels());
appDefine.setAnnotations(monitor.getAnnotations());
resetMetricsCommonField(monitor, appDefine, sdMonitorParam);
List<Configmap> configmaps = params.stream().map(param -> {
param.setMonitorId(monitorId);
return new Configmap(param.getField(), param.getParamValue(), param.getType());
}).collect(Collectors.toList());
appDefine.setConfigmap(configmaps);
long jobId = collector == null ? collectJobScheduling.addAsyncCollectJob(appDefine, null) :
collectJobScheduling.addAsyncCollectJob(appDefine, collector);
try {
detectMonitor(monitor, params, collector);
} catch (Exception ignored) {
}
try {
if (collector != null) {
CollectorMonitorBind collectorMonitorBind = CollectorMonitorBind.builder()
.collector(collector)
.monitorId(monitorId)
.build();
collectorMonitorBindDao.save(collectorMonitorBind);
}
monitor.setId(monitorId);
monitor.setJobId(jobId);
// create grafana dashboard
if (monitor.getApp().equals(CommonConstants.PROMETHEUS) && grafanaDashboard != null && grafanaDashboard.isEnabled()) {
dashboardService.createOrUpdateDashboard(grafanaDashboard.getTemplate(), monitorId);
}
monitorDao.save(monitor);
if (Objects.nonNull(monitorBind)) {
monitorBindDao.save(monitorBind);
}
paramDao.saveAll(params);
} catch (Exception e) {
log.error("Error while adding monitor: {}", e.getMessage(), e);
collectJobScheduling.cancelAsyncCollectJob(jobId);
throw new MonitorDatabaseException(e.getMessage());
}
}
@Override
public Monitor getMonitor(Long monitorId) {
return monitorDao.findById(monitorId).orElse(null);
@@ -916,73 +812,69 @@ public class MonitorServiceImpl implements MonitorService {
return monitorDao.findMonitorsByAppEquals(app);
}
private void resetMetricsCommonField(Monitor monitor, Job appDefine, SdMonitorParam sdMonitorParam) {
if (Objects.isNull(sdMonitorParam)
|| org.apache.commons.lang3.StringUtils.isAnyBlank(sdMonitorParam.getDetectedHost(), sdMonitorParam.getDetectedPort())) {
return;
@Override
@Transactional(rollbackFor = Exception.class)
public void copyMonitor(Long id) {
// Get the source monitor information
Optional<Monitor> monitorOptional = monitorDao.findById(id);
if (monitorOptional.isEmpty()) {
throw new IllegalArgumentException("Monitor not found: " + id);
}
for (Metrics metric : appDefine.getMetrics()) {
Arrays.stream(metric.getClass().getDeclaredFields())
.filter(field -> metric.getProtocol().equalsIgnoreCase(field.getName()))
.findFirst()
.ifPresent(field -> {
field.setAccessible(true);
final Object obj;
try {
obj = field.get(metric);
if (obj instanceof CommonRequestProtocol protocol) {
protocol.setHost(sdMonitorParam.getDetectedHost());
protocol.setPort(sdMonitorParam.getDetectedPort());
}
} catch (IllegalAccessException exception) {
log.warn("Not such field {}", field.getName());
}
});
Monitor sourceMonitor = monitorOptional.get();
// Get the parameters of source monitor
List<Param> sourceParams = paramDao.findParamsByMonitorId(id);
// Create new monitor object
Monitor newMonitor = new Monitor();
// Copy basic properties, exclude ID, jobId and status
BeanUtils.copyProperties(sourceMonitor, newMonitor, "id", "jobId", "status");
// Set new name
newMonitor.setName(sourceMonitor.getName() + "_copy");
// Set initial status
newMonitor.setStatus(CommonConstants.MONITOR_UP_CODE);
// Set create and update time
newMonitor.setGmtCreate(LocalDateTime.now());
newMonitor.setGmtUpdate(LocalDateTime.now());
// Copy parameters
List<Param> newParams = new ArrayList<>();
if (!sourceParams.isEmpty()) {
for (Param sourceParam : sourceParams) {
Param newParam = new Param();
BeanUtils.copyProperties(sourceParam, newParam, "id");
newParam.setMonitorId(newMonitor.getId());
newParams.add(newParam);
}
}
monitor.setHost(sdMonitorParam.getDetectedHost());
Map<String, String> labels = monitor.getLabels();
if (labels == null) {
labels = new HashMap<>(8);
monitor.setLabels(labels);
}
labels.put(CommonConstants.TAG_MONITOR_HOST, sdMonitorParam.getDetectedHost());
monitor.setName(monitor.getApp().toUpperCase() + CommonConstants.LOCALE_SEPARATOR + sdMonitorParam.getDetectedHost()
+ CommonConstants.LOCALE_SEPARATOR + SnowFlakeIdGenerator.generateId());
addMonitor(newMonitor, newParams, null, null);
}
private void copyMonitor(Monitor monitor, List<Param> params) {
monitor.setName(String.format("%s - copy", monitor.getName()));
addMonitor(monitor, params, null, null);
}
private void collectOneTimeSdData(Monitor monitor, String collector, Param sdParam) {
private void detectSdMonitor(Monitor monitor, List<Param> params, String collector) {
Long monitorId = monitor.getId();
if (monitorId == null || monitorId == 0) {
monitorId = MONITOR_ID_TMP;
}
Job appDefine = appService.getAppDefine(monitor.getApp());
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
if (monitor.getScrape() == null || params == null || params.isEmpty()) {
throw new IllegalArgumentException("scrape params is null or empty!");
}
Job appDefine = appService.getAppDefine(monitor.getScrape());
appDefine.setMonitorId(monitorId);
appDefine.setCyclic(false);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName());
appDefine.setMetadata(metadata);
appDefine.setLabels(monitor.getLabels());
appDefine.setAnnotations(monitor.getAnnotations());
final Job sdJob = SdMonitorOperator.constructSdJob(appDefine, sdParam);
List<Configmap> configmaps = params.stream().map(param ->
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
appDefine.setConfigmap(configmaps);
appDefine.setSd(true);
List<CollectRep.MetricsData> collectRep;
if (collector != null) {
collectRep = collectJobScheduling.collectSyncJobData(sdJob, collector);
collectRep = collectJobScheduling.collectSyncJobData(appDefine, collector);
} else {
collectRep = collectJobScheduling.collectSyncJobData(sdJob);
collectRep = collectJobScheduling.collectSyncJobData(appDefine);
}
monitor.setStatus(CommonConstants.MONITOR_UP_CODE);
// If the detection result fails, a detection exception is thrown
if (collectRep == null || collectRep.isEmpty()) {
monitor.setStatus(CommonConstants.MONITOR_DOWN_CODE);
@@ -1038,95 +930,4 @@ public class MonitorServiceImpl implements MonitorService {
}
collectRep.forEach(CollectRep.MetricsData::close);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void copyMonitor(Long id) {
// Get the source monitor information
Optional<Monitor> monitorOptional = monitorDao.findById(id);
if (monitorOptional.isEmpty()) {
throw new IllegalArgumentException("Monitor not found: " + id);
}
Monitor sourceMonitor = monitorOptional.get();
// Get the parameters of source monitor
List<Param> sourceParams = paramDao.findParamsByMonitorId(id);
// Create new monitor object
Monitor newMonitor = new Monitor();
// Copy basic properties, exclude ID, jobId and status
BeanUtils.copyProperties(sourceMonitor, newMonitor, "id", "jobId", "status");
// Set new name
newMonitor.setName(sourceMonitor.getName() + "_copy");
// Set initial status
newMonitor.setStatus(CommonConstants.MONITOR_UP_CODE);
// Set create and update time
newMonitor.setGmtCreate(LocalDateTime.now());
newMonitor.setGmtUpdate(LocalDateTime.now());
// Generate new ID using snowflake algorithm
newMonitor.setId(SnowFlakeIdGenerator.generateId());
// Save new monitor
newMonitor = monitorDao.save(newMonitor);
// Ensure ID is set
if (newMonitor.getId() == null) {
throw new RuntimeException("Failed to generate monitor ID");
}
// Copy parameters
if (!sourceParams.isEmpty()) {
List<Param> newParams = new ArrayList<>();
for (Param sourceParam : sourceParams) {
Param newParam = new Param();
BeanUtils.copyProperties(sourceParam, newParam, "id");
newParam.setMonitorId(newMonitor.getId());
newParams.add(newParam);
}
paramDao.saveAll(newParams);
}
try {
// Build collect job
Job appDefine = appService.getAppDefine(newMonitor.getApp());
if (CommonConstants.PROMETHEUS.equals(newMonitor.getApp())) {
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + newMonitor.getName());
}
// Ensure using correct monitor ID
appDefine.setMonitorId(newMonitor.getId());
appDefine.setDefaultInterval(newMonitor.getIntervals());
appDefine.setCyclic(true);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, newMonitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, newMonitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(newMonitor.getLabels());
appDefine.setAnnotations(newMonitor.getAnnotations());
List<Configmap> configmaps = sourceParams.stream()
.map(param -> new Configmap(param.getField(), param.getParamValue(), param.getType()))
.collect(Collectors.toList());
appDefine.setConfigmap(configmaps);
// Get collector configuration from source monitor
Optional<CollectorMonitorBind> bindOptional =
collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(sourceMonitor.getId());
String collector = bindOptional.map(CollectorMonitorBind::getCollector).orElse(null);
// Dispatch collect job
long jobId = collectJobScheduling.addAsyncCollectJob(appDefine, collector);
newMonitor.setJobId(jobId);
monitorDao.save(newMonitor);
// Copy collector binding if exists
if (collector != null) {
CollectorMonitorBind newBind = CollectorMonitorBind.builder()
.collector(collector)
.monitorId(newMonitor.getId())
.build();
collectorMonitorBindDao.save(newBind);
}
} catch (Exception e) {
log.error("Create collect job error: {}", e.getMessage(), e);
throw new RuntimeException("Create collect job failed: " + e.getMessage());
}
}
}
@@ -132,10 +132,21 @@ warehouse:
# (please set this configuration reasonably as history records can affect performance when it is large)
max-history-record-num: 6000
victoria-metrics:
# Standalone mode toggle — must be set to false when using cluster mode
enabled: false
url: http://localhost:8428
username: root
password: root
cluster:
enabled: false
select:
url: http://localhost:8481
username: root
password: root
insert:
url: http://localhost:8480
username: root
password: root
td-engine:
enabled: false
driver-class-name: com.taosdata.jdbc.rs.RestfulDriver
@@ -0,0 +1,62 @@
# 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.
# The monitoring type categoryservice-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
category: __system__
# Monitoring application type name (consistent with file name) eg: linux windows tomcat mysql aws...
app: http_sd
# The app api i18n name
name:
zh-CN: Http Service Discovery
en-US: Http Service Discovery
# Input params define for app api(render web ui by the definition)
params:
# field-param field key
- field: __sd_url__
# name-param field display i18n name
name:
zh-CN: 服务发现地址
en-US: Service Discovery Url
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: true
metrics:
- name: target
i18n:
zh-CN: 监控目标
en-US: Monitor Target
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
priority: 0
# collect metrics content
fields:
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: host
type: 1
i18n:
zh-CN: Host
en-US: Host
- field: port
type: 1
i18n:
zh-CN: Port
en-US: Port
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
protocol: http_sd
# the config content when protocol is http_sd
http_sd:
url: ^_^__sd_url__^_^
@@ -14,7 +14,7 @@
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
category: service
category: __system__
# Monitoring application type name (consistent with file name) eg: linux windows tomcat mysql aws...
app: push
# The app api i18n name
@@ -207,12 +207,12 @@ metrics:
# collect metrics content
fields:
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: Content-Type
- field: content_type
type: 1
i18n:
zh-CN: Content Type
en-US: Content Type
- field: Content-Length
- field: content_length
type: 1
i18n:
zh-CN: 响应内容长度
@@ -145,15 +145,4 @@ class MonitorsControllerTest {
.andExpect(jsonPath("$.code").value("0"))
.andExpect(jsonPath("$.msg").value("Import success"));
}
@Test
void duplicateMonitors() throws Exception {
// Mock the behavior of monitorService.copyMonitors
doNothing().when(monitorService).copyMonitors(List.of(6565463543L));
// Perform the POST request and verify the response
this.mockMvc.perform(MockMvcRequestBuilders.post("/api/monitors/copy")
.param("ids", "6565463543"))
.andExpect(status().isOk());
}
}
@@ -17,13 +17,8 @@
package org.apache.hertzbeat.manager.service;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import java.util.Collections;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.manager.dao.DefineDao;
import org.apache.hertzbeat.manager.dao.MonitorDao;
import org.apache.hertzbeat.manager.service.impl.AppServiceImpl;
import org.apache.hertzbeat.manager.service.impl.ObjectStoreConfigServiceImpl;
@@ -35,6 +30,15 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* Test case for {@link AppService}
*/
@@ -47,6 +51,9 @@ class AppServiceTest {
@Mock
private MonitorDao monitorDao;
@Mock
private DefineDao defineDao;
@Mock
private WarehouseService warehouseService;
@@ -55,6 +62,7 @@ class AppServiceTest {
@BeforeEach
void setUp() throws Exception {
when(defineDao.findAll()).thenReturn(new ArrayList<>());
appService.afterPropertiesSet();
}
@@ -744,7 +744,7 @@ class MonitorServiceTest {
List<Param> params = Collections.singletonList(new Param());
when(monitorDao.findById(1L)).thenReturn(Optional.of(monitor));
when(paramDao.findParamsByMonitorId(1L)).thenReturn(params);
assertDoesNotThrow(() -> monitorService.copyMonitors(List.of(1L)));
assertDoesNotThrow(() -> monitorService.copyMonitor(1L));
}
}
@@ -24,7 +24,7 @@
<version>2.0-SNAPSHOT</version>
</parent>
<artifactId>hertzbeat-log</artifactId>
<artifactId>hertzbeat-otel</artifactId>
<name>${project.artifactId}</name>
<properties>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
@@ -43,25 +43,14 @@
<artifactId>hertzbeat-warehouse</artifactId>
</dependency>
<!-- OpenTelemetry -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-logs</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-appender-1.0</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.log.config;
package org.apache.hertzbeat.otel.config;
import org.apache.hertzbeat.common.constants.ConfigConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
@@ -0,0 +1,154 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.hertzbeat.otel.config;
import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;
import static org.apache.http.HttpHeaders.CONTENT_TYPE;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
import io.opentelemetry.sdk.resources.Resource;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* OpenTelemetryConfig provides customizations for the auto-configured OpenTelemetry SDK,
* specifically for integrating with GrepTimeDB for logs and traces.
*/
@Configuration
@Slf4j
public class OpenTelemetryConfig {
private static final String HERTZBEAT_SERVICE_NAME = "HertzBeat";
private static final String DEFAULT_GREPTIME_DB_NAME = "public";
private static final String DEFAULT_LOGS_TABLE_NAME = "hzb_logs";
private static final String DEFAULT_TRACES_TABLE_NAME = "hzb_traces";
private static final String GREPTIME_DB_NAME_HEADER = "X-Greptime-DB-Name";
private static final String GREPTIME_LOG_TABLE_NAME_HEADER = "X-Greptime-Log-Table-Name";
private static final String GREPTIME_TRACE_TABLE_NAME_HEADER = "X-Greptime-Trace-Table-Name";
private static final String GREPTIME_PIPELINE_NAME_HEADER = "X-Greptime-Pipeline-Name";
/**
* Adds authentication headers if credentials are provided.
*/
private void addAuthenticationHeaders(Map<String, String> headers, GreptimeProperties greptimeProps) {
if (greptimeProps != null && StringUtils.isNotBlank(greptimeProps.username())
&& StringUtils.isNotBlank(greptimeProps.password())) {
String credentials = greptimeProps.username() + ":" + greptimeProps.password();
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
headers.put("Authorization", "Basic " + encodedCredentials);
log.debug("Added Basic Authentication header for GreptimeDB.");
} else {
log.debug("GreptimeDB username/password not configured, skipping Authentication header.");
}
}
/**
* Builds HTTP Log headers for OTLP communication with GreptimeDB.
*/
private Map<String, String> buildGreptimeOtlpLogHeaders(GreptimeProperties greptimeProps) {
Map<String, String> headers = new HashMap<>();
headers.put(GREPTIME_DB_NAME_HEADER, DEFAULT_GREPTIME_DB_NAME);
headers.put(GREPTIME_LOG_TABLE_NAME_HEADER, DEFAULT_LOGS_TABLE_NAME);
addAuthenticationHeaders(headers, greptimeProps);
return Collections.unmodifiableMap(headers);
}
/**
* Builds HTTP Trace headers for OTLP communication with GreptimeDB.
*/
private Map<String, String> buildGreptimeOtlpTraceHeaders(GreptimeProperties greptimeProps) {
Map<String, String> headers = new HashMap<>();
headers.put(GREPTIME_DB_NAME_HEADER, DEFAULT_GREPTIME_DB_NAME);
headers.put(GREPTIME_TRACE_TABLE_NAME_HEADER, DEFAULT_TRACES_TABLE_NAME);
headers.put(CONTENT_TYPE, "application/x-protobuf");
headers.put(GREPTIME_PIPELINE_NAME_HEADER, "greptime_trace_v1");
addAuthenticationHeaders(headers, greptimeProps);
return Collections.unmodifiableMap(headers);
}
/**
* Provides an AutoConfigurationCustomizerProvider to tailor the auto-configured OpenTelemetry SDK.
* This includes setting up GrepTimeDB exporters for logs and traces, and customizing the resource.
* Active only if 'greptime.enabled' is true.
*
* @param greptimeProperties Configuration for GrepTimeDB.
* @return AutoConfigurationCustomizerProvider instance.
*/
@Bean
@ConditionalOnProperty(name = "warehouse.store.greptime.enabled", havingValue = "true")
public AutoConfigurationCustomizerProvider greptimeOtelCustomizer(GreptimeProperties greptimeProperties) {
log.info("GreptimeDB is enabled. Applying OpenTelemetry SDK customizations.");
return providerCustomizer -> providerCustomizer
.addPropertiesCustomizer(sdkConfigProperties -> {
Map<String, String> newProperties = new HashMap<>();
newProperties.put("otel.metrics.exporter", "none");
newProperties.put("otel.traces.exporter", "otlp");
newProperties.put("otel.logs.exporter", "none");
return newProperties;
})
.addResourceCustomizer((resource, configProperties) -> {
log.info("Customizing auto-configured OpenTelemetry Resource with service name: {}.", HERTZBEAT_SERVICE_NAME);
return resource.merge(Resource.builder().put(SERVICE_NAME, HERTZBEAT_SERVICE_NAME).build());
})
.addSpanExporterCustomizer((originalSpanExporter, configProperties) -> {
String traceEndpoint = greptimeProperties.httpEndpoint() + "/v1/otlp/v1/traces";
log.info("Programmatically configuring OtlpHttpSpanExporter for GreptimeDB traces. Endpoint: {}", traceEndpoint);
Map<String, String> traceHeaders = buildGreptimeOtlpTraceHeaders(greptimeProperties);
log.info("Trace Headers for GreptimeDB (programmatic HTTP config): {}", traceHeaders);
OtlpHttpSpanExporterBuilder httpExporterBuilder = OtlpHttpSpanExporter.builder()
.setEndpoint(traceEndpoint)
.setHeaders(() -> traceHeaders)
.setTimeout(10000, TimeUnit.MILLISECONDS);
return httpExporterBuilder.build();
})
.addLoggerProviderCustomizer((sdkLoggerProviderBuilder, configProperties) -> {
log.info("Customizing auto-configured SdkLoggerProviderBuilder for GrepTimeDB logs.");
OtlpHttpLogRecordExporter logExporter = OtlpHttpLogRecordExporter.builder()
.setEndpoint(greptimeProperties.httpEndpoint() + "/v1/otlp/v1/logs")
.setHeaders(() -> buildGreptimeOtlpLogHeaders(greptimeProperties))
.setTimeout(10000, TimeUnit.MILLISECONDS)
.build();
BatchLogRecordProcessor batchLogProcessor = BatchLogRecordProcessor.builder(logExporter)
.setScheduleDelay(1000, TimeUnit.MILLISECONDS)
.setMaxExportBatchSize(512)
.build();
return sdkLoggerProviderBuilder.addLogRecordProcessor(batchLogProcessor);
});
}
}
@@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.hertzbeat.otel.config;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
/**
* Installs the OpenTelemetryAppender for Logback once the auto-configured
* OpenTelemetry SDK is available and GrepTimeDB integration is enabled.
*/
@Component
@ConditionalOnProperty(name = "warehouse.store.greptime.enabled", havingValue = "true")
@Slf4j
public class OpenTelemetryLogbackAppenderInstaller implements InitializingBean {
private final OpenTelemetry openTelemetry;
@Autowired
public OpenTelemetryLogbackAppenderInstaller(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}
@Override
public void afterPropertiesSet() {
if (this.openTelemetry instanceof OpenTelemetrySdk) {
log.info("Auto-configured OpenTelemetry SDK detected. Installing OpenTelemetryAppender for Logback.");
OpenTelemetryAppender.install(this.openTelemetry);
} else {
log.warn("OpenTelemetry SDK is not an instance of OpenTelemetrySdk (type: {}). "
+ "OpenTelemetryAppender for Logback will not be installed. "
+ "Ensure OpenTelemetry auto-configuration is active and correctly providing an SDK.",
this.openTelemetry != null ? this.openTelemetry.getClass().getName() : "null");
}
}
}
@@ -14,4 +14,4 @@
# limitations under the License.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.hertzbeat.log.config.LogAutoConfiguration
org.apache.hertzbeat.otel.config.LogAutoConfiguration
@@ -25,6 +25,7 @@ import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAmount;
import java.util.Arrays;
@@ -33,6 +34,9 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
@@ -76,11 +80,13 @@ import static org.apache.hertzbeat.common.constants.ConfigConstants.FunctionModu
@Slf4j
public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorage {
private static final String IMPORT_PATH = "/api/v1/import";
private static final String EXPORT_PATH = "/api/v1/export";
private static final String STATUS_PATH = "/api/v1/status/tsdb";
private static final String VM_INSERT_BASE_PATH = "/insert/%s/%s";
private static final String VM_SELECT_BASE_PATH = "/select/%s/%s";
private static final String IMPORT_PATH = "prometheus/api/v1/import";
private static final String EXPORT_PATH = "prometheus/api/v1/export";
private static final String STATUS_PATH = "prometheus/api/v1/status/tsdb";
private static final String STATUS_SUCCESS = "success";
private static final String QUERY_RANGE_PATH = "/api/v1/query_range";
private static final String QUERY_RANGE_PATH = "prometheus/api/v1/query_range";
private static final String LABEL_KEY_NAME = "__name__";
private static final String LABEL_KEY_JOB = "job";
private static final String LABEL_KEY_INSTANCE = "instance";
@@ -88,6 +94,7 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
private static final String MONITOR_METRICS_KEY = "__metrics__";
private static final String MONITOR_METRIC_KEY = "__metric__";
private final VictoriaMetricsClusterProperties vmClusterProps;
private final VictoriaMetricsInsertProperties vmInsertProps;
private final VictoriaMetricsSelectProperties vmSelectProps;
@@ -100,6 +107,7 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
throw new IllegalArgumentException("please config Warehouse victoriaMetrics cluster props");
}
this.restTemplate = restTemplate;
this.vmClusterProps = vmClusterProps;
this.vmInsertProps = vmClusterProps.insert();
this.vmSelectProps = vmClusterProps.select();
serverAvailable = checkVictoriaMetricsDatasourceAvailable();
@@ -108,7 +116,23 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
private boolean checkVictoriaMetricsDatasourceAvailable() {
// check server status
try {
String result = restTemplate.getForObject(vmSelectProps.url() + STATUS_PATH, String.class);
String selectNodeStatusUrl = vmClusterProps.select().url() + VM_SELECT_BASE_PATH.formatted(vmClusterProps.accountID(), STATUS_PATH);
HttpHeaders headers = new HttpHeaders();
if (StringUtils.hasText(vmInsertProps.username())
&& StringUtils.hasText(vmInsertProps.password())) {
String authStr = vmInsertProps.username() + ":" + vmInsertProps.password();
String encodedAuth = Base64Util.encode(authStr);
headers.add(HttpHeaders.AUTHORIZATION, NetworkConstants.BASIC + SignConstants.BLANK + encodedAuth);
}
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
ResponseEntity<String> responseEntity = restTemplate.exchange(
selectNodeStatusUrl,
HttpMethod.GET,
requestEntity,
String.class
);
String result = responseEntity.getBody();
JsonNode jsonNode = JsonUtil.fromJson(result);
if (jsonNode != null && STATUS_SUCCESS.equalsIgnoreCase(jsonNode.get(STATUS).asText())) {
@@ -225,7 +249,8 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
stringBuilder.append(JsonUtil.toJson(content)).append("\n");
}
HttpEntity<String> httpEntity = new HttpEntity<>(stringBuilder.toString(), headers);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(vmInsertProps.url() + IMPORT_PATH,
String importUrl = vmClusterProps.insert().url() + VM_INSERT_BASE_PATH.formatted(vmClusterProps.accountID(), IMPORT_PATH);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(importUrl,
httpEntity, String.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
log.debug("insert metrics data to victoria-metrics success.");
@@ -252,9 +277,11 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
if (CommonConstants.PROMETHEUS.equals(app)) {
labelName = metrics;
}
String timeSeriesSelector =
LABEL_KEY_NAME + "=\"" + labelName + "\"" + "," + LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"" + ","
+ (CommonConstants.PROMETHEUS.equals(app) ? "" : "," + MONITOR_METRIC_KEY + "=\"" + metric + "\"");
String timeSeriesSelector = Stream.of(
LABEL_KEY_NAME + "=\"" + labelName + "\"",
LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"",
CommonConstants.PROMETHEUS.equals(app) ? null : MONITOR_METRIC_KEY + "=\"" + metric + "\""
).filter(Objects::nonNull).collect(Collectors.joining(","));
Map<String, List<Value>> instanceValuesMap = new HashMap<>(8);
try {
HttpHeaders headers = new HttpHeaders();
@@ -267,11 +294,15 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
+ SignConstants.BLANK + encodedAuth);
}
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
URI uri = UriComponentsBuilder.fromHttpUrl(vmSelectProps.url() + EXPORT_PATH)
.queryParam(URLEncoder.encode("match[]", StandardCharsets.UTF_8),
URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
.queryParam("start", URLEncoder.encode("now-" + history, StandardCharsets.UTF_8))
.queryParam("end", "now").build(true).toUri();
Instant end = Instant.now();
Duration duration = Duration.ofHours(Long.parseLong(history.replace("h", "")));
Instant start = end.minus(duration);
String exportUrl = vmClusterProps.select().url() + VM_SELECT_BASE_PATH.formatted(vmClusterProps.accountID(), EXPORT_PATH);
URI uri = UriComponentsBuilder.fromHttpUrl(exportUrl)
.queryParam("match", URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
.queryParam("start", String.valueOf(start.getEpochSecond()))
.queryParam("end", String.valueOf(end.getEpochSecond()))
.build(true).toUri();
ResponseEntity<String> responseEntity = restTemplate.exchange(uri, HttpMethod.GET, httpEntity,
String.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
@@ -347,9 +378,11 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
if (CommonConstants.PROMETHEUS.equals(app)) {
labelName = metrics;
}
String timeSeriesSelector =
LABEL_KEY_NAME + "=\"" + labelName + "\"" + "," + LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"" + ","
+ (CommonConstants.PROMETHEUS.equals(app) ? "" : "," + MONITOR_METRIC_KEY + "=\"" + metric + "\"");
String timeSeriesSelector = Stream.of(
LABEL_KEY_NAME + "=\"" + labelName + "\"",
LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"",
CommonConstants.PROMETHEUS.equals(app) ? null : MONITOR_METRIC_KEY + "=\"" + metric + "\""
).filter(Objects::nonNull).collect(Collectors.joining(","));
Map<String, List<Value>> instanceValuesMap = new HashMap<>(8);
try {
HttpHeaders headers = new HttpHeaders();
@@ -362,10 +395,13 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
+ SignConstants.BLANK + encodedAuth);
}
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
URI uri = UriComponentsBuilder.fromHttpUrl(vmSelectProps.url() + QUERY_RANGE_PATH)
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8),
URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
.queryParam("step", "4h").queryParam("start", startTime).queryParam("end", endTime).build(true)
String rangeUrl = VM_SELECT_BASE_PATH.formatted(vmClusterProps.accountID(), QUERY_RANGE_PATH);
URI uri = UriComponentsBuilder.fromHttpUrl(rangeUrl)
.queryParam("query", URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
.queryParam("step", "4h")
.queryParam("start", startTime)
.queryParam("end", endTime)
.build(true)
.toUri();
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri, HttpMethod.GET,
httpEntity, PromQlQueryContent.class);
@@ -400,10 +436,12 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
log.error("query metrics data from victoria-metrics failed. {}", responseEntity);
}
// max
uri = UriComponentsBuilder.fromHttpUrl(vmSelectProps.url() + QUERY_RANGE_PATH)
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8),
URLEncoder.encode("max_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
.queryParam("step", "4h").queryParam("start", startTime).queryParam("end", endTime).build(true)
uri = UriComponentsBuilder.fromHttpUrl(rangeUrl)
.queryParam("query", URLEncoder.encode("max_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
.queryParam("step", "4h")
.queryParam("start", startTime)
.queryParam("end", endTime)
.build(true)
.toUri();
responseEntity = restTemplate.exchange(uri, HttpMethod.GET, httpEntity, PromQlQueryContent.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
@@ -436,10 +474,12 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
}
}
// min
uri = UriComponentsBuilder.fromHttpUrl(vmSelectProps.url() + QUERY_RANGE_PATH)
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8),
URLEncoder.encode("min_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
.queryParam("step", "4h").queryParam("start", startTime).queryParam("end", endTime).build(true)
uri = UriComponentsBuilder.fromHttpUrl(rangeUrl)
.queryParam("query", URLEncoder.encode("min_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
.queryParam("step", "4h")
.queryParam("start", startTime)
.queryParam("end", endTime)
.build(true)
.toUri();
responseEntity = restTemplate.exchange(uri, HttpMethod.GET, httpEntity, PromQlQueryContent.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
@@ -472,10 +512,12 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
}
}
// avg
uri = UriComponentsBuilder.fromHttpUrl(vmSelectProps.url() + QUERY_RANGE_PATH)
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8),
URLEncoder.encode("avg_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
.queryParam("step", "4h").queryParam("start", startTime).queryParam("end", endTime).build(true)
uri = UriComponentsBuilder.fromHttpUrl(rangeUrl)
.queryParam("query", URLEncoder.encode("avg_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
.queryParam("step", "4h")
.queryParam("start", startTime)
.queryParam("end", endTime)
.build(true)
.toUri();
responseEntity = restTemplate.exchange(uri, HttpMethod.GET, httpEntity, PromQlQueryContent.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
@@ -21,6 +21,7 @@ import org.apache.hertzbeat.common.constants.ConfigConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.warehouse.constants.WarehouseConstants;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
/**
* Victoriametrics configuration information
@@ -32,6 +33,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
+ SignConstants.DOT
+ WarehouseConstants.HistoryName.VM_CLUSTER)
public record VictoriaMetricsClusterProperties(
@DefaultValue("false") boolean enabled,
@DefaultValue("0") String accountID,
VictoriaMetricsInsertProperties insert,
VictoriaMetricsSelectProperties select
) {
-8
View File
@@ -53,18 +53,10 @@ Bug fix.
5. [[collector]bugfix: fix warehouse data queue consume error #153](https://github.com/apache/hertzbeat/pull/153). issue by @daqianxiaoyao
6. [[web-app]bugfix:fix input blocking when input error in dark theme #157](https://github.com/apache/hertzbeat/pull/157). issue by @ConradWen
**Full Changelog**: <https://github.com/apache/hertzbeat/compare/v1.0-beta.8...v1.0>
Online <https://console.tancloud.cn>.
-----------------------
Redis monitor is coming
<img width="1910" alt="2022-05-29 20 23 58" src="https://user-images.githubusercontent.com/24788200/170868079-325ccc08-165f-4d0e-9ebb-18b0b5c9db3f.png"/>
<img width="959" alt="2022-05-29 20 24 21" src="https://user-images.githubusercontent.com/24788200/170868094-3c4de70f-934a-4a13-ae1a-0744c30f14f3.png"/>
> [HertzBeat](https://github.com/apache/hertzbeat), incubated by [Dromara](https://dromara.org) and open-sourced by [TanCloud](https://tancloud.cn), is an open-source monitoring and alerting project with a user-friendly visual interface that supports monitoring types such as websites, APIs, PING, ports, databases, operating systems, and more.
> Of course, we also provide a corresponding [SaaS cloud monitoring version](https://console.tancloud.cn), so small and medium-sized teams and individuals no longer need to deploy a cumbersome monitoring system to monitor their website resources; you can [log in](https://console.tancloud.cn) to start monitoring for free.
>
-59
View File
@@ -36,9 +36,6 @@ Bugfixes:
Online at <https://console.tancloud.cn>.
---
Windows Monitor coming
![HertzBeat](https://user-images.githubusercontent.com/24788200/174481159-b8a73c87-aff5-4c4c-befb-bd0d26685d71.png)
⚠️ ⚠️⚠️⚠️Please note that upgrading to v1.1.0 from other versions requires running the following SQL script. Now, our table names have a unified prefix "hzb_prefix".
@@ -60,62 +57,6 @@ Have Fun!
---
## V1.1.0
Home: hertzbeat.com | tancloud.cn
Hi guys! HertzBeat v1.1.0 is coming. This version we support snmp protocol and use snmp to collect windows metrics.
Another major change is that we use the H2 database by default to replace the MYSQL database as storage to facilitate the installation and deployment of users. Now only one docker command is needed to install and experience hertzbeat `docker run -d -p 1157:1157 --name hertzbeat apache/hertzbeat`
Let's Try It!
Thanks to the contributors! 👍👍
Feature
1. [[monitor]feature: support snmp collect protocol and windows monitor type #192](https://github.com/apache/hertzbeat/pull/192). contribute by @ChineseTony
2. [[monitor]change default database mysql to h2 #191](https://github.com/apache/hertzbeat/pull/191)
3. [[manager]support monitor params name i18n #184](https://github.com/apache/hertzbeat/pull/184).
4. [[script]build multi cpu arch hertzbeat docker version #189](https://github.com/apache/hertzbeat/pull/189).
5. [[monitor]feature: support oracle multi tablespaces #163](https://github.com/apache/hertzbeat/pull/163) contribute by @brave4Time
6. [[monitor]database tables append prefix hzb_ #193](https://github.com/apache/hertzbeat/pull/193) issue from @shimingxy
Bugfix.
1. [[monitor]fix can not collect cpu metrics in tencent centos #164](https://github.com/apache/hertzbeat/pull/164) contribute by @wyt199905 .
2. [[manager]fix oracle config yml percentage error #168](https://github.com/apache/hertzbeat/pull/168)
3. [[monitor] bugfix: fix elasticsearch collect error when need basic auth #174](https://github.com/apache/hertzbeat/pull/174) contribute by @weifuqing
4. [Change the Oracle database name to the service name to reduce ambiguity #182](https://github.com/apache/hertzbeat/pull/182) @zklmcookle
Online <https://console.tancloud.cn>.
---
Windows Monitor coming
<img width="1444" alt="2022-06-19 11 30 57" src="https://user-images.githubusercontent.com/24788200/174481159-b8a73c87-aff5-4c4c-befb-bd0d26685d71.png"/>
⚠️ ⚠️⚠️⚠️Attention other version upgrade to v1.1.0 need run sql script. Now the tables name has hzb_ prefix.
```properties
ALTER TABLE alert RENAME TO hzb_alert;
ALTER TABLE alert_define RENAME TO hzb_alert_define;
ALTER TABLE alert_define_monitor_bind RENAME TO hzb_alert_define_monitor_bind;
ALTER TABLE monitor RENAME TO hzb_monitor;
ALTER TABLE notice_receiver RENAME TO hzb_notice_receiver;
ALTER TABLE notice_rule RENAME TO hzb_notice_rule;
ALTER TABLE param RENAME TO hzb_param;
ALTER TABLE param_define RENAME TO hzb_param_define;
ALTER TABLE tag RENAME TO hzb_tag;
ALTER TABLE tag_monitor_bind RENAME TO hzb_tag_monitor_bind;
commit;
```
Have Fun!
Have Fun!
---
> [HertzBeat](https://github.com/apache/hertzbeat), incubated by [Dromara](https://dromara.org) and open-sourced by [TanCloud](https://tancloud.cn), is an open-source project supporting monitoring for websites, APIs, PING, ports, databases, operating systems, and more, with a user-friendly visual interface.
> We also offer a [SAAS cloud monitoring version](https://console.tancloud.cn), allowing small and medium teams and individuals to start monitoring their web resources without deploying a complicated monitoring system, simply [log in to start](https://console.tancloud.cn) your monitoring journey for free.
> HertzBeat supports custom monitoring; you can customize the monitoring types and metrics you need through configuration of the YML file to meet common personalized needs.
-58
View File
@@ -36,10 +36,6 @@ Bugfixes:
Online at [https://console.tancloud.cn](https://console.tancloud.cn).
---
Windows Monitor is coming:
![HertzBeat](https://user-images.githubusercontent.com/24788200/174481159-b8a73c87-aff5-4c4c-befb-bd0d26685d71.png)
⚠️ ⚠️⚠️⚠️Please note that upgrading to v1.1.0 from other versions requires running the following SQL script. Now, our table names have a unified prefix hzb_prefix.
```properties
@@ -60,60 +56,6 @@ Have Fun!
---
## V1.1.0
Home: hertzbeat.com | tancloud.cn
Hi guys! HertzBeat v1.1.0 is coming. This version we support snmp protocol and use snmp to collect windows metrics.
Another major change is that we use the H2 database by default to replace the MYSQL database as storage to facilitate the installation and deployment of users. Now only one docker command is needed to install and experience hertzbeat `docker run -d -p 1157:1157 --name hertzbeat apache/hertzbeat`
Let's Try It!
Thanks to the contributors! 👍👍
Feature
1. [[monitor]feature: support snmp collect protocol and windows monitor type #192](https://github.com/apache/hertzbeat/pull/192). contribute by @ChineseTony
2. [[monitor]change default database mysql to h2 #191](https://github.com/apache/hertzbeat/pull/191)
3. [[manager]support monitor params name i18n #184](https://github.com/apache/hertzbeat/pull/184).
4. [[script]build multi cpu arch hertzbeat docker version #189](https://github.com/apache/hertzbeat/pull/189).
5. [[monitor]feature: support oracle multi tablespaces #163](https://github.com/apache/hertzbeat/pull/163) contribute by @brave4Time
6. [[monitor]database tables append prefix hzb_ #193](https://github.com/apache/hertzbeat/pull/193) issue from @shimingxy
Bugfix.
1. [[monitor]fix can not collect cpu metrics in tencent centos #164](https://github.com/apache/hertzbeat/pull/164) contribute by @wyt199905 .
2. [[manager]fix oracle config yml percentage error #168](https://github.com/apache/hertzbeat/pull/168)
3. [[monitor] bugfix: fix elasticsearch collect error when need basic auth #174](https://github.com/apache/hertzbeat/pull/174) contribute by @weifuqing
4. [Change the Oracle database name to the service name to reduce ambiguity #182](https://github.com/apache/hertzbeat/pull/182) @zklmcookle
Online <https://console.tancloud.cn>.
---
Windows Monitor coming
<img width="1444" alt="2022-06-19 11 30 57" src="https://user-images.githubusercontent.com/24788200/174481159-b8a73c87-aff5-4c4c-befb-bd0d26685d71.png"/>
⚠️ ⚠️⚠️⚠️Attention other version upgrade to v1.1.0 need run sql script. Now the tables name has hzb_ prefix.
```properties
ALTER TABLE alert RENAME TO hzb_alert;
ALTER TABLE alert_define RENAME TO hzb_alert_define;
ALTER TABLE alert_define_monitor_bind RENAME TO hzb_alert_define_monitor_bind;
ALTER TABLE monitor RENAME TO hzb_monitor;
ALTER TABLE notice_receiver RENAME TO hzb_notice_receiver;
ALTER TABLE notice_rule RENAME TO hzb_notice_rule;
ALTER TABLE param RENAME TO hzb_param;
ALTER TABLE param_define RENAME TO hzb_param_define;
ALTER TABLE tag RENAME TO hzb_tag;
ALTER TABLE tag_monitor_bind RENAME TO hzb_tag_monitor_bind;
commit;
```
Have Fun!
---
> [HertzBeat](https://github.com/apache/hertzbeat), incubated by [Dromara](https://dromara.org) and open-sourced by [TanCloud](https://tancloud.cn), is an open-source monitoring and alerting project that supports monitoring types such as websites, APIs, PING, ports, databases, operating systems, etc., with a user-friendly and easy-to-use visual interface.
> We also offer a [SaaS cloud monitoring version](https://console.tancloud.cn) for small and medium teams and individuals, eliminating the need to deploy a complicated monitoring system to monitor their web resources. [Sign up to start](https://console.tancloud.cn) your monitoring journey for free.
> HertzBeat supports custom monitoring; by configuring the YML file, we can customize the required monitoring types and metrics to meet common personalized needs.
+1 -1
View File
@@ -7,7 +7,7 @@ author_image_url: https://avatars.githubusercontent.com/u/24788200?s=400&v=4
tags: [opensource]
---
> Friendly Cloud Monitoring Tool. | 易用友好的实时监控工具, 无需Agent, 强大自定义监控能力.
> Friendly Cloud Monitoring Tool.
**Home: [hertzbeat.com](https://hertzbeat.com)**
+3 -3
View File
@@ -60,7 +60,7 @@ Have Fun!
Need Convert `application.yml`.
```yaml
spring.
spring:
resources: static-locations.
static-locations.
- classpath:/dist/
@@ -70,8 +70,8 @@ spring.
To
```yaml
spring.
web.
spring:
web:
resources: static-locations.
static-locations.
- classpath:/dist/
-47
View File
@@ -54,50 +54,3 @@ Bugfix.
Online <https://console.tancloud.cn>.
Have Fun!
----
## V1.2.2
官网: hertzbeat.com | tancloud.cn
大家好,HertzBeat v1.2.2发布啦!这个版本带来个超多重大更新,我们支持了对云原生kubernets, docker的监控,支持了对springboot应用, nacos注册发现中心,达梦数据库,opengauss数据库等的指标监控。我们也引入了一个实验性特性,用户可以使用promethues promql 从promethues server拿取指标数据作为hertzbeat自定义监控指标数据。当然我们也新增了多个测试用户覆盖,修复了多个BUG。还有个很多用户想要的更新,我们新增了对linux监控的top10 cpu 内存利用率的进程监控指标。有个这个指标,我们就可以干很多事情。比如监控某个进程CPU异常,内存爆满啥的。快来试试吧!
只需要一条docker命令即可安装体验heartbeat
`docker run -d -p 1157:1157 --name hertzbeat apache/hertzbeat`
感谢hertzbeat贡献者们的贡献!👍👍
我们急需对测试用例,新增应用监控,文档等各方面的贡献者,非常欢迎您的加入。快来吧,HertzBeat上手非常简单!
Feature
1. [[manager,collector] support dm database monitor #410](https://github.com/apache/hertzbeat/pull/410) @TJxiaobao
2. [[home] add DM db document supplement #411](https://github.com/apache/hertzbeat/pull/411) @TJxiaobao
3. [[home] support algolia search #416](https://github.com/apache/hertzbeat/pull/416)
4. [[collector] support trigger and grading multiple subtasks through -_- placeholder expression #418](https://github.com/apache/hertzbeat/pull/418)
5. [WIP:feature support k8s monitor, http monitor nacos, service&http_micro monitor msa #421](https://github.com/apache/hertzbeat/pull/421) @cuipiheqiuqiu
6. [[manager] support opengauss database monitor #422](https://github.com/apache/hertzbeat/pull/422)
7. [[#406]\[warehose\] Add unit test MetricsDataControllerTest.java #426](https://github.com/apache/hertzbeat/pull/426) @haibo-duan
8. [[#358]\[manager\] Add unit test manager/service/NoticeConfigServiceTest.java #427](https://github.com/apache/hertzbeat/pull/427) @haibo-duan
9. [[#356]\[manager\] unit test case of manager/service/MailServiceTest.java #432](https://github.com/apache/hertzbeat/pull/432) @csyshu
10. [[manager,collector] support docker metrics monitor #438](https://github.com/apache/hertzbeat/pull/438) @TJxiaobao
11. [[alerter] implement AlertDefineControllerTest unit case #448](https://github.com/apache/hertzbeat/pull/448) @Ceilzcx
12. [[collector] support spi load AbstractCollect Impl instance #449](https://github.com/apache/hertzbeat/pull/449)
13. [[manager] support linux process top10 cpu_usage mem_usage #451](https://github.com/apache/hertzbeat/pull/451)
14. [[hertzbeat] support springboot2.0 metrics monitor #453](https://github.com/apache/hertzbeat/pull/453)
15. [[manager-monitors](增强)应用服务检测-网站检测-分页:添加默认name升序 enhancementmanager-… #455](https://github.com/apache/hertzbeat/pull/455) @luxx-lq
16. [[hertzbeat] update use PromQL to collect metrics from prometheus server #456](https://github.com/apache/hertzbeat/pull/456)
17. [[manager] support custom monitor api response data code #460](https://github.com/apache/hertzbeat/pull/460)
Bugfix.
1. [【bugfix#408】if logs dir not exist, create logs dir #409](https://github.com/apache/hertzbeat/pull/409) @Ceilzcx
2. [[warehouse] bugfix RealTimeRedisDataStorage wrong extend from #413](https://github.com/apache/hertzbeat/pull/413)
3. [end The query closed the dataSet #414](https://github.com/apache/hertzbeat/pull/414) @Ceilzcx
4. [[alerter] bugfix monitor status not change when alert #415](https://github.com/apache/hertzbeat/pull/415)
5. [[OS Monitor]bugfix:Fix cpu cores and interrupt acquisition under Orac… #424](https://github.com/apache/hertzbeat/pull/424) @assassinfym
6. [[manager] bugfix the gmtUpdate not change when update monitor param #459](https://github.com/apache/hertzbeat/pull/459)
7. [[home] fix typo in springboot2.md #464](https://github.com/apache/hertzbeat/pull/464) @eltociear
----
-44
View File
@@ -52,47 +52,3 @@ Bugfix.
Online <https://console.tancloud.cn>.
Have Fun!
----
## V1.2.3
官网: hertzbeat.com | tancloud.cn
大家好,HertzBeat v1.2.3发布啦!这个版本带来了重大更新,我们支持了对prometheus exporter协议监控,用户可以很方便的使用hertzbeat来适配监控prometheus exporter. 基于这个能力,这个版本我们也支持了对apache shenyu, apache iotdb的指标监控。我们更新了UI布局,修复了多个BUG,也支持了短信通知。快来体验下吧!
只需要一条docker命令即可安装体验heartbeat
`docker run -d -p 1157:1157 --name hertzbeat apache/hertzbeat`
感谢hertzbeat贡献者们的贡献!👍👍
我们急需对测试用例,新增应用监控,文档等各方面的贡献者,非常欢迎您的加入。快来吧,HertzBeat上手非常简单!
Feature
1. [[doc] note: startup via source code not required mysql and tdengine env #472](https://github.com/apache/hertzbeat/pull/472) @xingshuaiLi
2. [[doc] fix up:update the environment of hertzbeat to Java version 11 #473](https://github.com/apache/hertzbeat/pull/473) @BKing2020
3. [[docs] update kubernetes.md #478](https://github.com/apache/hertzbeat/pull/478) @wangke6666
4. [[web-app] enable alert define preset true by default #485](https://github.com/apache/hertzbeat/pull/485)
5. [[web-app] support friendly tip when add notice receiver #486](https://github.com/apache/hertzbeat/pull/486)
6. [[web-app] update dashboard category card ui #487](https://github.com/apache/hertzbeat/pull/487)
7. [[collector] limit trigger sub task max num #488](https://github.com/apache/hertzbeat/pull/488)
8. [[script] support service restart shell #489](https://github.com/apache/hertzbeat/pull/489) @zanglikun
9. [[docs] use rainbond deploy hertzbeat #495](https://github.com/apache/hertzbeat/pull/495) @zzzhangqi
10. [[webapp] upgrade web base angular version to 14 #501](https://github.com/apache/hertzbeat/pull/501)
11. [[hertzbeat] support sms alert notice #503](https://github.com/apache/hertzbeat/pull/503)
12. [add Prometheus exporter metrics parser and IoTDB monitor #505](https://github.com/apache/hertzbeat/pull/505) @Ceilzcx
13. [support apache shenyu metrics monitoring #507](https://github.com/apache/hertzbeat/pull/507)
Bugfix.
1. [[manager] fix cross domain problem in SecurityCorsConfiguration #469](https://github.com/apache/hertzbeat/pull/469) @zenan08
2. [[manager] bugfix linux cpu usage collect incorrect sometime #479](https://github.com/apache/hertzbeat/pull/479) @LWBobo
3. [[collector] fix protocol ssl_cert not support #491](https://github.com/apache/hertzbeat/pull/491)
4. [Update sqlserver.md #493](https://github.com/apache/hertzbeat/pull/493) @SuitSmile
5. [fix: Remove Alert Unused Monitoring IDs #502](https://github.com/apache/hertzbeat/pull/502) @wang1027-wqh
6. [[collector] bugfix npe when ssh collect error #508](https://github.com/apache/hertzbeat/pull/508)
7. [监控k8s问题issue描述与解决方案 #511](https://github.com/apache/hertzbeat/pull/511) @MrAndyMing
8. [[manager] springboot2 monitor support base path config #515](https://github.com/apache/hertzbeat/pull/515)
----
-4
View File
@@ -22,8 +22,6 @@ Website: hertzbeat.com | tancloud.cn
>
> Do you believe that you can immediately adapt a new monitoring type such as K8s or Docker just by defining YML?
![HertzBeat](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4236e748f5ac4352b7cf4bb65ccf97aa~tplv-k3u1fbpfcp-zoom-1.image)
**Github: <https://github.com/apache/hertzbeat>**
**Gitee: <https://gitee.com/hertzbeat/hertzbeat>**
@@ -34,8 +32,6 @@ After a month of iterations, HertzBeat v1.3.0 was officially released last weeke
- Hertzbeat has powerful custom monitoring capabilities, all our supported monitoring types are mapped to a YML. This time we bring custom monitoring pagination, welcome to use and share your own monitoring type definition.
![HertzBeat](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b5c9dd3e28c54c72b49a7470012a0c36~tplv-k3u1fbpfcp-zoom-1.image)
- **support for monitoring network switches**.
hertzbeat supported snmp protocol long time ago, windows monitoring is monitored by snmp protocol, this version we not only support more windows performance metrics, but also support snmp walk, adapt several common network switches monitoring, welcome to contribute more types and metrics to the community.
-2
View File
@@ -25,8 +25,6 @@ Website: hertzbeat.com | tancloud.cn
> `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)**.
![HertzBeat](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4236e748f5ac4352b7cf4bb65ccf97aa~tplv-k3u1fbpfcp-zoom-1.image)
**Github: <https://github.com/apache/hertzbeat>**
**Gitee: <https://gitee.com/hertzbeat/hertzbeat>**
-2
View File
@@ -25,8 +25,6 @@ Website: hertzbeat.com | tancloud.cn
> `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)**.
![HertzBeat](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4236e748f5ac4352b7cf4bb65ccf97aa~tplv-k3u1fbpfcp-zoom-1.image)
**Github: <https://github.com/apache/hertzbeat>**
**Gitee: <https://gitee.com/hertzbeat/hertzbeat>**
-24
View File
@@ -116,8 +116,6 @@ Detailed config refer to [Install HertzBeat via Docker](https://hertzbeat.com/do
* @XiaTian688 made their first contribution in <https://github.com/apache/hertzbeat/pull/1308>
* @liyin made their first contribution in <https://github.com/apache/hertzbeat/pull/1311>
**Full Changelog**: <https://github.com/apache/hertzbeat/compare/v1.4.1...v1.4.2>
---
## ⛄ Supported
@@ -135,25 +133,3 @@ Detailed config refer to [Install HertzBeat via Docker](https://hertzbeat.com/do
**Github: <https://github.com/apache/hertzbeat>**
**Gitee: <https://gitee.com/hertzbeat/hertzbeat>**
### **Download Link**
**hertzbeat server**
* ⬇️ [hertzbeat-1.4.2.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-1.4.2.tar.gz)
* ⬇️ [hertzbeat-1.4.2.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-1.4.2.zip)
* ⬇️ [hertzbeat-linux_amd64_1.4.2.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-linux_amd64_1.4.2.tar.gz)
* ⬇️ [hertzbeat-linux_arm64_1.4.2.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-linux_arm64_1.4.2.tar.gz)
* ⬇️ [hertzbeat-macos_arm64_1.4.2.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-macos_arm64_1.4.2.tar.gz)
* ⬇️ [hertzbeat-macos_amd64_1.4.2.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-macos_amd64_1.4.2.tar.gz)
* ⬇️ [hertzbeat-windows64_1.4.2.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-windows64_1.4.2.zip)
**hertzbeat collector**
* ⬇️ [hertzbeat-collector-1.4.2.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-collector-1.4.2.tar.gz)
* ⬇️ [hertzbeat-collector-1.4.2.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-collector-1.4.2.zip)
* ⬇️ [hertzbeat-collector-linux_amd64_1.4.2.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-collector-linux_amd64_1.4.2.tar.gz)
* ⬇️ [hertzbeat-collector-linux_arm64_1.4.2.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-collector-linux_arm64_1.4.2.tar.gz)
* ⬇️ [hertzbeat-collector-macos_arm64_1.4.2.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-collector-macos_arm64_1.4.2.tar.gz)
* ⬇️ [hertzbeat-collector-macos_amd64_1.4.2.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-collector-macos_amd64_1.4.2.tar.gz)
* ⬇️ [hertzbeat-collector-windows64_1.4.2.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.2/hertzbeat-collector-windows64_1.4.2.zip)
-24
View File
@@ -130,8 +130,6 @@ Detailed config refer to [Install HertzBeat via Docker](https://hertzbeat.com/do
* @jinyaoMa made their first contribution in <https://github.com/apache/hertzbeat/pull/1379>
* @LinuxSuRen made their first contribution in <https://github.com/apache/hertzbeat/pull/1387>
**Full Changelog**: <https://github.com/apache/hertzbeat/compare/v1.4.2...v1.4.3>
---
## ⛄ Supported
@@ -149,25 +147,3 @@ Detailed config refer to [Install HertzBeat via Docker](https://hertzbeat.com/do
**Github: <https://github.com/apache/hertzbeat>**
**Gitee: <https://gitee.com/hertzbeat/hertzbeat>**
### **Download Link**
**hertzbeat server**
* ⬇️ [hertzbeat-1.4.3.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-1.4.3.tar.gz)
* ⬇️ [hertzbeat-1.4.3.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-1.4.3.zip)
* ⬇️ [hertzbeat-linux_amd64_1.4.3.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-linux_amd64_1.4.3.tar.gz)
* ⬇️ [hertzbeat-linux_arm64_1.4.3.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-linux_arm64_1.4.3.tar.gz)
* ⬇️ [hertzbeat-macos_arm64_1.4.3.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-macos_arm64_1.4.3.tar.gz)
* ⬇️ [hertzbeat-macos_amd64_1.4.3.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-macos_amd64_1.4.3.tar.gz)
* ⬇️ [hertzbeat-windows64_1.4.3.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-windows64_1.4.3.zip)
**hertzbeat collector**
* ⬇️ [hertzbeat-collector-1.4.3.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-collector-1.4.3.tar.gz)
* ⬇️ [hertzbeat-collector-1.4.3.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-collector-1.4.3.zip)
* ⬇️ [hertzbeat-collector-linux_amd64_1.4.3.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-collector-linux_amd64_1.4.3.tar.gz)
* ⬇️ [hertzbeat-collector-linux_arm64_1.4.3.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-collector-linux_arm64_1.4.3.tar.gz)
* ⬇️ [hertzbeat-collector-macos_arm64_1.4.3.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-collector-macos_arm64_1.4.3.tar.gz)
* ⬇️ [hertzbeat-collector-macos_amd64_1.4.3.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-collector-macos_amd64_1.4.3.tar.gz)
* ⬇️ [hertzbeat-collector-windows64_1.4.3.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.3/hertzbeat-collector-windows64_1.4.3.zip)
-28
View File
@@ -144,8 +144,6 @@ Detailed config refer to [Install HertzBeat via Docker](https://hertzbeat.com/do
* @tslj1024 made their first contribution in <https://github.com/apache/hertzbeat/pull/1461>
* @ldysdu made their first contribution in <https://github.com/apache/hertzbeat/pull/1455>
**Full Changelog**: <https://github.com/apache/hertzbeat/compare/v1.4.3...v1.4.4>
---
## ⛄ Supported
@@ -163,29 +161,3 @@ Detailed config refer to [Install HertzBeat via Docker](https://hertzbeat.com/do
**Github: <https://github.com/apache/hertzbeat>**
**Gitee: <https://gitee.com/hertzbeat/hertzbeat>**
### **Download Link**
**hertzbeat server**
* ⬇️ [hertzbeat-1.4.4.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-1.4.4.tar.gz)
* ⬇️ [hertzbeat-1.4.4.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-1.4.4.zip)
* ⬇️ [hertzbeat-linux_amd64_1.4.4.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-linux_amd64_1.4.4.tar.gz)
* ⬇️ [hertzbeat-linux_arm64_1.4.4.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-linux_arm64_1.4.4.tar.gz)
* ⬇️ [hertzbeat-macos_arm64_1.4.4.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-macos_arm64_1.4.4.tar.gz)
* ⬇️ [hertzbeat-macos_amd64_1.4.4.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-macos_amd64_1.4.4.tar.gz)
* ⬇️ [hertzbeat-windows64_1.4.4.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-windows64_1.4.4.zip)
**hertzbeat collector**
* ⬇️ [hertzbeat-collector-1.4.4.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-collector-1.4.4.tar.gz)
* ⬇️ [hertzbeat-collector-1.4.4.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-collector-1.4.4.zip)
* ⬇️ [hertzbeat-collector-linux_amd64_1.4.4.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-collector-linux_amd64_1.4.4.tar.gz)
* ⬇️ [hertzbeat-collector-linux_arm64_1.4.4.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-collector-linux_arm64_1.4.4.tar.gz)
* ⬇️ [hertzbeat-collector-macos_arm64_1.4.4.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-collector-macos_arm64_1.4.4.tar.gz)
* ⬇️ [hertzbeat-collector-macos_amd64_1.4.4.tar.gz](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-collector-macos_amd64_1.4.4.tar.gz)
* ⬇️ [hertzbeat-collector-windows64_1.4.4.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.4/hertzbeat-collector-windows64_1.4.4.zip)
**hertzbeat docker compose script**
* ⬇️ [docker-compose.zip](https://github.com/apache/hertzbeat/releases/download/v1.4.4/docker-compose.zip)
@@ -97,7 +97,7 @@ Due to the Apache Foundation's requirements for license compliance, HertzBeat's
- MySQL: [https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.25.zip](https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.25.zip)
- Oracle (If you want to monitor Oracle, these two drivers are required):
- [https://download.oracle.com/otn-pub/otn_software/jdbc/234/ojdbc8.jar](https://download.oracle.com/otn-pub/otn_software/jdbc/234/ojdbc8.jar)
- [https://repo1.maven.org/maven2/com/oracle/database/jdbc/ojdbc8/23.4.0.24.05/ojdbc8-23.4.0.24.05.jar](https://repo1.maven.org/maven2/com/oracle/database/jdbc/ojdbc8/23.4.0.24.05/ojdbc8-23.4.0.24.05.jar)
- [https://repo.mavenlibs.com/maven/com/oracle/database/nls/orai18n/21.5.0.0/orai18n-21.5.0.0.jar](https://repo.mavenlibs.com/maven/com/oracle/database/nls/orai18n/21.5.0.0/orai18n-21.5.0.0.jar)
Next, run the start-up script as before to experience the latest HertzBeat 1.6.0!
@@ -0,0 +1,94 @@
# HertzBeat Upgrade Guide (Docker Mode)
## Docker-based Upgrade
### 1. Data Backup
- **Back up the database**: Manually back up MySQL data as needed.
```bash
mysqldump -h<HOST-IP> -P<PORT> -uroot -p"PASSWORD" <DB_NAME> > hertzbeat_backup-`date +%Y-%m-%d`.sql # Single database
mysqldump -h<HOST-IP> -P<PORT> -uroot -p"PASSWORD" --all-databases > hertzbeat_backup-`date +%Y-%m-%d`.sql # Full database
```
- **Back up configuration files and data directory**:
```bash
mv application.yml application-bak.yml && mv sureness.yml sureness-bak.yml
cp -R data data-`date +%Y-%m-%d`.bak
```
### 2. Stop and Remove the HertzBeat Container
```bash
docker stop hertzbeat && docker rm hertzbeat
```
### 3. Upgrade Database Schema
Navigate to [HertzBeat GitHub Migration Scripts](https://github.com/apache/hertzbeat/tree/master/hertzbeat-manager/src/main/resources/db/migration), select the appropriate `V160__update_column.sql` file under your database type (e.g., MySQL), and execute it in MySQL.
### 4. Restart HertzBeat with the New Image
```bash
docker run -d -p 1157:1157 -p 1158:1158 \
-v $(pwd)/data:/opt/hertzbeat/data \
-v $(pwd)/logs:/opt/hertzbeat/logs \
-v $(pwd)/application.yml:/opt/hertzbeat/config/application.yml \
-v $(pwd)/sureness.yml:/opt/hertzbeat/config/sureness.yml \
--restart=always \
--name hertzbeat apache/hertzbeat:v1.7.0
```
### 5. Update Configuration Files
Modify the backup configurations as needed:
- **`application.yml`** (Typical modifications):
```yaml
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/hertzbeat?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
hikari:
max-lifetime: 120000
jpa:
show-sql: false
database-platform: org.eclipse.persistence.platform.database.MySQLPlatform
database: mysql
properties:
eclipselink:
logging:
level: SEVERE
```
- **`sureness.yml`** (Optional, modify for account/password changes):
```yaml
account:
- appId: admin
credential: hertzbeat
role: [admin]
- appId: tom
credential: hertzbeat
role: [user]
- appId: guest
credential: hertzbeat
role: [guest]
- appId: lili
credential: 94C6B34E7A199A9F9D4E1F208093B489
salt: 123
role: [user]
```
### 6. Add Database Drivers
Due to Apache Foundations license compliance requirements, HertzBeat cannot include GPL-licensed dependencies (e.g., MySQL, Oracle). Users must manually download drivers and place them in the `ext-lib` directory, then mount it to `/opt/hertzbeat/ext-lib`:
- **MySQL Driver**: [Download MySQL Connector/J 8.0.25](https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.25.zip)
- **Oracle Driver** (Required for Oracle monitoring):
- [ojdbc8.jar](https://download.oracle.com/otn-pub/otn_software/jdbc/234/ojdbc8.jar)
- [orai18n-21.5.0.0.jar](https://repo.mavenlibs.com/maven/com/oracle/database/nls/orai18n/21.5.0.0/orai18n-21.5.0.0.jar)
@@ -0,0 +1,95 @@
# HertzBeat Upgrade Guide from v1.6.1 to v1.7.0 (Helm Mode)
## 1. Prerequisites
1. Ensure the following tools are installed:
- Helm 3.x
- kubectl
- Git (optional)
2. Verify current deployment information:
```bash
helm list -n <your-namespace>
# If the old chart package is missing, export values.yaml with:
helm get values hertzbeat -n <your-namespace> > hertzbeat-1.6.1-values.yaml
```
3. Data Backup:
> **For custom monitoring templates:**
>
> - Backup `/opt/hertzbeat/define` from the running pod:
>
> ```bash
> kubectl cp hertzbeat/hertzbeat-978477f84-fr894:/opt/hertzbeat/define ./define
> ```
>
> **For external databases (MySQL/PostgreSQL):**
>
> - Use `mysqldump`/`pg_dump` or copy PVC directories:
>
> ```bash
> kubectl get pvc -n hertzbeat
> ```
## 2. Upgrade Steps
### 1. Pull the latest Chart
```bash
helm repo update
helm pull hertzbeat/hertzbeat --version 1.7.0 --untar
cd hertzbeat
```
Or clone from GitHub:
```bash
git clone https://github.com/hertzbeat/helm-charts.git
cd helm-charts/charts/hertzbeat
```
### 2. Update values.yaml
Compare and merge configurations:
```bash
diff -u ../hertzbeat-1.6.1-values.yaml values.yaml
# Use vimdiff to compare and merge changes
```
Key configurations to check:
- `image.tag`
- `resources`
- `persistence`
- `service.type`
- Ingress settings
- External database configurations
### 3. Dry-run Upgrade
```bash
helm upgrade hertzbeat . -n <your-namespace> \
--values values.yaml \
--dry-run \
--debug
```
### 4. Execute Upgrade
```bash
helm upgrade hertzbeat . -n <your-namespace> \
--values values.yaml \
--atomic \ # Auto-rollback on failure
--timeout 10m # Set timeout
```
### 5. Verify Upgrade
```bash
helm status hertzbeat -n <your-namespace>
kubectl get pods -n <your-namespace> -l app.kubernetes.io/instance=hertzbeat
kubectl logs -n <your-namespace> -l app.kubernetes.io/instance=hertzbeat --tail=100
```
+2 -2
View File
@@ -1,7 +1,7 @@
---
id: aiConfig
id: ai_config
title: AI QuickStart
sidebar_label: AI QuickStartr
sidebar_label: AI QuickStart
keywords: [AI]
---
+2 -2
View File
@@ -1,7 +1,7 @@
---
id: alarm_silence
title: Alert Silence
sidebar_label: Alert Silence
title: Alarm Silence
sidebar_label: Alarm Silence
keywords: [ Open Source Monitoring System, Alert Silence ]
---
+38
View File
@@ -0,0 +1,38 @@
---
id: alert_integration
title: Alert Integration
sidebar_label: Alert Integration
keywords:
[
open-source monitoring,
alert integration,
alert management,
multi-source alerts,
]
---
> The alert integration module of HertzBeat is designed to achieve unified reception, standardized processing, and intelligent dispatching of alerts from various third-party monitoring and observability platforms. As a centralized "Alert Center," HertzBeat efficiently ingests external alert information and applies flexible alert management strategies.
### Core Capabilities
- **Multi-Source Alert Ingestion**: Supports receiving alert messages from major platforms such as Webhook, Prometheus, Alertmanager, SkyWalking, and Tencent Cloud.
- **Alert Format Standardization**: Converts alerts from different platforms into a unified internal format within HertzBeat for easier processing.
- **Comprehensive Alert Processing Mechanisms**, including:
- **Grouping and Convergence**: Manage alerts by grouping them based on labels and deduplicate repeated alerts within a specific time window.
- **Inhibition**: Automatically suppresses secondary alerts when specific conditions are met.
- **Silencing**: Temporarily disables alert notifications during system maintenance or known issues to avoid unnecessary noise.
### Supported Alert Sources
HertzBeat currently supports alert integration from the following third-party monitoring platforms:
- **Webhook**: A generic integration method supporting customized alert format push.
- **Prometheus**: You can configure HertzBeats service address directly in the Prometheus Server's Alertmanager configuration, allowing HertzBeat to replace Alertmanager for receiving and handling Prometheus Server alerts.
- **Alertmanager**: Supports forwarding alerts from Prometheus AlertManager to the HertzBeat alert platform.
- **SkyWalking**: Sends SkyWalking alerts to the HertzBeat alert platform via Webhook.
- **Tencent Cloud Monitoring**: Sends Tencent Cloud alerts to the HertzBeat alert platform via Webhook.
- **And more**HertzBeat is actively expanding its integration support. If the integration you need is not yet available, the community is actively contributing and can assist in adding it.
You can view the detailed integration methods and configuration examples through the "Integration" interface in HertzBeat.
![integration](/img/docs/help/alert_integration_en.png)
+22 -34
View File
@@ -1,55 +1,43 @@
---
id: alert_threshold
title: Threshold Alert Configuration
sidebar_label: Threshold Alert Configuration
title: Alarm Threshold Configuration
sidebar_label: Alarm Threshold
---
> Configure alert thresholds for monitoring metrics (warning alert, critical alert, emergency alert). The system triggers alerts based on threshold configuration and collected metric data.
:::tip
Alarm Threshold are the core function of `HertzBeat`, users can configure the trigger conditions of the alarm through the threshold rules.
Support real-time threshold and scheduled threshold, real-time threshold can directly trigger the alarm when monitoring data is collected, scheduled threshold supports PromQL and other expressions to calculate the trigger alarm within a specified time period.
Support visual page configuration or more flexible expression rule configuration, support configuring trigger times, alarm levels, notification templates, associated specified monitoring and so on.
:::
## Operational Steps
![threshold](/img/docs/help/alert-threshold-1.png)
### 1. Setting Labels for Monitoring Services (Optional)
## Real-time Threshold
If you need to categorize alerts, you can set labels for the monitored targets. For example: If you have multiple Linux systems to monitor, and each system has different monitoring metrics, such as: Server A has available memory greater than 1G, Server B has available memory greater than 2G, then you can set labels for Server A and Server B respectively, and then configure alerts based on these labels.
#### Creating Labels
Navigate to **Label Management -> Add Label**
![threshold](/img/docs/help/alert-threshold-2-en.png)
As shown in the image above, add a new label. Here we set the label as: linux:dev (Linux used in development environment).
#### Configuring Labels
TODO Update image name
![threshold](/img/docs/help/alert-threshold-3-en.png)
As shown in the image above, click on `Add Label`.
![threshold](/img/docs/help/alert-threshold-4-en.png)
Select our label, here demonstrated as selecting the `linux:dev` label.
> Real-time threshold means that the alarm is triggered directly when the monitoring data is collected, which is suitable for scenarios with high real-time requirements.
### Creating Threshold Rules
Navigate to **[Threshold Rules] -> [Add Threshold Rule] -> [Confirm Configuration]**
> HertzBeat Page -> Alerting -> Threshold -> New Threshold -> ReadTime Threshold Rule
![threshold](/img/docs/help/alert-threshold-1-en.png)
Configure the threshold, for example: Select the SSL certificate metric object, configure the alarm expression-triggered when the metric `expired` is `true`, that is, `equals(expired,"true")`, set the alarm level notification template information, etc.
The above image explains the configuration details:
![HertzBeat](/img/docs/start/ssl_5.png)
Configuration item details:
- **Rule Name**Unique name defining this threshold rule
- **Metric Object**: Select the monitoring metric object for which we need to configure the threshold. For example: Under website monitoring type -> under the summary metric set -> responseTime metric.
- **Threshold Rule**: Use this expression to calculate whether to trigger the threshold. Expression variables and operators are provided on the page for reference. For example: Set an alert to trigger if response time is greater than 50, the expression would be `responseTime > 50`. For detailed help on threshold expressions, see [Threshold Expression Help](alert_threshold_expr).
- **Threshold Rule**: Configure the alarm trigger rules for specific indicators, support graphical interface and expression rules. For expression environment variables and operators, see the page prompts. For detailed help on threshold expressions, see [Threshold Expression Help](alert_threshold_expr).
- **Associated Monitors**Apply this threshold rule to the specified monitoring object (support direct binding and label association). If not configured, it will be applied to all monitoring objects that meet this threshold type rule.
- **Alert Level**: The alert level triggered by the threshold, from low to high: warning, critical, emergency.
- **Trigger Count**: Set how many times the threshold must be triggered before the alert is actually triggered.
- **Notification Template**: The template for the notification message sent after the alert is triggered. Template variables are provided on the page. For example: `${app}.${metrics}.${metric} metric value is ${responseTime}, which is greater than 50 triggering the alert`.
- **Bind Label**: Select the label we need to apply. If no label is selected, it will apply to all services corresponding to the set metric object.
- **Apply Globally**: Set whether this threshold applies globally to all such metrics, default is no. After adding a threshold, it needs to be associated with the monitoring object for the threshold to take effect.
- **Recovery Notification**: Whether to send a recovery notification after the alert is triggered, default is not to send.
- **Bing Annotation**Add annotation information to this threshold rule (the annotation content supports environment variables). When an alarm is generated, this annotation information will be rendered and attached to the alarm.
- **Enable Alert**: Enable or disable this alert threshold configuration.
**The threshold alert configuration is complete, and alerts that have been successfully triggered can be viewed in the [Alert Center].**
**If you need to send alert notifications via email, WeChat, DingTalk, or Feishu, you can configure it in [Alert Notifications].**
**The threshold alert configuration is complete, and alerts that have been successfully triggered can be viewed in the [Alarm Center].**
**If you need to send alert notifications via email, WeChat, DingTalk, or Feishu, you can configure it in [Notification].**
For other issues, you can provide feedback through the community chat group or issue tracker!
## Scheduled Threshold
+1 -1
View File
@@ -9,7 +9,7 @@ keywords: [open source monitoring tool, open source database monitoring tool, mo
### Attention, Need Add ORACLE jdbc driver jar
- Download the ORACLE jdbc driver jar package, such as [ojdbc8.jar](https://download.oracle.com/otn-pub/otn_software/jdbc/234/ojdbc8.jar) [oracle-i18n](https://repo.mavenlibs.com/maven/com/oracle/database/nls/orai18n/21.5.0.0/orai18n-21.5.0.0.jar)
- Download the ORACLE jdbc driver jar package, such as [ojdbc8.jar](https://repo1.maven.org/maven2/com/oracle/database/jdbc/ojdbc8/23.4.0.24.05/ojdbc8-23.4.0.24.05.jar) [oracle-i18n](https://repo.mavenlibs.com/maven/com/oracle/database/nls/orai18n/21.5.0.0/orai18n-21.5.0.0.jar)
- Copy the jar package to the `hertzbeat/ext-lib` directory.
- Restart the HertzBeat service.
+47 -27
View File
@@ -7,15 +7,6 @@ slug: /
> A real-time monitoring system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
[![Discord](https://img.shields.io/badge/Chat-Discord-7289DA?logo=discord)](https://discord.gg/Fb6M73htGr)
[![Reddit](https://img.shields.io/badge/Reddit-Community-7289DA?logo=reddit)](https://www.reddit.com/r/hertzbeat/)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://x.com/hertzbeat1024)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8139/badge)](https://www.bestpractices.dev/projects/8139)
[![Docker Pulls](https://img.shields.io/docker/pulls/apache/hertzbeat?style=%20for-the-badge&logo=docker&label=DockerHub%20Download)](https://hub.docker.com/r/apache/hertzbeat)
[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/hertzbeat)](https://artifacthub.io/packages/search?repo=hertzbeat)
[![QQ](https://img.shields.io/badge/QQ-630061200-orange)](https://qm.qq.com/q/FltGGGIX2m)
[![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCri75zfWX0GHqJFPENEbLow?logo=youtube&label=YouTube%20Channel)](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
**Home: [hertzbeat.apache.org](https://hertzbeat.apache.org)**
## 🎡 <font color="green">Introduction</font>
@@ -36,7 +27,7 @@ slug: /
---
### Powerful Monitoring Templates
### Powerful Monitoring Template
> Before we discuss the customizable monitoring capabilities of HertzBeat, which we mentioned at the beginning, let's introduce the different monitoring templates of HertzBeat. And it is because of this monitoring template design that the advanced features come later.
@@ -91,7 +82,7 @@ Do you believe that users can just write a monitoring template on the UI page, c
* And More Your Custom Template.
* Notified Support `Discord` `Slack` `Telegram` `Email` `Dingtalk` `WeChat` `FeiShu` `Webhook` `SMS` `ServerChan`.
### Powerful Customization
### Customization
> From the previous introduction of **Monitoring Templates**, it is clear that `HertzBeat` has powerful customization features.
> Each monitor type is considered as a monitor template, no matter it is built-in or user-defined. You can easily add, modify and delete indicators by modifying the monitoring template.
@@ -156,7 +147,7 @@ In an isolated network where multiple networks are not connected, we need to dep
---
## Quickly Start
## 🥐 Experience Now
Just run a single command in a Docker environment: `docker run -d -p 1157:1157 -p 1158:1158 --name hertzbeat apache/hertzbeat`
Browser access `http://localhost:1157` default account password `admin/hertzbeat`
@@ -171,7 +162,7 @@ Browser access `http://localhost:1157` default account password `admin/hertzbeat
* The global overview page shows the distribution of current monitoring categories, users can visualize the current monitoring types and quantities and click to jump to the corresponding monitoring types for maintenance and management.
* Show the status of currently registered collector clusters, including collector on-line status, monitoring tasks, startup time, IP address, name and so on.
* Show the list of recent alarm messages, alarm level distribution and alarm processing rate.
* Show the list of recent alarm messages, alarm level distribution etc.
![HertzBeat](/img/home/1.png)
@@ -222,7 +213,7 @@ Built-in support for monitoring types include:
![HertzBeat](/img/home/2.png)
### Add and Modify Surveillance
### New Monitor
* You can add or modify monitoring instances of a specific monitoring type, configure the IP, port and other parameters of the monitoring on the other end, set the collection period, collection task scheduling method, support detecting availability in advance, etc. The monitoring instances on the page are defined by the corresponding monitoring templates.
* The monitoring parameters configured on the page are defined by the monitoring template of the corresponding monitoring type, and users can modify the configuration parameters on the page by modifying the monitoring template.
@@ -235,7 +226,7 @@ Built-in support for monitoring types include:
* The monitoring data detail page shows the basic parameter information of the current monitoring, and the monitoring indicator data information.
* Monitor Real-time Data Report displays the real-time values of all the currently monitored indicators in the form of a list of small cards, and users can configure alarm threshold rules based on the real-time values for reference.
* Monitor Historical Data Report displays the historical values of the currently monitored metrics in the form of trend charts, supports querying hourly, daily and monthly historical data, and supports configuring the page refresh time.
* ⚠️ Note that the monitoring history charts need to be configured with an external timing database in order to get the full functionality, timing database support: IOTDB, TDengine, InfluxDB, GreptimeDB
* ⚠️ Note that the monitoring history charts need to be configured with an external timing database in order to get the full functionality.
![HertzBeat](/img/home/3.png)
@@ -248,24 +239,34 @@ Built-in support for monitoring types include:
![HertzBeat](/img/home/7.png)
### Threshold Rules
### Alarm Threshold
* Threshold rules can be configured for monitoring the availability status, and alerts can be issued when the value of a particular metric exceeds the expected range.
* There are three levels of alerts: notification alerts, critical alerts, and emergency alerts.
* Threshold rules support visual page configuration or expression rule configuration for more flexibility.
* It supports configuring the number of triggers, alarm levels, notification templates, associated with a specific monitor and so on.
* Alarm Threshold are the core function of `HertzBeat`, users can configure the trigger conditions of the alarm through the threshold rules.
* Support real-time threshold and scheduled threshold, real-time threshold can directly trigger the alarm when monitoring data is collected, scheduled threshold supports PromQL and other expressions to calculate the trigger alarm within a specified time period.
* Support visual page configuration or more flexible expression rule configuration, support configuring trigger times, alarm levels, notification templates, associated specified monitoring and so on.
![HertzBeat](/img/home/6.png)
![HertzBeat](/img/docs/start/ssl_5.png)
### Alarm Integration
* Integration with third-party monitoring systems such as `Prometheus`, `WebHook`, `Skywalking`, `AlertManager`, etc. to receive alarm messages from these systems and perform alarm processing.
![HertzBeat](/img/home/11.png)
### Alarm Convergence
### Alarm Grouping
* When the alarm is triggered by the threshold rule, it will enter into the alarm convergence, the alarm convergence will be based on the rules of the specific time period of the duplicate alarm message de-emphasis convergence, to avoid a large number of repetitive alarms lead to the receiver alarm numbness.
* Alarm convergence rules support duplicate alarm effective time period, label matching and alarm level matching filter.
* Group convergence supports merging alarms for specified group labels by grouping. It deduplicates and converges the same repeated alarms in a time period.
* When the threshold rule triggers an alarm or an external alarm is reported, it will enter the grouping convergence for alarm grouping and alarm deduplication to avoid alarm storms caused by a large number of alarm messages.
![HertzBeat](/img/home/12.png)
### Alarm Inhibition
* Alarm suppression is used to configure the suppression relationship between alarms. For example, high-level alarms suppress low-level alarms under the same instance.
* When an alarm occurs, it can suppress the occurrence of other alarms. For example, when a server crashes, it can suppress all alarms on that server.
![HertzBeat](/img/home/13.png)
### Alarm Silence
@@ -274,8 +275,6 @@ Built-in support for monitoring types include:
* This application scenario, such as users in the system maintenance, do not need to send known alarms. Users will only receive alarm messages on weekdays, and users need to avoid disturbances at night.
* Alarm silence rules support one-time time period or periodic time period, support label matching and alarm level matching.
![HertzBeat](/img/home/14.png)
![HertzBeat](/img/home/15.png)
### Message Notification
@@ -292,17 +291,38 @@ Built-in support for monitoring types include:
![HertzBeat](/img/home/8.png)
### Monitoring Templates
![HertzBeat](/img/home/14.png)
### Monitoring Template
* HertzBeat makes `Http, Jmx, Ssh, Snmp, Jdbc, Prometheus` and other protocols configurable so that you can customize the metrics you want to collect using these protocols by simply configuring the monitoring template `YML` in your browser. Would you believe that you can instantly adapt a new monitoring type such as `K8s` or `Docker` just by configuring it?
* All our built-in monitoring types (mysql, website, jvm, k8s) are also mapped to corresponding monitoring templates, so you can add and modify monitoring templates to customize your monitoring functions.
![HertzBeat](/img/home/9.png)
### Collector Cluster
* Users can configure collector clusters to achieve distributed collection of large-scale monitoring tasks.
* The collector cluster supports multi-node deployment, automatic load balancing, automatic failover, etc.
* Supports unified management of multiple isolated networks, cloud-edge collaboration.
![HertzBeat](/img/home/18.png)
### Status Page
* Based on HertzBeat, quickly build an external status page for your own product and easily convey the real-time status of your product service to users. For example, the service status page provided by Github <https://www.githubstatus.com>.
* Support synchronization between the status of the status page component and the monitoring status, as well as the fault event maintenance management mechanism, etc. Improve your transparency, professionalism, and user trust, and reduce communication costs.
![HertzBeat](/img/home/19.png)
![HertzBeat](/img/home/status.png)
---
**There's so much more to discover. Have Fun!**
**More functions are welcome to be explored. Have Fun!**
---
**Github: <https://github.com/apache/hertzbeat>**
**Home: <https://hertzbeat.apache.org/>**
+1 -1
View File
@@ -35,7 +35,7 @@ It is necessary to have Docker environment in your environment. If not installed
- `-v $(pwd)/logs:/opt/hertzbeat/logs` : (optional) Mount the log file to the local host to facilitate viewing.
- `-v $(pwd)/application.yml:/opt/hertzbeat/config/application.yml` : (optional) Mount the configuration file to the container (please ensure that the file exists locally). [Download](https://github.com/apache/hertzbeat/raw/master/script/application.yml)
- `-v $(pwd)/sureness.yml:/opt/hertzbeat/config/sureness.yml` : (optional) Mount the account configuration file to the container (please ensure that the file exists locally). [Download](https://github.com/apache/hertzbeat/raw/master/script/sureness.yml)
- `-v $(pwd)/ext-lib:/opt/hertzbeat/ext-lib` : (optional) Mount external third-party JAR package [mysql-jdbc](https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.25.zip) [oracle-jdbc](https://download.oracle.com/otn-pub/otn_software/jdbc/234/ojdbc8.jar) [oracle-i18n](https://repo.mavenlibs.com/maven/com/oracle/database/nls/orai18n/21.5.0.0/orai18n-21.5.0.0.jar)
- `-v $(pwd)/ext-lib:/opt/hertzbeat/ext-lib` : (optional) Mount external third-party JAR package [mysql-jdbc](https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.25.zip) [oracle-jdbc](https://repo1.maven.org/maven2/com/oracle/database/jdbc/ojdbc8/23.4.0.24.05/ojdbc8-23.4.0.24.05.jar) [oracle-i18n](https://repo.mavenlibs.com/maven/com/oracle/database/nls/orai18n/21.5.0.0/orai18n-21.5.0.0.jar)
- `--name hertzbeat` : (optional) Naming container name hertzbeat
- `--restart=always` : (optional) Configure the container to restart automatically.
- `apache/hertzbeat` : Use the [official application mirror](https://hub.docker.com/r/apache/hertzbeat) to start the container, if the network times out, use `quay.io/tancloud/hertzbeat` instead.
+7 -3
View File
@@ -6,8 +6,12 @@ sidebar_label: Update to 1.6.0 guide
## HertzBeat 1.6.0 Upgrade Guide
**Note: This guide is applicable for upgrading from 1.5.0 to 1.6.0 to version 1.6.0.**
**If you are using an older version, it is recommended to reinstall using the export function, or upgrade to 1.5.0 and then follow this guide to 1.6.0.**
:::note
This guide is applicable for upgrading from 1.5.0 to 1.6.0 to version 1.6.0.
If you are using an older version, it is recommended to reinstall using the export function, or upgrade to 1.5.0 and then follow this guide to 1.6.0.
:::
Follow the [HertzBeat New Version Upgrade](upgrade)
## Binary Installation Package Upgrade
@@ -103,7 +107,7 @@ Due to the Apache Foundation's requirements for license compliance, HertzBeat's
- MySQL: [https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.25.zip](https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.25.zip)
- Oracle (If you want to monitor Oracle, these two drivers are required):
- [https://download.oracle.com/otn-pub/otn_software/jdbc/234/ojdbc8.jar](https://download.oracle.com/otn-pub/otn_software/jdbc/234/ojdbc8.jar)
- [https://repo1.maven.org/maven2/com/oracle/database/jdbc/ojdbc8/23.4.0.24.05/ojdbc8-23.4.0.24.05.jar](https://repo1.maven.org/maven2/com/oracle/database/jdbc/ojdbc8/23.4.0.24.05/ojdbc8-23.4.0.24.05.jar)
- [https://repo.mavenlibs.com/maven/com/oracle/database/nls/orai18n/21.5.0.0/orai18n-21.5.0.0.jar](https://repo.mavenlibs.com/maven/com/oracle/database/nls/orai18n/21.5.0.0/orai18n-21.5.0.0.jar)
Next, run the start-up script as before to experience the latest HertzBeat 1.6.0!
+41
View File
@@ -0,0 +1,41 @@
---
id: 1.7.0-update
title: How to update to 1.7.0
sidebar_label: Update to 1.7.0 guide
---
## HertzBeat 1.7.0 Upgrade Guide
:::note
This guide is applicable for upgrading from 1.6.x to version 1.7.0.
If you are using an older version, it is recommended to reinstall using the export function, or upgrade to 1.6.0 and then follow this guide to 1.7.0.
:::
Follow the [HertzBeat New Version Upgrade](upgrade)
## Installation Upgrade
### Upgrade Database
In 1.7.0, we use the `label` instead of `tag`, in some environment, we need drop or delete the table `hzb_tag_monitor_bind` in database.
```sql
DELETE FROM hzb_tag_monitor_bind;
```
### Upgrade Alarm Threshold
In 1.7.0, we redesign the new alarm threshold, include the Real-Time Threshold and Scheduled Threshold.
We need reconfigure the alarm threshold, alarm group by manual.
:::tip
There are no default built-in threshold rules, such as the previous availability threshold.
So if you find that there is no alarm after the monitoring is down, you need to configure the corresponding availability threshold yourself.
:::
## Upgrade via Export and Import
If you do not want to go through the tedious script upgrade method mentioned above, you can directly export and import the monitoring tasks and threshold information from the old environment.
- Deploy a new environment with the latest version.
- Export the monitoring tasks and threshold information from the old environment on the page
@@ -0,0 +1,245 @@
---
id: springboot-auto-practice
title: SpringBoot Web Application Monitoring Practice
sidebar_label: SpringBoot Web Application Monitoring Practice
---
:::tip
In the cloud-native era, monitoring and visualization of SpringBoot applications have become core components of O&M (Operations & Maintenance) systems. Real-time monitoring of application performance is crucial for ensuring system stability.
:::
This article introduces an integrated solution using the HertzBeat monitoring system, covering metric collection, visualization, and alarm notifications. It demonstrates the complete operational procedure and monitoring practices.
## What is HertzBeat
Apache HertzBeat (incubating) is a real-time monitoring tool with powerful custom monitoring capabilities without Agent. Website monitoring, PING connectivity, port availability, database, operating system, middleware, API monitoring, threshold alarms, alarm notification (email, WeChat, Ding Ding Feishu).
**github: <https://github.com/apache/hertzbeat>**
## Install HertzBeat
> For production environments, it is recommended to deploy HertzBeat using **PostgreSQL** + **VictoriaMetrics**.
1. Deploy HertzBeat
> Download the installation package
>
> Download the `apache-hertzbeat-xxx-incubating-bin.tar.gz` installation package corresponding to your system environment from the [Download Page](https://hertzbeat.apache.org/docs/download). Extract the package to a host directory, e.g., `/opt/hertzbeat`.
```sh
tar zxvf apache-hertzbeat-xxx-incubating-bin.tar.gz
```
2. Quick Install PostgreSQL + VictoriaMetrics Services
> Install PostgreSQL via Docker
```sh
docker run -d --name postgresql -p 5432:5432 -v "$PWD/postgresql:/var/lib/postgresql/data" -e POSTGRES_USER=root -e POSTGRES_PASSWORD=123456 -e TZ=Asia/Shanghai postgres:15
```
> Install VictoriaMetrics via Docker
```sh
docker run -d -p 8428:8428 -v "$PWD/victoria-metrics-data:/victoria-metrics-data" --name victoria-metrics victoriametrics/victoria-metrics:v1.95.1
```
3. Modify HertzBeat's Configuration File
> Switch the metadata storage data source
>
> Modify the `hertzbeat/config/application.yml` configuration file to use the PostgreSQL service.
:::note
For PostgreSQL configuration details, please refer to the documentation: [Meta Store PostgreSQL (Recommended)](https://hertzbeat.apache.org/docs/start/postgresql-change)
:::
> Configure the time-series database for metric storage
>
> Modify the `hertzbeat/config/application.yml` configuration file to enable the VictoriaMetrics service for storing metrics.
:::note
For VictoriaMetrics configuration details, please refer to the documentation: [Metrics Store VictoriaMetrics (Recommended)](https://hertzbeat.apache.org/docs/start/victoria-metrics-init)
:::
4. Start HertzBeat
> Use the `startup.sh` script located in the `bin` directory of the extracted installation folder. For Windows environments, use `startup.bat`.
>
> After successful startup, access `http://localhost:1157` in your browser to begin. The default username and password are `admin/hertzbeat`.
```sh
./startup.sh
```
## SpringBoot Application Configuration
1. Enable Actuator Configuration
> Add the following dependencies to project's `pom.xml`:
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
```
> Configure `application.yml` to expose endpoints:
```yml
management:
endpoints:
web:
exposure:
include: '*'
enabled-by-default: true
metrics:
export:
prometheus:
enabled: true
```
> *Note: If your project also introduces authentication related dependencies, such as springboot security, the interfaces exposed by SpringBoot Actor may be intercepted. In this case, you need to manually release these interfaces. Taking springboot security as an example, you should add the following code to the Security Configuration class:*
```java
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity
// Configure the interfaces to be opened
.antMatchers("/actuator/**").permitAll()
.antMatchers("/metrics/**").permitAll()
.antMatchers("/trace").permitAll()
.antMatchers("/heapdump").permitAll()
// ...
}
}
```
2. Verify Endpoints
> After starting SpringBoot application, request the following URL:
>
> * `http://<your-host>:<port>/actuator`: Verify the enabled endpoints.
> * `http://<your-host>:<port>/actuator/prometheus`: Should return metric data in Prometheus format.
## Monitor SpringBoot Application
1. Add AUTO Monitor
> HertzBeat Page -> Monitors -> New Monitor -> AUTO -> Prometheus Task
![HertzBeat](/img/docs/start/springboot-auto-practice-1.png)
2. Fill in Key Parameters
> **Target Host**: The server address of the SpringBoot application (without the protocol prefix like `http://` or `https://`).
>
> **Port**: The application service port (e.g., `8080`).
>
> **Endpoint Path**: `/actuator/prometheus`
>
> Tasks can be managed using label classification, such as adding Bind Labels like `env=test`.
![HertzBeat](/img/docs/start/springboot-auto-practice-2.png)
3. View Detected Metric Data
> Click on the newly created monitor to view detailed metric data, historical charts, etc.
![HertzBeat](/img/docs/start/springboot-auto-practice-3.png)
![HertzBeat](/img/docs/start/springboot-auto-practice-4.png)
## Grafana Visualization Integration (Optional)
1. Grafana Dashboard Configuration
> Enable Grafana's embedding feature and enable anonymous access.
:::note
For complete configuration details, please refer to the documentation: [Grafana Historical Charts](https://hertzbeat.apache.org/docs/help/grafana_dashboard)
:::
2. Embed Grafana Dashboard in HertzBeat Monitor
> After configuring and enabling Grafana, restart the HertzBeat service. Then, in the newly added AUTO monitor settings, enable Grafana Templates and upload a Grafana dashboard template.
>
> For example: In Grafana, select `hertzbeat-victoria-metrics` as the data source. Then click on the dashboard:「Share」→「Export」→「Save to file」to download the template and upload this file to the HertzBeat monitor.
![HertzBeat](/img/docs/start/springboot-auto-practice-5.png)
3. View Grafana Dashboard
> Go to the AUTO monitor page, click the Grafana icon button, and view the integrated Grafana dashboard.
![HertzBeat](/img/docs/start/springboot-auto-practice-6.png)
## Alarm and Notification Interlinkage
1. HertzBeat Alarm Configuration
> HertzBeat Page -> Alerting -> Threshold -> New -> New Threshold Rule
![HertzBeat](/img/docs/start/springboot-auto-practice-7.png)
> HertzBeat provides two types of threshold rule settings: **RealTime Calculation** and **Periodically execute**. Here, we use the **Periodically execute** threshold rule as an example.
>
> * **Rule Name**: Name of the threshold rule.
> * **Threshold Rule**: Enter the rule for monitoring the metric (supports `PromQL`).
> * **Execution Period**: The time interval for periodically executing the threshold calculation.
> * **Alarm Severity**: The alarm level that triggers the threshold, from low to high: WarningCriticalEmergency.
> * **Trigger Times**: Set how many times the threshold is triggered before sending an alarm.
> * **Alarm Content**: Enter the content of the monitoring alarm(supports variables).
2. Set Threshold Rule
> For example, to monitor the CPU usage of the SpringBoot application, add a threshold rule like: `system_cpu_usage{job="Jolly_Vulture_43vT"} > 0.01`
>
>You can create many combinations of threshold rules. Users can set up richer alarm rules based on their specific needs.
![HertzBeat](/img/docs/start/springboot-auto-practice-8.png)
> Finally, you can see the triggered alarm in the alarm center.
![HertzBeat](/img/docs/start/springboot-auto-practice-9.png)
3. Alarm Notification
> HertzBeat Page -> Notification -> Notice Receiver -> New Receiver
![HertzBeat](/img/docs/start/springboot-auto-practice-10.png)
> HertzBeat Page -> Notification -> Notice Policy -> New Notice Policy -> Select Recipient(s) and Enable Notification
![HertzBeat](/img/docs/start/springboot-auto-practice-11.png)
4. OK! When the threshold is triggered, we will receive the corresponding alarm message. If no notification is configured, you can also view the alarm information in the alarm center.
## Quick Summary
This article demonstrates how users can build a complete monitoring system within minutes using simple configurations, highlighting several advantages of HertzBeat:
* Monitors SpringBoot Actuator endpoints without needing to deploy Exporter or Agent, supporting custom metric collection and alert rules.
* Lightweight: Compared to the traditional Prometheus + AlertManager combination, HertzBeat simplifies the deployment and maintenance process.
* Seamless Integration with Grafana: HertzBeat pushes collected time-series data to Grafana in real-time for building visualization dashboards.
* Integrated Monitoring + Alerting + Notification: Combines full protocol coverage, real-time/periodic threshold detection, and multi-channel notifications (DingTalk/Feishu/Webhook, etc.).
------
## Wrapping Up
That concludes this practical guide on monitoring SpringBoot applications with HertzBeat. Of course, this functionality is just the tip of the iceberg for HertzBeat. If you think hertzbeat is a good open source project, please give it a **Star** on GitHub and Gitee! Your Star motivate our continuous improvement! Feel free to light up the little star ✨
**Making monitoring simpler, looking forward to building the ecosystem together!** 💝
**GitHub: <https://github.com/apache/hertzbeat>**
**Gitee: <https://gitee.com/hertzbeat/hertzbeat>**
@@ -1,39 +1,45 @@
---
id: ssl-cert-practice
title: SSL Certificate Monitor Practice
sidebar_label: SSL Certificate Monitor Practice
title: SSL Certificate Monitoring Practice
sidebar_label: SSL Certificate Monitoring Practice
---
:::tip
Most websites now support HTTPS by default. The certificate we apply for is usually 3 months or 1 year. It is easy to expire the SSL certificate over time, but we did not find it the first time, or did not update the certificate in time before it expired.
:::
This article introduces how to use the hertzbeat monitoring tool to detect the validity period of our website's SSL certificate, and send us a warning message when the certificate expires or a few days before the certificate expires.
#### What is HertzBeat
## What is HertzBeat
Apache HertzBeat (incubating) is a real-time monitoring tool with powerful custom monitoring capabilities without Agent. Website monitoring, PING connectivity, port availability, database, operating system, middleware, API monitoring, threshold alarms, alarm notification (email, WeChat, Ding Ding Feishu).
github: <https://github.com/apache/hertzbeat>
#### Install HertzBeat
## Install HertzBeat
1. The `docker` environment can be installed with just one command
`docker run -d -p 1157:1157 --name hertzbeat apache/hertzbeat`
`docker run -d -p 1157:1157 -p 1158:1158 --name hertzbeat apache/hertzbeat`
2. After the installation is successful, the browser can access `localhost:1157` to start, the default account password is `admin/hertzbeat`
#### Monitoring SSL certificates
:::note
The production environment recommends a complete deployment method, refer <https://hertzbeat.apache.org/docs/start/docker-compose-deploy>
:::
## Monitoring SSL Certificates
1. Click Add SSL Certificate Monitor
> System Page -> Monitor Menu -> SSL Certificate -> Add SSL Certificate
> HertzBeat Page -> Monitors Menu -> New Monitor -> Service Monitor -> Add SSL Certificate
![HertzBeat](/img/docs/start/ssl_1.png)
2. Configure the monitoring website
> Here we take the example of monitoring Baidu website, configure monitoring host domain name, name, collection interval, etc.
> Click OK Note ⚠️Before adding, it will test the connectivity of the website by default, and the connection will be successful before adding. Of course, you can also gray out the **Test or not** button.
> Here we take the example of monitoring Baidu website, configure monitoring host domain name, name, collection interval, etc.
> Click OK Note
![HertzBeat](/img/docs/start/ssl_2.png)
@@ -43,51 +49,46 @@ github: <https://github.com/apache/hertzbeat>
![HertzBeat](/img/docs/start/ssl_3.png)
![HertzBeat](/img/docs/start/ssl_11.png)
![HertzBeat](/img/docs/start/ssl_4.png)
4. Set the threshold (triggered when the certificate expires)
> System Page -> Alarms -> Alarm Thresholds -> New Thresholds
![HertzBeat](/img/docs/start/ssl_4.png)
> HertzBeat Page -> Alerting -> Threshold -> New Threshold -> ReadTime Threshold Rule
> Configure the threshold, select the SSL certificate metric object, configure the alarm expression-triggered when the metric `expired` is `true`, that is, `equals(expired,"true")`, set the alarm level notification template information, etc.
![HertzBeat](/img/docs/start/ssl_5.png)
> Associating thresholds with monitoring, in the threshold list, set which monitoring this threshold applies to.
![HertzBeat](/img/docs/start/ssl_6.png)
> Threshold rule has others function you can try eg: associating thresholds with monitoring, trigger times so on.
5. Set the threshold (triggered one week before the certificate expires)
> In the same way, add a new configuration threshold and configure an alarm expression - when the metric expires timestamp `end_timestamp`, the `now()` function is the current timestamp, if the configuration triggers an alarm one week in advance: `end_timestamp <= (now( ) + 604800000)` , where `604800000` is the 7-day total time difference in milliseconds.
> In the same way, switch coding threshold, add a new configuration threshold and configure an alarm expression - when the metric expires timestamp `end_timestamp`, the `now()` function is the current timestamp, if the configuration triggers an alarm one week in advance: `end_timestamp <= (now() + 604800000)` , where `604800000` is the 7-day total time difference in milliseconds.
![HertzBeat](/img/docs/start/ssl_7.png)
![HertzBeat](/img/docs/start/ssl_6.png)
> Finally, you can see the triggered alarm in the alarm center.
![HertzBeat](/img/docs/start/ssl_8.png)
![HertzBeat](/img/docs/start/ssl_7.png)
6. Alarm notification (in time notification via Dingding WeChat Feishu, etc.)
> Monitoring Tool -> Alarm Notification -> New Receiver
> HertzBeat Page -> Notification -> Notice Receiver -> New Receiver -> Config the Feishu Receiver
![HertzBeat](/img/docs/start/ssl_10.png)
![HertzBeat](/img/docs/start/notice_receiver_1.png)
For token configuration such as Dingding WeChat Feishu, please refer to the help document
For token configuration such as Feishu, please refer to the help document
<https://hertzbeat.apache.org/docs/help/alert_dingtalk>
<https://hertzbeat.apache.org/docs/help/alert_feishu>
> Alarm Notification -> New Alarm Notification Policy -> Enable Notification for the Recipient Just Configured
> Notification -> Notice Policy -> New Notice Policy -> Enable Notification for the Recipient Just Configured
![HertzBeat](/img/docs/start/ssl_11.png)
![HertzBeat](/img/docs/start/notice_policy_1.png)
7. OK When the threshold is triggered, we can receive the corresponding alarm message. If there is no notification, you can also view the alarm information in the alarm center.
----
#### Finish
## The End
The practice of monitoring SSL certificates is here. Of course, for hertzbeat, this function is just the tip of the iceberg. If you think hertzbeat is a good open source project, please give us a Gitee star on GitHub, thank you very much. Thank you for your support. Refill!
+88
View File
@@ -60,6 +60,94 @@ After the installation you can check if the Docker version normally output at th
4. Restart HertzBeat
### Use VictoriaMetrics in Cluster Mode
VictoriaMetrics provides a **cluster mode** with separate components for ingestion (`vminsert`), storage (`vmstorage`), and querying (`vmselect`). The following section explains how to deploy VictoriaMetrics in cluster mode and integrate it with HertzBeat.
#### 1. Deploy VictoriaMetrics Cluster via Docker Compose
Create a `docker-compose.yml` file with the following content:
```yaml
version: "3"
services:
vmstorage1:
image: victoriametrics/vmstorage
command:
- "-retentionPeriod=1"
- "-storageDataPath=/storage"
volumes:
- vmstorage-data:/storage
ports:
- "8400:8400" # vminsert connects here
- "8401:8401" # vmselect connects here
vminsert:
image: victoriametrics/vminsert
command:
- "-storageNode=vmstorage1:8400"
- "-httpAuth.username=root"
- "-httpAuth.password=root"
ports:
- "8480:8480" # ingestion endpoint
vmselect:
image: victoriametrics/vmselect
command:
- "-storageNode=vmstorage1:8401"
- "-httpAuth.username=root"
- "-httpAuth.password=root"
ports:
- "8481:8481" # query endpoint
volumes:
vmstorage-data:
```
Start the cluster with:
```shell
docker-compose up -d
```
Check all components are running via:
```shell
docker ps
```
#### 2. Configure HertzBeat for Cluster Mode
Edit the `hertzbeat/config/application.yml` configuration file as follows:
```yaml
warehouse:
store:
jpa:
enabled: false
victoria-metrics:
cluster:
enabled: true
select:
url: http://127.0.0.1:8481
username: root
password: root
insert:
url: http://127.0.0.1:8480
username: root
password: root
```
**Note:**
- Set `enabled: true` under `cluster` to enable cluster mode.
- Ensure the URLs match your Docker Compose network or deployment addresses.
#### 3. Restart HertzBeat
Once configured, restart HertzBeat to connect to the VictoriaMetrics cluster.
### FAQ
1. Do both the time series databases need to be configured? Can they both be used?
@@ -47,16 +47,12 @@
"message": "Server Monitor",
"description": "The label for category Server in sidebar docs"
},
"sidebar.docs.category.threshold": {
"message": "Threshold Alarm Setting",
"description": "The label for category threshold in sidebar docs"
"sidebar.docs.category.alert": {
"message": "Alerting",
"description": "The label for category alert in sidebar docs"
},
"sidebar.docs.category.reduce": {
"message": "Alarm Reduce Setting",
"description": "The label for category reduce in sidebar docs"
},
"sidebar.docs.category.notice": {
"message": "Alarm Notice Setting",
"message": "Notification",
"description": "The label for category notice in sidebar docs"
},
"sidebar.docs.category.mid": {
@@ -52,18 +52,10 @@ Bug修复.
5. [[collector]bugfix: 修复 warehouse data queue 未消费异常 #153](https://github.com/apache/hertzbeat/pull/153). issue by @daqianxiaoyao
6. [[web-app]bugfix: 修复黑暗主题时页面输入框校验出错时不可见 #157](https://github.com/apache/hertzbeat/pull/157). issue by @ConradWen
**Full Changelog**: <https://github.com/apache/hertzbeat/compare/v1.0-beta.8...v1.0>
Online <https://console.tancloud.cn>.
-----------------------
Redis监控来啦:
<img width="1910" alt="2022-05-29 20 23 58" src="https://user-images.githubusercontent.com/24788200/170868079-325ccc08-165f-4d0e-9ebb-18b0b5c9db3f.png"/>
<img width="959" alt="2022-05-29 20 24 21" src="https://user-images.githubusercontent.com/24788200/170868094-3c4de70f-934a-4a13-ae1a-0744c30f14f3.png"/>
> [HertzBeat赫兹跳动](https://github.com/apache/hertzbeat) 是由 [Dromara](https://dromara.org) 孵化,[TanCloud](https://tancloud.cn)开源的一个支持网站,API,PING,端口,数据库,操作系统等监控类型,拥有易用友好的可视化操作界面的开源监控告警项目。
> 当然,我们也提供了对应的[SAAS云监控版本](https://console.tancloud.cn),中小团队和个人无需再为了监控自己的网站资源,而去部署一套繁琐的监控系统,[登录即可免费开始](https://console.tancloud.cn)监控之旅。
> HertzBeat 支持自定义监控,只用通过配置YML文件我们就可以自定义需要的监控类型和指标,来满足常见的个性化需求。
@@ -36,9 +36,6 @@ Bugfix.
Online <https://console.tancloud.cn>.
---
Windows Monitor coming
<img width="1444" alt="2022-06-19 11 30 57" src="https://user-images.githubusercontent.com/24788200/174481159-b8a73c87-aff5-4c4c-befb-bd0d26685d71.png"/>
⚠️ ⚠️⚠️⚠️请注意其它版本升级到v1.1.0需要先执行下面的SQL脚本. 现在我们的表名称有个统一前缀 hzb_ prefix.
@@ -60,60 +57,6 @@ Have Fun!
---
## V1.1.0
Home: hertzbeat.com | tancloud.cn
Hi guys! HertzBeat v1.1.0 is coming. This version we support snmp protocol and use snmp to collect windows metrics.
Another major change is that we use the H2 database by default to replace the MYSQL database as storage to facilitate the installation and deployment of users. Now only one docker command is needed to install and experience hertzbeat `docker run -d -p 1157:1157 --name hertzbeat apache/hertzbeat`
Let's Try It!
Thanks to the contributors! 👍👍
Feature
1. [[monitor]feature: support snmp collect protocol and windows monitor type #192](https://github.com/apache/hertzbeat/pull/192). contribute by @ChineseTony
2. [[monitor]change default database mysql to h2 #191](https://github.com/apache/hertzbeat/pull/191)
3. [[manager]support monitor params name i18n #184](https://github.com/apache/hertzbeat/pull/184).
4. [[script]build multi cpu arch hertzbeat docker version #189](https://github.com/apache/hertzbeat/pull/189).
5. [[monitor]feature: support oracle multi tablespaces #163](https://github.com/apache/hertzbeat/pull/163) contribute by @brave4Time
6. [[monitor]database tables append prefix hzb_ #193](https://github.com/apache/hertzbeat/pull/193) issue from @shimingxy
Bugfix.
1. [[monitor]fix can not collect cpu metrics in tencent centos #164](https://github.com/apache/hertzbeat/pull/164) contribute by @wyt199905 .
2. [[manager]fix oracle config yml percentage error #168](https://github.com/apache/hertzbeat/pull/168)
3. [[monitor] bugfix: fix elasticsearch collect error when need basic auth #174](https://github.com/apache/hertzbeat/pull/174) contribute by @weifuqing
4. [Change the Oracle database name to the service name to reduce ambiguity #182](https://github.com/apache/hertzbeat/pull/182) @zklmcookle
Online <https://console.tancloud.cn>.
---
Windows Monitor coming
<img width="1444" alt="2022-06-19 11 30 57" src="https://user-images.githubusercontent.com/24788200/174481159-b8a73c87-aff5-4c4c-befb-bd0d26685d71.png"/>
⚠️ ⚠️⚠️⚠️Attention other version upgrade to v1.1.0 need run sql script. Now the tables name has hzb_ prefix.
```properties
ALTER TABLE alert RENAME TO hzb_alert;
ALTER TABLE alert_define RENAME TO hzb_alert_define;
ALTER TABLE alert_define_monitor_bind RENAME TO hzb_alert_define_monitor_bind;
ALTER TABLE monitor RENAME TO hzb_monitor;
ALTER TABLE notice_receiver RENAME TO hzb_notice_receiver;
ALTER TABLE notice_rule RENAME TO hzb_notice_rule;
ALTER TABLE param RENAME TO hzb_param;
ALTER TABLE param_define RENAME TO hzb_param_define;
ALTER TABLE tag RENAME TO hzb_tag;
ALTER TABLE tag_monitor_bind RENAME TO hzb_tag_monitor_bind;
commit;
```
Have Fun!
---
> [HertzBeat赫兹跳动](https://github.com/apache/hertzbeat) 是由 [Dromara](https://dromara.org) 孵化,[TanCloud](https://tancloud.cn)开源的一个支持网站,API,PING,端口,数据库,操作系统等监控类型,拥有易用友好的可视化操作界面的开源监控告警项目。
> 当然,我们也提供了对应的[SAAS云监控版本](https://console.tancloud.cn),中小团队和个人无需再为了监控自己的网站资源,而去部署一套繁琐的监控系统,[登录即可免费开始](https://console.tancloud.cn)监控之旅。
> HertzBeat 支持自定义监控,只用通过配置YML文件我们就可以自定义需要的监控类型和指标,来满足常见的个性化需求。

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