mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
58
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeee629c5f | ||
|
|
d0b7e38537 | ||
|
|
cc4b63f9ad | ||
|
|
096153b3a4 | ||
|
|
93a22c3b22 | ||
|
|
8954775632 | ||
|
|
59781ad673 | ||
|
|
b313db36c6 | ||
|
|
ce61c7d456 | ||
|
|
47298033dc | ||
|
|
f05e91247a | ||
|
|
320766a48e | ||
|
|
efb2ac02df | ||
|
|
06c939c810 | ||
|
|
e56823b240 | ||
|
|
3b18d1ddaf | ||
|
|
d44fe43eb0 | ||
|
|
a221ddfba2 | ||
|
|
8e31a3147b | ||
|
|
d2f9a56fe7 | ||
|
|
a0b41dc7bb | ||
|
|
3f572a1ddc | ||
|
|
767deccee5 | ||
|
|
9221e2f6c1 | ||
|
|
0a36b3df90 | ||
|
|
f3202133d8 | ||
|
|
6ee458c521 | ||
|
|
cbc1caf4ac | ||
|
|
86db630fc7 | ||
|
|
145c4d1d5e | ||
|
|
0217cbb7f9 | ||
|
|
9f5df95f29 | ||
|
|
0f955019bf | ||
|
|
129b6282d3 | ||
|
|
36c5d8f518 | ||
|
|
de8a473e24 | ||
|
|
ffc4641dc9 | ||
|
|
d0cb9f8bf3 | ||
|
|
0d4ff33f19 | ||
|
|
86f40e5e15 | ||
|
|
dbbbc045d4 | ||
|
|
b0fdd5e11d | ||
|
|
9c5ea20b15 | ||
|
|
7a74f80906 | ||
|
|
ee32a890eb | ||
|
|
ec1c04194b | ||
|
|
55d8dfdede | ||
|
|
918073dca7 | ||
|
|
2defaecd93 | ||
|
|
641ba47f4c | ||
|
|
ccbc2151ae | ||
|
|
1cb919a301 | ||
|
|
7351a03c81 | ||
|
|
26c3a802d1 | ||
|
|
47dbe2fd2a | ||
|
|
f4943e9d89 | ||
|
|
1f6614d1a1 | ||
|
|
4a96c02b9a |
+2
-2
@@ -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) {
|
||||
|
||||
+64
-66
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+52
@@ -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";
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -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);
|
||||
|
||||
+4
-1
@@ -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));
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ public interface DispatchConstants {
|
||||
/**
|
||||
* protocol http sd
|
||||
*/
|
||||
String PROTOCOL_HTTP_SD = "httpsd";
|
||||
String PROTOCOL_HTTP_SD = "http_sd";
|
||||
/**
|
||||
* protocol redfish
|
||||
*/
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+4
-107
@@ -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
|
||||
*/
|
||||
|
||||
+1
-1
@@ -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
|
||||
*/
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-8
@@ -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
|
||||
|
||||
+5
-10
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
-67
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
-151
@@ -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;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@
|
||||
<!-- log -->
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-log</artifactId>
|
||||
<artifactId>hertzbeat-otel</artifactId>
|
||||
</dependency>
|
||||
<!-- spring -->
|
||||
<dependency>
|
||||
|
||||
+58
-60
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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");
|
||||
|
||||
-12
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
+7
-9
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+8
-17
@@ -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()))) {
|
||||
|
||||
+3
-14
@@ -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
|
||||
*
|
||||
|
||||
-4
@@ -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;
|
||||
|
||||
+128
-327
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 category:service-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 category:service-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: 响应内容长度
|
||||
|
||||
-11
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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>
|
||||
+1
-1
@@ -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;
|
||||
+154
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
+58
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
@@ -1,103 +1,94 @@
|
||||
# HertzBeat 升级指导-(Docker Mode)
|
||||
# HertzBeat Upgrade Guide (Docker Mode)
|
||||
|
||||
## Docker 方式升级 - Mysql 数据库
|
||||
## Docker-based Upgrade
|
||||
|
||||
1. 数据备份
|
||||
### 1. Data Backup
|
||||
|
||||
- 备份数据库,将 mysql 数据手动做备份,按需备份
|
||||
- **Back up the database**: Manually back up MySQL data as needed.
|
||||
|
||||
```bash
|
||||
mysqldump -h<HOST-IP> -P<PORT> -uroot -p"PASSWORD" <库名> hertzbeat_backup-`date +%Y-%m-%d`.sql #单库备份
|
||||
mysqldump -h<HOST-IP> -P<PORT> -uroot -p"PASSWORD" > hertzbeat_backup-`date +%Y-%m-%d`.sql # 整库备份
|
||||
```
|
||||
```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-bak.yml
|
||||
cp -R data data-`date +%Y-%m-%d`. bak
|
||||
```
|
||||
```bash
|
||||
mv application.yml application-bak.yml && mv sureness.yml sureness-bak.yml
|
||||
cp -R data data-`date +%Y-%m-%d`.bak
|
||||
```
|
||||
|
||||
2. 关闭 并移除 HertzBeat 容器
|
||||
### 2. Stop and Remove the HertzBeat Container
|
||||
|
||||
```shell
|
||||
docker stop hertzbeat && docker rm hertzbeat
|
||||
```
|
||||
```bash
|
||||
docker stop hertzbeat && docker rm hertzbeat
|
||||
```
|
||||
|
||||
3. 升级数据库脚本
|
||||
### 3. Upgrade Database Schema
|
||||
|
||||
打开[https://github.com/apache/hertzbeat/tree/master/hertzbeat-manager/src/main/resources/db/migration](https://github.com/apache/hertzbeat/tree/master/hertzbeat-manager/src/main/resources/db/migration), 选择你使用的数据库的目录下相应的 `V160__update_column.sql`文件在 Mysql 执行升级 sql。
|
||||
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. 更换镜像重新启动 HertzBeat 容器
|
||||
### 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
|
||||
```
|
||||
```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. 升级配置文件
|
||||
### 5. Update Configuration Files
|
||||
|
||||
参考备份配置根据自己的需求基础上进行修改。
|
||||
Modify the backup configurations as needed:
|
||||
|
||||
- `application.yml`一般需要修改以下部分
|
||||
- **`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
|
||||
```
|
||||
|
||||
```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
|
||||
- **`sureness.yml`** (Optional, modify for account/password changes):
|
||||
|
||||
jpa:
|
||||
show-sql: false
|
||||
database-platform: org.eclipse.persistence.platform.database.MySQLPlatform
|
||||
database: mysql
|
||||
properties:
|
||||
eclipselink:
|
||||
logging:
|
||||
level: SEVERE
|
||||
```
|
||||
```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]
|
||||
```
|
||||
|
||||
- `sureness.yml`修改是可选的,一般在你需要修改账号密码时
|
||||
### 6. Add Database Drivers
|
||||
|
||||
```yaml
|
||||
# account info config
|
||||
# eg: admin has role [admin,user], password is hertzbeat
|
||||
# eg: tom has role [user], password is hertzbeat
|
||||
# eg: lili has role [guest], plain password is lili, salt is 123, salted password is 1A676730B0C7F54654B0E09184448289
|
||||
account:
|
||||
- appId: admin
|
||||
credential: hertzbeat
|
||||
role: [admin]
|
||||
- appId: tom
|
||||
credential: hertzbeat
|
||||
role: [user]
|
||||
- appId: guest
|
||||
credential: hertzbeat
|
||||
role: [guest]
|
||||
- appId: lili
|
||||
# credential = MD5(password + salt)
|
||||
# plain password: hertzbeat
|
||||
# attention: digest authentication does not support salted encrypted password accounts
|
||||
credential: 94C6B34E7A199A9F9D4E1F208093B489
|
||||
salt: 123
|
||||
role: [user]
|
||||
```
|
||||
Due to Apache Foundation’s 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`:
|
||||
|
||||
6. 添加相应的数据库驱动
|
||||
|
||||
由于 apache 基金会对于 license 合规的要求,HertzBeat 的安装包不能包含 mysql,oracle 等 gpl 许可的依赖,需要用户自行添加,用户可通过以下链接自行下载驱动 jar 放到本地 `ext-lib`目录下,然后启动时将`ext-lib`挂载到容器的 `/opt/hertzbeat/ext-lib`目录。
|
||||
|
||||
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.18.zip)
|
||||
oracle(如果你要监控 oracle,这两个驱动是必须的):
|
||||
[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://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?utm_source=mavenlibs.com)
|
||||
- **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)
|
||||
|
||||
@@ -1,43 +1,41 @@
|
||||
# HertzBeat 从 1.6.1 版本升级到 1.7.0 版本指引-(Helm Mode)
|
||||
# HertzBeat Upgrade Guide from v1.6.1 to v1.7.0 (Helm Mode)
|
||||
|
||||
## 1. 前置准备
|
||||
|
||||
1. 确保已安装以下工具:
|
||||
## 1. Prerequisites
|
||||
|
||||
1. Ensure the following tools are installed:
|
||||
- Helm 3.x
|
||||
- kubectl
|
||||
- Git (可选)
|
||||
- Git (optional)
|
||||
|
||||
2. 确认当前部署信息:
|
||||
2. Verify current deployment information:
|
||||
|
||||
```bash
|
||||
helm list -n <your-namespace>
|
||||
# 如果你老版本的chart包不见了,可以使用以下命令导出values.yaml文件
|
||||
# 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. 数据备份
|
||||
3. Data Backup:
|
||||
|
||||
> 1. 若使用了自定义监控模版
|
||||
> **For custom monitoring templates:**
|
||||
>
|
||||
> - 需要备份 `kubectl cp hertzbeat/hertzbeat-978477f84-fr894:/opt/hertzbeat/define ./define` 当前运行 pod里面的 `/opt/hertzbeat/define` 目录到当前主机下,如果做了持久化 请拷贝持久化目录
|
||||
> - `kubectl cp hertzbeat/hertzbeat-978477f84-fr894:/opt/hertzbeat/define ./define`
|
||||
> - Backup `/opt/hertzbeat/define` from the running pod:
|
||||
>
|
||||
> 2. 若使用外置关系型数据库 Mysql, PostgreSQL数据
|
||||
> ```bash
|
||||
> kubectl cp hertzbeat/hertzbeat-978477f84-fr894:/opt/hertzbeat/define ./define
|
||||
> ```
|
||||
>
|
||||
> - 一般使用helm部署部署都做了持久化,可以选择拷贝持久化目录,也可以通过mysqldump、pgdump等工具完成备份
|
||||
> **For external databases (MySQL/PostgreSQL):**
|
||||
>
|
||||
> ```bash
|
||||
> kubectl get pvc -n hertzbeat
|
||||
> NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
|
||||
> hertzbeat-database Bound pvc-c63cf479-0033-423b-8466-eb00aa181657 4Gi RWO standard 68d
|
||||
> hertzbeat-ext-lib Bound pvc-31fee163-1211-424f-8966-e3e805c23ff5 1Gi RWX standard 68d
|
||||
> hertzbeat-tsdb Bound pvc-4f2ef614-0302-4a4c-8dd4-e68b34e9061c 4Gi RWO standard 68d
|
||||
> ```
|
||||
> - Use `mysqldump`/`pg_dump` or copy PVC directories:
|
||||
>
|
||||
> ```bash
|
||||
> kubectl get pvc -n hertzbeat
|
||||
> ```
|
||||
|
||||
## 2. 升级步骤
|
||||
## 2. Upgrade Steps
|
||||
|
||||
### 1. 拉取最新Chart到本地
|
||||
### 1. Pull the latest Chart
|
||||
|
||||
```bash
|
||||
helm repo update
|
||||
@@ -45,33 +43,32 @@ helm pull hertzbeat/hertzbeat --version 1.7.0 --untar
|
||||
cd hertzbeat
|
||||
```
|
||||
|
||||
或者从GitHub仓库获取(按需修改Chart):
|
||||
Or clone from GitHub:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/hertzbeat/helm-charts.git
|
||||
cd helm-charts/charts/hertzbeat
|
||||
```
|
||||
|
||||
### 2. 修改values.yaml
|
||||
### 2. Update values.yaml
|
||||
|
||||
比较并合并您的自定义配置到新版本values.yaml:
|
||||
Compare and merge configurations:
|
||||
|
||||
```bash
|
||||
# 使用diff工具比较新旧values文件
|
||||
diff -u ../hertzbeat-1.6.1-values.yaml values.yaml
|
||||
vimdiff 对着修改,改完继续diff,无输出则正常
|
||||
# Use vimdiff to compare and merge changes
|
||||
```
|
||||
|
||||
常见需要关注的配置项:
|
||||
Key configurations to check:
|
||||
|
||||
- 镜像版本: `image.tag`
|
||||
- 资源限制: `resources`
|
||||
- 持久化配置: `persistence`
|
||||
- 服务类型: `service.type`
|
||||
- Ingress配置
|
||||
- 数据库配置(如果使用外部数据库)
|
||||
- `image.tag`
|
||||
- `resources`
|
||||
- `persistence`
|
||||
- `service.type`
|
||||
- Ingress settings
|
||||
- External database configurations
|
||||
|
||||
### 3. 测试升级(干运行)
|
||||
### 3. Dry-run Upgrade
|
||||
|
||||
```bash
|
||||
helm upgrade hertzbeat . -n <your-namespace> \
|
||||
@@ -80,24 +77,19 @@ helm upgrade hertzbeat . -n <your-namespace> \
|
||||
--debug
|
||||
```
|
||||
|
||||
### 4. 执行升级
|
||||
### 4. Execute Upgrade
|
||||
|
||||
```bash
|
||||
helm upgrade hertzbeat . -n <your-namespace> \
|
||||
--values values.yaml \
|
||||
--atomic \ # 升级失败自动回滚
|
||||
--timeout 10m # 设置超时时间
|
||||
--atomic \ # Auto-rollback on failure
|
||||
--timeout 10m # Set timeout
|
||||
```
|
||||
|
||||
### 5. 验证升级
|
||||
### 5. Verify Upgrade
|
||||
|
||||
```bash
|
||||
# 检查发布状态
|
||||
helm status hertzbeat -n <your-namespace>
|
||||
|
||||
# 检查Pod状态
|
||||
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
|
||||
```
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
# 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 Foundation’s 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)
|
||||
@@ -1,95 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# HertzBeat 升级指导-(Docker Mode)
|
||||
|
||||
## Docker 方式升级 - Mysql 数据库
|
||||
|
||||
1. 数据备份
|
||||
|
||||
- 备份数据库,将 mysql 数据手动做备份,按需备份
|
||||
|
||||
```bash
|
||||
mysqldump -h<HOST-IP> -P<PORT> -uroot -p"PASSWORD" <库名> hertzbeat_backup-`date +%Y-%m-%d`.sql #单库备份
|
||||
mysqldump -h<HOST-IP> -P<PORT> -uroot -p"PASSWORD" > hertzbeat_backup-`date +%Y-%m-%d`.sql # 整库备份
|
||||
```
|
||||
|
||||
- 备份配置文件及数据目录
|
||||
|
||||
```bash
|
||||
mv application.yml application-bak.yml && mv sureness-bak.yml
|
||||
cp -R data data-`date +%Y-%m-%d`. bak
|
||||
```
|
||||
|
||||
2. 关闭 并移除 HertzBeat 容器
|
||||
|
||||
```shell
|
||||
docker stop hertzbeat && docker rm hertzbeat
|
||||
```
|
||||
|
||||
3. 升级数据库脚本
|
||||
|
||||
打开[https://github.com/apache/hertzbeat/tree/master/hertzbeat-manager/src/main/resources/db/migration](https://github.com/apache/hertzbeat/tree/master/hertzbeat-manager/src/main/resources/db/migration), 选择你使用的数据库的目录下相应的 `V160__update_column.sql`文件在 Mysql 执行升级 sql。
|
||||
|
||||
4. 更换镜像重新启动 HertzBeat 容器
|
||||
|
||||
```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. 升级配置文件
|
||||
|
||||
参考备份配置根据自己的需求基础上进行修改。
|
||||
|
||||
- `application.yml`一般需要修改以下部分
|
||||
|
||||
默认为:
|
||||
|
||||
```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`修改是可选的,一般在你需要修改账号密码时
|
||||
|
||||
```yaml
|
||||
# account info config
|
||||
# eg: admin has role [admin,user], password is hertzbeat
|
||||
# eg: tom has role [user], password is hertzbeat
|
||||
# eg: lili has role [guest], plain password is lili, salt is 123, salted password is 1A676730B0C7F54654B0E09184448289
|
||||
account:
|
||||
- appId: admin
|
||||
credential: hertzbeat
|
||||
role: [admin]
|
||||
- appId: tom
|
||||
credential: hertzbeat
|
||||
role: [user]
|
||||
- appId: guest
|
||||
credential: hertzbeat
|
||||
role: [guest]
|
||||
- appId: lili
|
||||
# credential = MD5(password + salt)
|
||||
# plain password: hertzbeat
|
||||
# attention: digest authentication does not support salted encrypted password accounts
|
||||
credential: 94C6B34E7A199A9F9D4E1F208093B489
|
||||
salt: 123
|
||||
role: [user]
|
||||
```
|
||||
|
||||
6. 添加相应的数据库驱动
|
||||
|
||||
由于 apache 基金会对于 license 合规的要求,HertzBeat 的安装包不能包含 mysql,oracle 等 gpl 许可的依赖,需要用户自行添加,用户可通过以下链接自行下载驱动 jar 放到本地 `ext-lib`目录下,然后启动时将`ext-lib`挂载到容器的 `/opt/hertzbeat/ext-lib`目录。
|
||||
|
||||
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.18.zip)
|
||||
oracle(如果你要监控 oracle,这两个驱动是必须的):
|
||||
[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://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?utm_source=mavenlibs.com)
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# HertzBeat 从 1.6.1 版本升级到 1.7.0 版本指引-(Helm Mode)
|
||||
|
||||
## 1. 前置准备
|
||||
|
||||
1. 确保已安装以下工具:
|
||||
|
||||
- Helm 3.x
|
||||
- kubectl
|
||||
- Git (可选)
|
||||
|
||||
2. 确认当前部署信息:
|
||||
|
||||
```bash
|
||||
helm list -n <your-namespace>
|
||||
# 如果你老版本的chart包不见了,可以使用以下命令导出values.yaml文件
|
||||
helm get values hertzbeat -n <your-namespace> > hertzbeat-1.6.1-values.yaml
|
||||
```
|
||||
|
||||
3. 数据备份
|
||||
|
||||
> 1. 若使用了自定义监控模版
|
||||
>
|
||||
> - 需要备份 `kubectl cp hertzbeat/hertzbeat-978477f84-fr894:/opt/hertzbeat/define ./define` 当前运行 pod里面的 `/opt/hertzbeat/define` 目录到当前主机下,如果做了持久化 请拷贝持久化目录
|
||||
> - `kubectl cp hertzbeat/hertzbeat-978477f84-fr894:/opt/hertzbeat/define ./define`
|
||||
>
|
||||
> 2. 若使用外置关系型数据库 Mysql, PostgreSQL数据
|
||||
>
|
||||
> - 一般使用helm部署部署都做了持久化,可以选择拷贝持久化目录,也可以通过mysqldump、pgdump等工具完成备份
|
||||
>
|
||||
> ```bash
|
||||
> kubectl get pvc -n hertzbeat
|
||||
> NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
|
||||
> hertzbeat-database Bound pvc-c63cf479-0033-423b-8466-eb00aa181657 4Gi RWO standard 68d
|
||||
> hertzbeat-ext-lib Bound pvc-31fee163-1211-424f-8966-e3e805c23ff5 1Gi RWX standard 68d
|
||||
> hertzbeat-tsdb Bound pvc-4f2ef614-0302-4a4c-8dd4-e68b34e9061c 4Gi RWO standard 68d
|
||||
> ```
|
||||
|
||||
## 2. 升级步骤
|
||||
|
||||
### 1. 拉取最新Chart到本地
|
||||
|
||||
```bash
|
||||
helm repo update
|
||||
helm pull hertzbeat/hertzbeat --version 1.7.0 --untar
|
||||
cd hertzbeat
|
||||
```
|
||||
|
||||
或者从GitHub仓库获取(按需修改Chart):
|
||||
|
||||
```bash
|
||||
git clone https://github.com/hertzbeat/helm-charts.git
|
||||
cd helm-charts/charts/hertzbeat
|
||||
```
|
||||
|
||||
### 2. 修改values.yaml
|
||||
|
||||
比较并合并您的自定义配置到新版本values.yaml:
|
||||
|
||||
```bash
|
||||
# 使用diff工具比较新旧values文件
|
||||
diff -u ../hertzbeat-1.6.1-values.yaml values.yaml
|
||||
vimdiff 对着修改,改完继续diff,无输出则正常
|
||||
```
|
||||
|
||||
常见需要关注的配置项:
|
||||
|
||||
- 镜像版本: `image.tag`
|
||||
- 资源限制: `resources`
|
||||
- 持久化配置: `persistence`
|
||||
- 服务类型: `service.type`
|
||||
- Ingress配置
|
||||
- 数据库配置(如果使用外部数据库)
|
||||
|
||||
### 3. 测试升级(干运行)
|
||||
|
||||
```bash
|
||||
helm upgrade hertzbeat . -n <your-namespace> \
|
||||
--values values.yaml \
|
||||
--dry-run \
|
||||
--debug
|
||||
```
|
||||
|
||||
### 4. 执行升级
|
||||
|
||||
```bash
|
||||
helm upgrade hertzbeat . -n <your-namespace> \
|
||||
--values values.yaml \
|
||||
--atomic \ # 升级失败自动回滚
|
||||
--timeout 10m # 设置超时时间
|
||||
```
|
||||
|
||||
### 5. 验证升级
|
||||
|
||||
```bash
|
||||
# 检查发布状态
|
||||
helm status hertzbeat -n <your-namespace>
|
||||
|
||||
# 检查Pod状态
|
||||
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
|
||||
```
|
||||
@@ -358,6 +358,7 @@ The text of each license is the standard Apache 2.0 license.
|
||||
https://mvnrepository.com/artifact/org.apache.xmlbeans/xmlbeans/3.1.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-api/0.12.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-driver-modbus/0.12.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-driver-s7/0.12.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.attoparser/attoparser/2.0.7.RELEASE Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.freemarker/freemarker/2.3.32 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.flywaydb/flyway-core/10.11.1
|
||||
@@ -434,10 +435,7 @@ The text of each license is the standard Apache 2.0 license.
|
||||
https://mvnrepository.com/artifact/org.webjars/swagger-ui/5.10.3 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.google.flatbuffers/flatbuffers-java/1.12.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/com.vesoft/client/3.6.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-api/1.49.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-sdk/1.49.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-sdk-logs/1.49.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/io.opentelemetry/opentelemetry-exporter-otlp/1.49.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/io.opentelemetry.instrumentation/opentelemetry-spring-boot-starter-2.15.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/io.opentelemetry.instrumentation/opentelemetry-logback-appender-1.0 Apache-2.0
|
||||
|
||||
========================================================================
|
||||
|
||||
@@ -358,6 +358,7 @@ The text of each license is the standard Apache 2.0 license.
|
||||
https://mvnrepository.com/artifact/org.apache.xmlbeans/xmlbeans/3.1.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-api/0.12.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-driver-modbus/0.12.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-driver-s7/0.12.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.attoparser/attoparser/2.0.7.RELEASE Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.freemarker/freemarker/2.3.32 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.flywaydb/flyway-core/10.11.1
|
||||
|
||||
@@ -291,6 +291,7 @@ The text of each license is the standard Apache 2.0 license.
|
||||
https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.19 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-api/0.12.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-driver-modbus/0.12.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.apache.plc4x/plc4j-driver-s7/0.12.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator/8.0.1.Final Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.lz4/lz4-java/1.8.0 Apache-2.0
|
||||
https://mvnrepository.com/artifact/org.mongodb/mongodb-driver-core/4.6.1 Apache-2.0
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<module>hertzbeat-push</module>
|
||||
<module>hertzbeat-plugin</module>
|
||||
<module>hertzbeat-grafana</module>
|
||||
<module>hertzbeat-log</module>
|
||||
<module>hertzbeat-otel</module>
|
||||
<module>hertzbeat-e2e</module>
|
||||
<module>hertzbeat-base</module>
|
||||
<module>hertzbeat-mcp</module>
|
||||
@@ -175,7 +175,7 @@
|
||||
<arrow.version>18.1.0</arrow.version>
|
||||
<snappy-java.version>1.1.10.7</snappy-java.version>
|
||||
<sshd-sftp.version>2.13.1</sshd-sftp.version>
|
||||
<opentelemetry-api.version>1.43.0</opentelemetry-api.version>
|
||||
<opentelemetry-starter.version>2.15.0</opentelemetry-starter.version>
|
||||
<opentelemetry-logback.version>2.14.0-alpha</opentelemetry-logback.version>
|
||||
</properties>
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
<!-- log -->
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-log</artifactId>
|
||||
<artifactId>hertzbeat-otel</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
</dependency>
|
||||
<!-- collector-basic -->
|
||||
@@ -477,33 +477,18 @@
|
||||
<version>${sshd-sftp.version}</version>
|
||||
</dependency>
|
||||
<!-- OpenTelemetry -->
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-api</artifactId>
|
||||
<version>${opentelemetry-api.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-sdk</artifactId>
|
||||
<version>${opentelemetry-api.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-sdk-logs</artifactId>
|
||||
<version>${opentelemetry-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-exporter-otlp</artifactId>
|
||||
<version>${opentelemetry-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry.instrumentation</groupId>
|
||||
<artifactId>opentelemetry-logback-appender-1.0</artifactId>
|
||||
<version>${opentelemetry-logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry.instrumentation</groupId>
|
||||
<artifactId>opentelemetry-instrumentation-bom</artifactId>
|
||||
<version>${opentelemetry-starter.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ if [ ! -d $LOGS_DIR ]; then
|
||||
fi
|
||||
|
||||
# JVM Configuration
|
||||
JAVA_OPTS=" -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED"
|
||||
JAVA_OPTS=" -Dfile.encoding=UTF-8 -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED"
|
||||
|
||||
# JVM Configuration
|
||||
JAVA_MEM_OPTS=" -server -XX:SurvivorRatio=6 -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$LOGS_DIR"
|
||||
|
||||
@@ -51,7 +51,7 @@ if not exist %LOGS_DIR% (
|
||||
)
|
||||
|
||||
rem JVM Configuration
|
||||
set JAVA_OPTS= -Duser.timezone=Asia/Shanghai -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED
|
||||
set JAVA_OPTS= -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED
|
||||
|
||||
set JAVA_MEM_OPTS= -server -XX:SurvivorRatio=6 -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=%LOGS_DIR%
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ if [ ! -d $LOGS_DIR ]; then
|
||||
fi
|
||||
|
||||
# JVM Configuration
|
||||
JAVA_OPTS=" -Duser.timezone=Asia/Shanghai -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED"
|
||||
JAVA_OPTS=" -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED"
|
||||
|
||||
JAVA_MEM_OPTS=" -server -XX:SurvivorRatio=6 -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$LOGS_DIR"
|
||||
|
||||
|
||||
@@ -47,9 +47,9 @@ fi
|
||||
|
||||
# JVM Configuration
|
||||
if [ -z "$JAVA_OPTS" ]; then
|
||||
JAVA_OPTS=" -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED"
|
||||
JAVA_OPTS=" -Dfile.encoding=UTF-8 -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED"
|
||||
else
|
||||
JAVA_OPTS="${JAVA_OPTS} -Doracle.jdbc.timezoneAsRegion=false"
|
||||
JAVA_OPTS="${JAVA_OPTS} -Dfile.encoding=UTF-8 -Doracle.jdbc.timezoneAsRegion=false"
|
||||
fi
|
||||
|
||||
# JVM Configuration
|
||||
|
||||
@@ -52,7 +52,7 @@ if not exist %LOGS_DIR% (
|
||||
|
||||
rem JVM Configuration
|
||||
|
||||
set JAVA_OPTS= -Duser.timezone=Asia/Shanghai -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED
|
||||
set JAVA_OPTS= -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED
|
||||
|
||||
set JAVA_MEM_OPTS= -server -XX:SurvivorRatio=6 -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=%LOGS_DIR%
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ fi
|
||||
|
||||
|
||||
# JVM Configuration
|
||||
JAVA_OPTS=" -Duser.timezone=Asia/Shanghai -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED"
|
||||
JAVA_OPTS=" -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Doracle.jdbc.timezoneAsRegion=false --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED"
|
||||
|
||||
JAVA_MEM_OPTS=" -server -XX:SurvivorRatio=6 -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$LOGS_DIR"
|
||||
|
||||
|
||||
Submodule script/helm/hertzbeat-helm-chart updated: bdb57c002b...2fa5905e9c
@@ -21,6 +21,7 @@ export class Monitor {
|
||||
id!: number;
|
||||
name!: string;
|
||||
app!: string;
|
||||
scrape!: string;
|
||||
host!: string;
|
||||
intervals: number = 60;
|
||||
// Monitoring status 0: Paused, 1: Up, 2: Down
|
||||
|
||||
@@ -68,15 +68,20 @@ export class AlertIntegrationComponent implements OnInit {
|
||||
name: this.i18nSvc.fanyi('alert.integration.source.skywalking'),
|
||||
icon: 'assets/img/integration/skywalking.svg'
|
||||
},
|
||||
{
|
||||
id: 'tencent',
|
||||
name: this.i18nSvc.fanyi('alert.integration.source.tencent'),
|
||||
icon: 'assets/img/integration/tencent.svg'
|
||||
},
|
||||
{
|
||||
id: 'uptime-kuma',
|
||||
name: this.i18nSvc.fanyi('alert.integration.source.uptime-kuma'),
|
||||
icon: 'assets/img/integration/uptime-kuma.svg'
|
||||
},
|
||||
{
|
||||
id: 'zabbix',
|
||||
name: this.i18nSvc.fanyi('alert.integration.source.zabbix'),
|
||||
icon: 'assets/img/integration/zabbix.svg'
|
||||
},
|
||||
{
|
||||
id: 'tencent',
|
||||
name: this.i18nSvc.fanyi('alert.integration.source.tencent'),
|
||||
icon: 'assets/img/integration/tencent.svg'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -145,6 +145,9 @@ export class MonitorDetailComponent implements OnInit, OnDestroy {
|
||||
if (message.code === 0) {
|
||||
this.monitor = message.data.monitor;
|
||||
this.app = this.monitor?.app;
|
||||
if (this.monitor.scrape && this.monitor.scrape != 'static') {
|
||||
this.app = this.monitor.scrape;
|
||||
}
|
||||
let params: Param[] = message.data.params;
|
||||
params.forEach(param => {
|
||||
if (param.field === 'port') {
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
[paramValueMap]="paramValueMap"
|
||||
[collector]="collector"
|
||||
[collectors]="collectors"
|
||||
[sdParams]="sdParams"
|
||||
[sdDefines]="sdDefines"
|
||||
(scrapeChange)="onScrapeChange($event)"
|
||||
(formCancel)="onCancel()"
|
||||
(formSubmit)="onSubmit($event)"
|
||||
(formDetect)="onDetect($event)"
|
||||
|
||||
@@ -24,8 +24,8 @@ import { I18NService } from '@core';
|
||||
import { ALAIN_I18N_TOKEN, TitleService } from '@delon/theme';
|
||||
import { NzNotificationService } from 'ng-zorro-antd/notification';
|
||||
import { NzUploadFile } from 'ng-zorro-antd/upload';
|
||||
import { throwError } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { finalize, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { Collector } from '../../../pojo/Collector';
|
||||
import { GrafanaDashboard } from '../../../pojo/GrafanaDashboard';
|
||||
@@ -54,6 +54,8 @@ export class MonitorEditComponent implements OnInit {
|
||||
@Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService
|
||||
) {}
|
||||
|
||||
sdDefines: ParamDefine[] = [];
|
||||
sdParams: Param[] = [];
|
||||
paramDefines!: ParamDefine[];
|
||||
hostName!: string;
|
||||
params!: Param[];
|
||||
@@ -83,6 +85,9 @@ export class MonitorEditComponent implements OnInit {
|
||||
if (message.code === 0) {
|
||||
let paramValueMap = new Map<String, Param>();
|
||||
this.monitor = message.data.monitor;
|
||||
if (this.monitor.scrape == null || this.monitor.scrape == undefined) {
|
||||
this.monitor.scrape = 'static';
|
||||
}
|
||||
this.grafanaDashboard = message.data.grafanaDashboard != undefined ? message.data.grafanaDashboard : new GrafanaDashboard();
|
||||
this.collector = message.data.collector == null ? '' : message.data.collector;
|
||||
this.titleSvc.setTitleByI18n(`monitor.app.${this.monitor.app}`);
|
||||
@@ -172,27 +177,142 @@ export class MonitorEditComponent implements OnInit {
|
||||
return this.collectorSvc.getCollectors();
|
||||
})
|
||||
)
|
||||
.pipe(
|
||||
switchMap((message: any) => {
|
||||
if (message.code === 0) {
|
||||
this.collectors = message.data.content?.map((item: { collector: any }) => item.collector);
|
||||
} else {
|
||||
console.warn(message.msg);
|
||||
}
|
||||
if (this.monitor.scrape == 'static') {
|
||||
return of({ code: 0, data: [], msg: '' });
|
||||
} else {
|
||||
return this.appDefineSvc.getAppParamsDefine(this.monitor.scrape);
|
||||
}
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
message => {
|
||||
if (message.code === 0) {
|
||||
this.collectors = message.data.content?.map(item => item.collector);
|
||||
if (message.data.length > 0) {
|
||||
let params: Param[] = [];
|
||||
let paramDefines: ParamDefine[] = [];
|
||||
message.data.forEach(define => {
|
||||
let param = this.paramValueMap.get(define.field);
|
||||
if (param === undefined) {
|
||||
param = new Param();
|
||||
param.field = define.field;
|
||||
if (define.type === 'number') {
|
||||
param.type = 0;
|
||||
} else if (define.type === 'key-value') {
|
||||
param.type = 3;
|
||||
} else if (define.type === 'array') {
|
||||
param.type = 4;
|
||||
} else {
|
||||
param.type = 1;
|
||||
}
|
||||
if (define.type === 'boolean') {
|
||||
param.paramValue = define.defaultValue == 'true';
|
||||
} else if (param.field === 'host') {
|
||||
param.paramValue = this.monitor.host;
|
||||
} else if (define.defaultValue != undefined) {
|
||||
if (define.type === 'number') {
|
||||
param.paramValue = Number(define.defaultValue);
|
||||
} else if (define.type === 'boolean') {
|
||||
param.paramValue = define.defaultValue.toLowerCase() == 'true';
|
||||
} else {
|
||||
param.paramValue = define.defaultValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (define.type === 'boolean') {
|
||||
if (param.paramValue != null) {
|
||||
param.paramValue = param.paramValue.toLowerCase() == 'true';
|
||||
} else {
|
||||
param.paramValue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
define.name = this.i18nSvc.fanyi(`monitor.app.${this.monitor.scrape}.param.${define.field}`);
|
||||
if (define.placeholder == null && this.i18nSvc.fanyi(`monitor.${define.field}.tip`) != `monitor.${define.field}.tip`) {
|
||||
define.placeholder = this.i18nSvc.fanyi(`monitor.${define.field}.tip`);
|
||||
}
|
||||
params.push(param);
|
||||
paramDefines.push(define);
|
||||
});
|
||||
this.sdParams = [...params];
|
||||
this.sdDefines = [...paramDefines];
|
||||
}
|
||||
} else {
|
||||
console.warn(message.msg);
|
||||
}
|
||||
this.isSpinning = false;
|
||||
},
|
||||
error => {
|
||||
console.error(error);
|
||||
this.isSpinning = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onScrapeChange(scrapeValue: string) {
|
||||
this.monitor.scrape = scrapeValue;
|
||||
if (this.monitor.scrape !== 'static') {
|
||||
this.isSpinning = true;
|
||||
let queryScrapeDefine$ = this.appDefineSvc
|
||||
.getAppParamsDefine(this.monitor.scrape)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
queryScrapeDefine$.unsubscribe();
|
||||
this.isSpinning = false;
|
||||
})
|
||||
)
|
||||
.subscribe(message => {
|
||||
if (message.code === 0) {
|
||||
let params: Param[] = [];
|
||||
let paramDefines: ParamDefine[] = [];
|
||||
message.data.forEach(define => {
|
||||
let param = new Param();
|
||||
param.field = define.field;
|
||||
if (define.type === 'number') {
|
||||
param.type = 0;
|
||||
} else if (define.type === 'key-value') {
|
||||
param.type = 3;
|
||||
} else if (define.type === 'array') {
|
||||
param.type = 4;
|
||||
} else {
|
||||
param.type = 1;
|
||||
}
|
||||
if (define.type === 'boolean') {
|
||||
param.paramValue = false;
|
||||
}
|
||||
if (define.defaultValue != undefined) {
|
||||
if (define.type === 'number') {
|
||||
param.paramValue = Number(define.defaultValue);
|
||||
} else if (define.type === 'boolean') {
|
||||
param.paramValue = define.defaultValue.toLowerCase() == 'true';
|
||||
} else {
|
||||
param.paramValue = define.defaultValue;
|
||||
}
|
||||
}
|
||||
define.name = this.i18nSvc.fanyi(`monitor.app.${this.monitor.scrape}.param.${define.field}`);
|
||||
if (define.placeholder == null && this.i18nSvc.fanyi(`monitor.${define.field}.tip`) != `monitor.${define.field}.tip`) {
|
||||
define.placeholder = this.i18nSvc.fanyi(`monitor.${define.field}.tip`);
|
||||
}
|
||||
params.push(param);
|
||||
paramDefines.push(define);
|
||||
});
|
||||
this.sdParams = [...params];
|
||||
this.sdDefines = [...paramDefines];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit(info: any) {
|
||||
let addMonitor = {
|
||||
monitor: info.monitor,
|
||||
collector: info.collector,
|
||||
params: info.params.concat(info.advancedParams),
|
||||
params: info.params.concat(info.advancedParams).concat(info.sdParams),
|
||||
grafanaDashboard: info.grafanaDashboard
|
||||
};
|
||||
this.spinningTip = 'Loading...';
|
||||
@@ -218,7 +338,7 @@ export class MonitorEditComponent implements OnInit {
|
||||
let detectMonitor = {
|
||||
monitor: info.monitor,
|
||||
collector: info.collector,
|
||||
params: info.params.concat(info.advancedParams)
|
||||
params: info.params.concat(info.advancedParams).concat(info.sdParams)
|
||||
};
|
||||
this.spinningTip = this.i18nSvc.fanyi('monitor.spinning-tip.detecting');
|
||||
this.isSpinning = true;
|
||||
|
||||
@@ -21,6 +21,45 @@
|
||||
<div class="-inner-content">
|
||||
<form nz-form #form="ngForm">
|
||||
<nz-form-item>
|
||||
<nz-form-control nzSpan="4" nzOffset="9" [nzErrorTip]="'validation.required' | i18n">
|
||||
<nz-select
|
||||
style="text-align: center"
|
||||
[nzDropdownStyle]="{ textAlign: 'center' }"
|
||||
[(ngModel)]="monitor.scrape"
|
||||
name="scrape"
|
||||
(ngModelChange)="onScrapeChange($event)"
|
||||
>
|
||||
<nz-option nzValue="static" [nzLabel]="'monitor.scrape.type.static' | i18n"></nz-option>
|
||||
<nz-option nzValue="http_sd" [nzLabel]="'monitor.scrape.type.http_sd' | i18n"></nz-option>
|
||||
</nz-select>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<ng-container *ngIf="monitor.scrape != 'static'">
|
||||
<ng-container *ngFor="let sdPramDefine of sdDefines; let i = index">
|
||||
<nz-form-item *ngIf="sdParams[i].display !== false">
|
||||
<nz-form-label nzSpan="7" [nzRequired]="sdPramDefine.required" [nzFor]="sdPramDefine.field"
|
||||
>{{ sdPramDefine.name }}
|
||||
</nz-form-label>
|
||||
<nz-form-control nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
|
||||
<app-form-field
|
||||
[item]="sdPramDefine"
|
||||
[name]="sdPramDefine.field"
|
||||
[(ngModel)]="sdParams[i].paramValue"
|
||||
(ngModelChange)="
|
||||
sdPramDefine.type === 'boolean'
|
||||
? onParamBooleanChanged($event, sdPramDefine.field)
|
||||
: sdPramDefine.type === 'radio'
|
||||
? onDependChanged($event, sdPramDefine.field)
|
||||
: null
|
||||
"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<nz-form-item *ngIf="monitor.scrape == 'static'">
|
||||
<nz-form-label nzSpan="7" [nzRequired]="true" [nzFor]="'host'">{{ hostName ? hostName : ('monitor.host' | i18n) }} </nz-form-label>
|
||||
<nz-form-control nzSpan="8" [nzErrorTip]="'validation.required' | i18n">
|
||||
<app-form-field
|
||||
@@ -53,6 +92,8 @@
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-divider></nz-divider>
|
||||
|
||||
<ng-container *ngFor="let paramDefine of paramDefines; let i = index">
|
||||
<nz-form-item *ngIf="params[i].display !== false && paramDefine.field !== 'host'">
|
||||
<nz-form-label nzSpan="7" [nzRequired]="paramDefine.required" [nzFor]="paramDefine.field">{{ paramDefine.name }} </nz-form-label>
|
||||
|
||||
@@ -46,11 +46,14 @@ export class MonitorFormComponent implements OnChanges {
|
||||
@Input() paramDefines!: ParamDefine[];
|
||||
@Input() advancedParamDefines!: ParamDefine[];
|
||||
@Input() paramValueMap!: Map<String, Param>;
|
||||
@Input() sdDefines!: ParamDefine[];
|
||||
@Input() sdParams!: Param[];
|
||||
|
||||
@Output() readonly formSubmit = new EventEmitter<any>();
|
||||
@Output() readonly formCancel = new EventEmitter<any>();
|
||||
@Output() readonly formDetect = new EventEmitter<any>();
|
||||
@Output() readonly hostChange = new EventEmitter<string>();
|
||||
@Output() readonly scrapeChange = new EventEmitter<string>();
|
||||
@Output() readonly collectorChange = new EventEmitter<string>();
|
||||
|
||||
hasAdvancedParams: boolean = false;
|
||||
@@ -87,7 +90,7 @@ export class MonitorFormComponent implements OnChanges {
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.monitor.host = this.monitor.host.trim();
|
||||
this.monitor.host = this.monitor.host ? this.monitor.host.trim() : '';
|
||||
this.monitor.name = this.monitor.name.trim();
|
||||
// todo Set the host property value separately for now
|
||||
this.params.forEach(param => {
|
||||
@@ -103,7 +106,18 @@ export class MonitorFormComponent implements OnChanges {
|
||||
param.paramValue = (param.paramValue as string).trim();
|
||||
}
|
||||
});
|
||||
this.formDetect.emit({ monitor: this.monitor, params: this.params, advancedParams: this.advancedParams, collector: this.collector });
|
||||
this.sdParams.forEach(param => {
|
||||
if (param.paramValue != null && typeof param.paramValue == 'string') {
|
||||
param.paramValue = (param.paramValue as string).trim();
|
||||
}
|
||||
});
|
||||
this.formDetect.emit({
|
||||
monitor: this.monitor,
|
||||
sdParams: this.sdParams,
|
||||
params: this.params,
|
||||
advancedParams: this.advancedParams,
|
||||
collector: this.collector
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(formGroup: FormGroup) {
|
||||
@@ -132,8 +146,14 @@ export class MonitorFormComponent implements OnChanges {
|
||||
param.paramValue = (param.paramValue as string).trim();
|
||||
}
|
||||
});
|
||||
this.sdParams.forEach(param => {
|
||||
if (param.paramValue != null && typeof param.paramValue == 'string') {
|
||||
param.paramValue = (param.paramValue as string).trim();
|
||||
}
|
||||
});
|
||||
this.formSubmit.emit({
|
||||
monitor: this.monitor,
|
||||
sdParams: this.sdParams,
|
||||
params: this.params,
|
||||
advancedParams: this.advancedParams,
|
||||
collector: this.collector,
|
||||
@@ -145,6 +165,10 @@ export class MonitorFormComponent implements OnChanges {
|
||||
this.formCancel.emit();
|
||||
}
|
||||
|
||||
onScrapeChange(scrape: string) {
|
||||
this.scrapeChange.emit(scrape);
|
||||
}
|
||||
|
||||
onHostChange(host: string) {
|
||||
this.hostChange.emit(host);
|
||||
}
|
||||
@@ -193,6 +217,17 @@ export class MonitorFormComponent implements OnChanges {
|
||||
}
|
||||
}
|
||||
});
|
||||
this.sdDefines.forEach((paramDefine, index) => {
|
||||
if (paramDefine.depend) {
|
||||
let fieldValues = new Map(Object.entries(paramDefine.depend)).get(dependField);
|
||||
if (fieldValues) {
|
||||
this.sdParams[index].display = false;
|
||||
if (fieldValues.map(String).includes(dependValue)) {
|
||||
this.sdParams[index].display = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this.advancedParamDefines.forEach((advancedParamDefine, index) => {
|
||||
if (advancedParamDefine.depend) {
|
||||
let fieldValues = new Map(Object.entries(advancedParamDefine.depend)).get(dependField);
|
||||
|
||||
@@ -174,6 +174,7 @@
|
||||
<div class="monitor-card-info">
|
||||
<div class="monitor-card-info-item host-item">
|
||||
<button
|
||||
*ngIf="data.scrape == 'static' || !data.scrape"
|
||||
nz-button
|
||||
nzType="text"
|
||||
[cdkCopyToClipboard]="data.host"
|
||||
@@ -184,6 +185,10 @@
|
||||
<i nz-icon nzType="global"></i>
|
||||
{{ data.host }}
|
||||
</button>
|
||||
<button nz-button nzType="text" *ngIf="data.scrape && data.scrape != 'static'">
|
||||
<i nz-icon nzType="partition"></i>
|
||||
{{ 'monitor.scrape.type.' + data.scrape | i18n }}
|
||||
</button>
|
||||
|
||||
<button nz-button nz-dropdown [nzDropdownMenu]="actionMenu" class="action-button">
|
||||
<span nz-icon nzType="ellipsis"></span>
|
||||
|
||||
@@ -28,7 +28,6 @@ import { NzUploadChangeParam } from 'ng-zorro-antd/upload';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
|
||||
import { Monitor } from '../../../pojo/Monitor';
|
||||
import { AppDefineService } from '../../../service/app-define.service';
|
||||
import { MemoryStorageService } from '../../../service/memory-storage.service';
|
||||
import { MonitorService } from '../../../service/monitor.service';
|
||||
import { findDeepestSelected, renderLabelColor } from '../../../shared/utils/common-util';
|
||||
@@ -46,7 +45,6 @@ export class MonitorListComponent implements OnInit, OnDestroy {
|
||||
private notifySvc: NzNotificationService,
|
||||
private monitorSvc: MonitorService,
|
||||
private storageSvc: MemoryStorageService,
|
||||
private appDefineSvc: AppDefineService,
|
||||
private menuService: MenuService,
|
||||
@Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService
|
||||
) {}
|
||||
@@ -506,43 +504,29 @@ export class MonitorListComponent implements OnInit, OnDestroy {
|
||||
onAppSwitchModalOpen() {
|
||||
this.appSwitchModalVisible = true;
|
||||
this.appSearchLoading = true;
|
||||
const getHierarchy$ = this.appDefineSvc
|
||||
.getAppHierarchy(this.i18nSvc.defaultLang)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
getHierarchy$.unsubscribe();
|
||||
this.appSearchLoading = false;
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
message => {
|
||||
if (message.code === 0) {
|
||||
let appMenus: Record<string, any> = {};
|
||||
message.data.forEach((app: any) => {
|
||||
let menus = appMenus[app.category];
|
||||
app.categoryLabel = this.i18nSvc.fanyi(`menu.monitor.${app.category}`);
|
||||
if (app.categoryLabel == `menu.monitor.${app.category}`) {
|
||||
app.categoryLabel = app.category.toUpperCase();
|
||||
}
|
||||
if (menus == undefined) {
|
||||
menus = { label: app.categoryLabel, child: [app] };
|
||||
} else {
|
||||
menus.child.push(app);
|
||||
}
|
||||
appMenus[app.category] = menus;
|
||||
});
|
||||
this.appSearchOrigin = Object.entries(appMenus);
|
||||
this.appSearchOrigin.sort((a, b) => {
|
||||
return b[1].length - a[1].length;
|
||||
});
|
||||
} else {
|
||||
console.warn(message.msg);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.warn(error.msg);
|
||||
}
|
||||
);
|
||||
let appMenus: Record<string, any> = {};
|
||||
let hierarchy: any[] = this.storageSvc.getData('hierarchy');
|
||||
hierarchy.forEach((app: any) => {
|
||||
if (app.category == '__system__') {
|
||||
return;
|
||||
}
|
||||
let menus = appMenus[app.category];
|
||||
app.categoryLabel = this.i18nSvc.fanyi(`menu.monitor.${app.category}`);
|
||||
if (app.categoryLabel == `menu.monitor.${app.category}`) {
|
||||
app.categoryLabel = app.category.toUpperCase();
|
||||
}
|
||||
if (menus == undefined) {
|
||||
menus = { label: app.categoryLabel, child: [app] };
|
||||
} else {
|
||||
menus.child.push(app);
|
||||
}
|
||||
appMenus[app.category] = menus;
|
||||
});
|
||||
this.appSearchOrigin = Object.entries(appMenus);
|
||||
this.appSearchOrigin.sort((a, b) => {
|
||||
return b[1].length - a[1].length;
|
||||
});
|
||||
this.appSearchLoading = false;
|
||||
}
|
||||
|
||||
onAppSwitchModalCancel() {
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
[advancedParamDefines]="advancedParamDefines"
|
||||
[collector]="collector"
|
||||
[collectors]="collectors"
|
||||
[sdParams]="sdParams"
|
||||
[sdDefines]="sdDefines"
|
||||
(scrapeChange)="onScrapeChange($event)"
|
||||
(hostChange)="onHostChange($event)"
|
||||
(formCancel)="onCancel()"
|
||||
(formSubmit)="onSubmit($event)"
|
||||
|
||||
@@ -22,7 +22,8 @@ import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
import { I18NService } from '@core';
|
||||
import { ALAIN_I18N_TOKEN, TitleService } from '@delon/theme';
|
||||
import { NzNotificationService } from 'ng-zorro-antd/notification';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { of } from 'rxjs';
|
||||
import { finalize, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { Collector } from '../../../pojo/Collector';
|
||||
import { GrafanaDashboard } from '../../../pojo/GrafanaDashboard';
|
||||
@@ -41,6 +42,8 @@ import { generateReadableRandomString } from '../../../shared/utils/common-util'
|
||||
styles: []
|
||||
})
|
||||
export class MonitorNewComponent implements OnInit {
|
||||
sdDefines: ParamDefine[] = [];
|
||||
sdParams: Param[] = [];
|
||||
paramDefines!: ParamDefine[];
|
||||
hostName!: string;
|
||||
params!: Param[];
|
||||
@@ -75,6 +78,7 @@ export class MonitorNewComponent implements OnInit {
|
||||
if (this.monitor.app == '') {
|
||||
this.router.navigateByUrl('/monitors/new?app=website');
|
||||
}
|
||||
this.monitor.scrape = paramMap.get('scrape') || 'static';
|
||||
this.titleSvc.setTitleByI18n(`monitor.app.${this.monitor.app}`);
|
||||
this.isSpinning = true;
|
||||
return this.appDefineSvc.getAppParamsDefine(this.monitor.app);
|
||||
@@ -140,21 +144,125 @@ export class MonitorNewComponent implements OnInit {
|
||||
return this.collectorSvc.getCollectors();
|
||||
})
|
||||
)
|
||||
.pipe(
|
||||
switchMap((message: any) => {
|
||||
if (message.code === 0) {
|
||||
this.collectors = message.data.content?.map((item: { collector: any }) => item.collector);
|
||||
} else {
|
||||
console.warn(message.msg);
|
||||
}
|
||||
if (this.monitor.scrape == 'static') {
|
||||
return of({ code: 0, data: [], msg: '' });
|
||||
} else {
|
||||
return this.appDefineSvc.getAppParamsDefine(this.monitor.scrape);
|
||||
}
|
||||
})
|
||||
)
|
||||
.subscribe(
|
||||
message => {
|
||||
if (message.code === 0) {
|
||||
this.collectors = message.data.content?.map(item => item.collector);
|
||||
if (message.data.length > 0) {
|
||||
let params: Param[] = [];
|
||||
let paramDefines: ParamDefine[] = [];
|
||||
message.data.forEach(define => {
|
||||
let param = new Param();
|
||||
param.field = define.field;
|
||||
if (define.type === 'number') {
|
||||
param.type = 0;
|
||||
} else if (define.type === 'key-value') {
|
||||
param.type = 3;
|
||||
} else if (define.type === 'array') {
|
||||
param.type = 4;
|
||||
} else {
|
||||
param.type = 1;
|
||||
}
|
||||
if (define.type === 'boolean') {
|
||||
param.paramValue = false;
|
||||
}
|
||||
if (define.defaultValue != undefined) {
|
||||
if (define.type === 'number') {
|
||||
param.paramValue = Number(define.defaultValue);
|
||||
} else if (define.type === 'boolean') {
|
||||
param.paramValue = define.defaultValue.toLowerCase() == 'true';
|
||||
} else {
|
||||
param.paramValue = define.defaultValue;
|
||||
}
|
||||
}
|
||||
define.name = this.i18nSvc.fanyi(`monitor.app.${this.monitor.scrape}.param.${define.field}`);
|
||||
if (define.placeholder == null && this.i18nSvc.fanyi(`monitor.${define.field}.tip`) != `monitor.${define.field}.tip`) {
|
||||
define.placeholder = this.i18nSvc.fanyi(`monitor.${define.field}.tip`);
|
||||
}
|
||||
params.push(param);
|
||||
paramDefines.push(define);
|
||||
});
|
||||
this.sdParams = [...params];
|
||||
this.sdDefines = [...paramDefines];
|
||||
}
|
||||
} else {
|
||||
console.warn(message.msg);
|
||||
}
|
||||
this.isSpinning = false;
|
||||
},
|
||||
error => {
|
||||
this.isSpinning = true;
|
||||
this.isSpinning = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onScrapeChange(scrapeValue: string) {
|
||||
this.monitor.scrape = scrapeValue;
|
||||
if (this.monitor.scrape !== 'static') {
|
||||
this.isSpinning = true;
|
||||
let queryScrapeDefine$ = this.appDefineSvc
|
||||
.getAppParamsDefine(this.monitor.scrape)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
queryScrapeDefine$.unsubscribe();
|
||||
this.isSpinning = false;
|
||||
})
|
||||
)
|
||||
.subscribe(message => {
|
||||
if (message.code === 0) {
|
||||
let params: Param[] = [];
|
||||
let paramDefines: ParamDefine[] = [];
|
||||
message.data.forEach(define => {
|
||||
let param = new Param();
|
||||
param.field = define.field;
|
||||
if (define.type === 'number') {
|
||||
param.type = 0;
|
||||
} else if (define.type === 'key-value') {
|
||||
param.type = 3;
|
||||
} else if (define.type === 'array') {
|
||||
param.type = 4;
|
||||
} else {
|
||||
param.type = 1;
|
||||
}
|
||||
if (define.type === 'boolean') {
|
||||
param.paramValue = false;
|
||||
}
|
||||
if (define.defaultValue != undefined) {
|
||||
if (define.type === 'number') {
|
||||
param.paramValue = Number(define.defaultValue);
|
||||
} else if (define.type === 'boolean') {
|
||||
param.paramValue = define.defaultValue.toLowerCase() == 'true';
|
||||
} else {
|
||||
param.paramValue = define.defaultValue;
|
||||
}
|
||||
}
|
||||
define.name = this.i18nSvc.fanyi(`monitor.app.${this.monitor.scrape}.param.${define.field}`);
|
||||
if (define.placeholder == null && this.i18nSvc.fanyi(`monitor.${define.field}.tip`) != `monitor.${define.field}.tip`) {
|
||||
define.placeholder = this.i18nSvc.fanyi(`monitor.${define.field}.tip`);
|
||||
}
|
||||
params.push(param);
|
||||
paramDefines.push(define);
|
||||
});
|
||||
this.sdParams = [...params];
|
||||
this.sdDefines = [...paramDefines];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onHostChange(hostValue: string) {
|
||||
if (this.monitor.name == undefined || this.monitor.name == '') {
|
||||
this.monitor.name = generateReadableRandomString();
|
||||
@@ -165,7 +273,7 @@ export class MonitorNewComponent implements OnInit {
|
||||
let addMonitor = {
|
||||
monitor: info.monitor,
|
||||
collector: info.collector,
|
||||
params: info.params.concat(info.advancedParams),
|
||||
params: info.params.concat(info.advancedParams).concat(info.sdParams),
|
||||
grafanaDashboard: info.grafanaDashboard
|
||||
};
|
||||
this.spinningTip = 'Loading...';
|
||||
@@ -191,7 +299,7 @@ export class MonitorNewComponent implements OnInit {
|
||||
let detectMonitor = {
|
||||
monitor: info.monitor,
|
||||
collector: info.collector,
|
||||
params: info.params.concat(info.advancedParams)
|
||||
params: info.params.concat(info.advancedParams).concat(info.sdParams)
|
||||
};
|
||||
this.spinningTip = this.i18nSvc.fanyi('monitor.spinning-tip.detecting');
|
||||
this.isSpinning = true;
|
||||
|
||||
@@ -94,6 +94,9 @@ export class DefineComponent implements OnInit {
|
||||
if (app.value == 'prometheus') {
|
||||
return;
|
||||
}
|
||||
if (app.category == '__system__') {
|
||||
return;
|
||||
}
|
||||
this.appLabel[app.value] = app.label;
|
||||
let menus = appMenus[app.category];
|
||||
if (menus == undefined) {
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
Send Zabbix alerts to the HertzBeat alert platform via Webhook.
|
||||
|
||||
### Step 1: Create Custom Webhook Media Type in Zabbix
|
||||
1. Log in to Zabbix Web interface
|
||||
2. Go to **Administration** > **Media types** > **Create media type**
|
||||
3. Configure basic information
|
||||
- Name: HertzBeat Webhook
|
||||
- Type: Webhook
|
||||
4. Add the following fields in the **Parameters** section
|
||||
|
||||
| Name | Value |
|
||||
|-----|-----|
|
||||
| URL | http://your-hertzbeat-server:1157/api/report/zabbix |
|
||||
| AlertName | {TRIGGER.NAME} |
|
||||
| AlertId | {EVENT.ID} |
|
||||
| HostName | {HOST.NAME} |
|
||||
| HostIp | {HOST.IP} |
|
||||
| TriggerDescription | {TRIGGER.DESCRIPTION} |
|
||||
| TriggerSeverity | {TRIGGER.SEVERITY} |
|
||||
| TriggerStatus | {EVENT.STATUS} |
|
||||
| ItemName | {ITEM.NAME} |
|
||||
| ItemValue | {ITEM.VALUE} |
|
||||
| ItemLastValue | {ITEM.LASTVALUE} |
|
||||
| EventDate | {EVENT.DATE} |
|
||||
| EventTime | {EVENT.TIME} |
|
||||
| EventTags | {EVENT.TAGS} |
|
||||
| EventRecoveryDate | {EVENT.RECOVERY.DATE} |
|
||||
| EventRecoveryTime | {EVENT.RECOVERY.TIME} |
|
||||
|
||||
5. Add the following JavaScript code in the **Script** section
|
||||
```javascript
|
||||
var Hertzbeat = {
|
||||
sendMessage: function(url, alert) {
|
||||
request = new HttpRequest();
|
||||
request.addHeader('Content-Type: application/json');
|
||||
data = JSON.stringify(alert);
|
||||
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] params: ' + data);
|
||||
// Push alert message
|
||||
response = request.post(url, data);
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] HTTP code: ' + request.Status());
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] response: ' + response);
|
||||
|
||||
// Format the returned result and make a judgment, throw an exception if there is an exception.
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (error) {
|
||||
response = null;
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] response parse error');
|
||||
}
|
||||
|
||||
if (request.Status() !== 200 || response.errcode !== 0 || response.errmsg !== 'ok') {
|
||||
if (typeof response.errmsg === 'string') {
|
||||
throw response.errmsg;
|
||||
}
|
||||
else {
|
||||
throw 'Unknown error. Check debug log for more information.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var params = JSON.parse(value);
|
||||
// Check if Webhook_url parameter is defined, throw an error if not defined.
|
||||
if (typeof params.URL === 'undefined') {
|
||||
throw 'Incorrect value is given for parameter "Webhook_url": parameter is missing';
|
||||
}
|
||||
|
||||
var currentTimestamp = Date.now();
|
||||
|
||||
// Convert Zabbix severity to HertzBeat priority
|
||||
function convertSeverity(severity) {
|
||||
var severityMap = {
|
||||
"信息": "info",
|
||||
"Information": "info",
|
||||
"警告": "warning",
|
||||
"Warning": "warning",
|
||||
"一般严重": "error",
|
||||
"Average": "error",
|
||||
"严重": "critical",
|
||||
"High": "critical",
|
||||
"灾难": "emergency",
|
||||
"Disaster": "emergency"
|
||||
};
|
||||
return severityMap[severity] || "error";
|
||||
}
|
||||
// Build fingerprint unique identifier
|
||||
var fingerprint = "zabbix-event:" + params.AlertId + "-" + params.HostName;
|
||||
|
||||
// Build labels
|
||||
var labels = {
|
||||
"alertname": params.AlertName,
|
||||
"source": "zabbix",
|
||||
"severity": convertSeverity(params.TriggerSeverity),
|
||||
"host": params.HostName,
|
||||
"hostip": params.HostIp,
|
||||
"itemname": params.ItemName
|
||||
};
|
||||
|
||||
// Parse event tags
|
||||
if (params.EventTags) {
|
||||
var tags = params.EventTags.split(',');
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
var tagParts = tags[i].split(':');
|
||||
if (tagParts.length == 2) {
|
||||
var key = tagParts[0].trim();
|
||||
var value = tagParts[1].trim();
|
||||
labels[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build annotations
|
||||
var annotations = {
|
||||
"summary": params.AlertName,
|
||||
"description": params.TriggerDescription,
|
||||
"value": params.ItemValue,
|
||||
"lastValue": params.ItemLastValue
|
||||
};
|
||||
|
||||
// Build alert content
|
||||
var content = "Host: " + params.HostName +
|
||||
"\nIP: " + params.HostIp +
|
||||
"\nAlert: " + params.AlertName +
|
||||
"\nLevel: " + params.TriggerSeverity +
|
||||
"\nDescription: " + params.TriggerDescription +
|
||||
"\nItem: " + params.ItemName +
|
||||
"\nCurrent Value: " + params.ItemValue +
|
||||
"\nLast Value: " + params.ItemLastValue +
|
||||
"\nTime: " + params.EventDate + " " + params.EventTime;
|
||||
|
||||
// Determine status
|
||||
var status = params.TriggerStatus === "RESOLVED" ? "resolved" : "firing";
|
||||
|
||||
// Calculate timestamp
|
||||
var startAt = currentTimestamp;
|
||||
var endAt = null;
|
||||
|
||||
// If it's a recovery event, calculate end time
|
||||
if (status === "resolved" && params.EventRecoveryDate && params.EventRecoveryTime) {
|
||||
endAt = currentTimestamp;
|
||||
}
|
||||
|
||||
// Build payload to send to HertzBeat
|
||||
var hertzbeatAlert = {
|
||||
"fingerprint": fingerprint,
|
||||
"labels": labels,
|
||||
"annotations": annotations,
|
||||
"content": content,
|
||||
"status": status,
|
||||
"triggerTimes": 1,
|
||||
"startAt": startAt,
|
||||
"activeAt": startAt,
|
||||
"endAt": endAt
|
||||
};
|
||||
|
||||
// Log
|
||||
Zabbix.Log(4, "HertzBeat webhook payload: " + JSON.stringify(hertzbeatAlert));
|
||||
|
||||
// Execute message push function
|
||||
Hertzbeat.sendMessage(params.URL, hertzbeatAlert);
|
||||
// Return to Zabbix, 'OK' will be used to identify successful execution in Zabbix actions.
|
||||
return 'OK';
|
||||
} catch (error) {
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] notification failed: ' + error);
|
||||
throw 'Sending failed: ' + error + '.';
|
||||
}
|
||||
```
|
||||
7. Click the **Add** button to save the media type
|
||||
|
||||
### Step 2: Configure Media for Users
|
||||
1. Go to **Administration** > **Users** > **Select the user who will receive alerts** (you can create a dedicated user for alerts)
|
||||
2. Select the **Media** tab > **Add**
|
||||
3. Select **HertzBeat Webhook** type
|
||||
4. Choose the time period and alert severity as needed, ensure the status is enabled
|
||||
5. Click the **Add** button to save the media
|
||||
|
||||
### Step 3: Configure Alert Actions
|
||||
1. Go to **Configuration** > **Actions** > **Trigger actions** > **Create action**
|
||||
2. Configure the Action tab information
|
||||
- Name: HertzBeat Webhook
|
||||
- Conditions: Configure trigger conditions as needed
|
||||
3. Configure Operations tab information
|
||||
- Operation step duration: Set as needed
|
||||
- In the **Operations** section, add and configure users or user groups, select the user configured with HertzBeat Webhook media, select **HertzBeat Webhook** as the media type, check custom message content, ensure all macros are correctly passed
|
||||
- **Recovery operations** and **Update operations** can be configured similarly
|
||||
4. Click the **Add** button to save the alert action
|
||||
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Alerts Not Received
|
||||
- Ensure the Webhook URL is accessible by the Zabbix server
|
||||
- Check server logs for request records
|
||||
|
||||
#### Alerts Not Triggered
|
||||
- Ensure alert policy conditions are correct and notifications are bound
|
||||
|
||||
For more information, refer to [Zabbix Webhook](https://www.zabbix.com/documentation/current/manual/config/notifications/webhook) and [Zabbix Macros](https://www.zabbix.com/documentation/current/en/manual/appendix/macros)
|
||||
```
|
||||
@@ -0,0 +1,201 @@
|
||||
>将 Zabbix 的告警通过 Webhook 方式发送到 HertzBeat 告警平台。
|
||||
|
||||
### 步骤一: 在 Zabbix 创建自定义 Webhook 媒介类型
|
||||
1. 登录 Zabbix Web 界面
|
||||
2. 进入 **告警** > **媒介类型** > **创建媒介类型**
|
||||
3. 配置基本信息
|
||||
- 名称: HertzBeat Webhook
|
||||
- 类型: Webhook
|
||||
4. 在 **参数** 部分添加以下字段
|
||||
|
||||
| 名称 | 值 |
|
||||
|-----|-----|
|
||||
| URL | http://your-hertzbeat-server:1157/api/report/zabbix |
|
||||
| AlertName | {TRIGGER.NAME} |
|
||||
| AlertId | {EVENT.ID} |
|
||||
| HostName | {HOST.NAME} |
|
||||
| HostIp | {HOST.IP} |
|
||||
| TriggerDescription | {TRIGGER.DESCRIPTION} |
|
||||
| TriggerSeverity | {TRIGGER.SEVERITY} |
|
||||
| TriggerStatus | {EVENT.STATUS} |
|
||||
| ItemName | {ITEM.NAME} |
|
||||
| ItemValue | {ITEM.VALUE} |
|
||||
| ItemLastValue | {ITEM.LASTVALUE} |
|
||||
| EventDate | {EVENT.DATE} |
|
||||
| EventTime | {EVENT.TIME} |
|
||||
| EventTags | {EVENT.TAGS} |
|
||||
| EventRecoveryDate | {EVENT.RECOVERY.DATE} |
|
||||
| EventRecoveryTime | {EVENT.RECOVERY.TIME} |
|
||||
|
||||
5. 在 **脚本** 部分添加以下 JavaScript 代码
|
||||
```javascript
|
||||
var Hertzbeat = {
|
||||
|
||||
sendMessage: function(url, alert) {
|
||||
request = new HttpRequest();
|
||||
request.addHeader('Content-Type: application/json');
|
||||
data = JSON.stringify(alert);
|
||||
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] params: ' + data);
|
||||
// 推送告警消息
|
||||
response = request.post(url, data);
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] HTTP code: ' + request.Status());
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] response: ' + response);
|
||||
|
||||
// 格式化返回的结果并做出判断,有异常则抛出异常。
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (error) {
|
||||
response = null;
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] response parse error');
|
||||
}
|
||||
|
||||
if (request.Status() !== 200 || response.errcode !== 0 || response.errmsg !== 'ok') {
|
||||
if (typeof response.errmsg === 'string') {
|
||||
throw response.errmsg;
|
||||
}
|
||||
else {
|
||||
throw 'Unknown error. Check debug log for more information.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var params = JSON.parse(value);
|
||||
// 判断Webhook_url参数是否定义,未定义抛出错误。
|
||||
if (typeof params.URL === 'undefined') {
|
||||
throw 'Incorrect value is given for parameter "Webhook_url": parameter is missing';
|
||||
}
|
||||
|
||||
var currentTimestamp = Date.now();
|
||||
|
||||
// 转换 zabbix 严重性到 HertzBeat 优先级
|
||||
function convertSeverity(severity) {
|
||||
var severityMap = {
|
||||
"信息": "info",
|
||||
"Information": "info",
|
||||
"警告": "warning",
|
||||
"Warning": "warning",
|
||||
"一般严重": "error",
|
||||
"Average": "error",
|
||||
"严重": "critical",
|
||||
"High": "critical",
|
||||
"灾难": "emergency",
|
||||
"Disaster": "emergency"
|
||||
};
|
||||
return severityMap[severity] || "error";
|
||||
}
|
||||
// 构建指纹唯一标识
|
||||
var fingerprint = "zabbix-event:" + params.AlertId + "-" + params.HostName;
|
||||
|
||||
// 构建 labels
|
||||
var labels = {
|
||||
"alertname": params.AlertName,
|
||||
"source": "zabbix",
|
||||
"severity": convertSeverity(params.TriggerSeverity),
|
||||
"host": params.HostName,
|
||||
"hostip": params.HostIp,
|
||||
"itemname": params.ItemName
|
||||
};
|
||||
|
||||
// 解析事件标签
|
||||
if (params.EventTags) {
|
||||
var tags = params.EventTags.split(',');
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
var tagParts = tags[i].split(':');
|
||||
if (tagParts.length == 2) {
|
||||
var key = tagParts[0].trim();
|
||||
var value = tagParts[1].trim();
|
||||
labels[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构建注解信息
|
||||
var annotations = {
|
||||
"summary": params.AlertName,
|
||||
"description": params.TriggerDescription,
|
||||
"value": params.ItemValue,
|
||||
"lastValue": params.ItemLastValue
|
||||
};
|
||||
|
||||
// 构建告警内容
|
||||
var content = "主机: " + params.HostName +
|
||||
"\nIP: " + params.HostIp +
|
||||
"\n告警: " + params.AlertName +
|
||||
"\n级别: " + params.TriggerSeverity +
|
||||
"\n描述: " + params.TriggerDescription +
|
||||
"\n监控项: " + params.ItemName +
|
||||
"\n当前值: " + params.ItemValue +
|
||||
"\n上次值: " + params.ItemLastValue +
|
||||
"\n时间: " + params.EventDate + " " + params.EventTime;
|
||||
|
||||
// 确定状态
|
||||
var status = params.TriggerStatus === "RESOLVED" ? "resolved" : "firing";
|
||||
|
||||
// 计算时间戳
|
||||
var startAt = currentTimestamp;
|
||||
var endAt = null;
|
||||
|
||||
// 如果是恢复事件,计算结束时间
|
||||
if (status === "resolved" && params.EventRecoveryDate && params.EventRecoveryTime) {
|
||||
endAt = currentTimestamp;
|
||||
}
|
||||
|
||||
// 构建发送到 HertzBeat 的 payload
|
||||
var hertzbeatAlert = {
|
||||
"fingerprint": fingerprint,
|
||||
"labels": labels,
|
||||
"annotations": annotations,
|
||||
"content": content,
|
||||
"status": status,
|
||||
"triggerTimes": 1,
|
||||
"startAt": startAt,
|
||||
"activeAt": startAt,
|
||||
"endAt": endAt
|
||||
};
|
||||
|
||||
// 记录日志
|
||||
Zabbix.Log(4, "HertzBeat webhook payload: " + JSON.stringify(hertzbeatAlert));
|
||||
|
||||
// 执行消息推送函数
|
||||
Hertzbeat.sendMessage(params.URL, hertzbeatAlert);
|
||||
// 返回给zabbix,ok 在 Zabbix 动作中会被用来标识成功执行。
|
||||
return 'OK';
|
||||
} catch (error) {
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] notification failed: ' + error);
|
||||
throw 'Sending failed: ' + error + '.';
|
||||
}
|
||||
```
|
||||
7. 点击 **添加** 按钮保存媒介类型
|
||||
|
||||
### 步骤二: 为用户配置媒介
|
||||
1. 进入 **用户** > **用户** > **选择要接收告警的用户** (可以创建一个专门用于告警的用户)
|
||||
2. 选择 **报警媒介** 选项卡 > **添加**
|
||||
3. 选择 **HertzBeat Webhook** 类型
|
||||
4. 启用时间段按需选择,报警严重性按需选择,确保状态为启用
|
||||
5. 点击 **添加** 按钮保存媒介
|
||||
|
||||
### 步骤三: 配置告警动作
|
||||
1. 进入 **告警** > **动作** > **触发器动作** > **创建动作**
|
||||
2. 配置动作选项卡信息
|
||||
- 名称: HertzBeat Webhook
|
||||
- 条件: 根据需要配置触发条件
|
||||
3. 配置操作选项卡信息
|
||||
- 操作步骤持续时间: 根据需要进行设置
|
||||
- 在**操作**部分添加,配置用户或用户组,选择之前配置 HertzBeat Webhook 媒介的用户,发送至媒体类型选择 **HertzBeat Webhook**,选中自定义消息内容,确保所有宏都被正确传递
|
||||
- **恢复操作**以及**更新操作**可以根据上述进行类似配置
|
||||
4. 点击 **添加** 按钮保存告警动作
|
||||
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 未收到告警
|
||||
- 确保 Webhook URL 可以被 zabbix 服务访问
|
||||
- 检查服务器日志是否有请求记录
|
||||
|
||||
#### 告警未触发
|
||||
- 确保告警策略的条件正确,并已绑定通知
|
||||
|
||||
更多信息请参考 [Zabbix Webhook](https://www.zabbix.com/documentation/current/manual/config/notifications/webhook) 以及 [Zabbix 宏](https://www.zabbix.com/documentation/current/zh/manual/appendix/macros)
|
||||
@@ -0,0 +1,201 @@
|
||||
將 Zabbix 的告警通過 Webhook 方式發送到 HertzBeat 告警平臺。
|
||||
|
||||
### 步驟一: 在 Zabbix 創建自定義 Webhook 媒介類型
|
||||
1. 登錄 Zabbix Web 界面
|
||||
2. 進入 **告警** > **媒介類型** > **創建媒介類型**
|
||||
3. 配置基本信息
|
||||
- 名稱: HertzBeat Webhook
|
||||
- 類型: Webhook
|
||||
4. 在 **參數** 部分添加以下字段
|
||||
|
||||
| 名稱 | 值 |
|
||||
|-----|-----|
|
||||
| URL | http://your-hertzbeat-server:1157/api/report/zabbix |
|
||||
| AlertName | {TRIGGER.NAME} |
|
||||
| AlertId | {EVENT.ID} |
|
||||
| HostName | {HOST.NAME} |
|
||||
| HostIp | {HOST.IP} |
|
||||
| TriggerDescription | {TRIGGER.DESCRIPTION} |
|
||||
| TriggerSeverity | {TRIGGER.SEVERITY} |
|
||||
| TriggerStatus | {EVENT.STATUS} |
|
||||
| ItemName | {ITEM.NAME} |
|
||||
| ItemValue | {ITEM.VALUE} |
|
||||
| ItemLastValue | {ITEM.LASTVALUE} |
|
||||
| EventDate | {EVENT.DATE} |
|
||||
| EventTime | {EVENT.TIME} |
|
||||
| EventTags | {EVENT.TAGS} |
|
||||
| EventRecoveryDate | {EVENT.RECOVERY.DATE} |
|
||||
| EventRecoveryTime | {EVENT.RECOVERY.TIME} |
|
||||
|
||||
5. 在 **腳本** 部分添加以下 JavaScript 代碼
|
||||
```javascript
|
||||
var Hertzbeat = {
|
||||
sendMessage: function(url, alert) {
|
||||
request = new HttpRequest();
|
||||
request.addHeader('Content-Type: application/json');
|
||||
data = JSON.stringify(alert);
|
||||
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] params: ' + data);
|
||||
// 推送告警消息
|
||||
response = request.post(url, data);
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] HTTP code: ' + request.Status());
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] response: ' + response);
|
||||
|
||||
// 格式化返回的結果並做出判斷,有異常則拋出異常。
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (error) {
|
||||
response = null;
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] response parse error');
|
||||
}
|
||||
|
||||
if (request.Status() !== 200 || response.errcode !== 0 || response.errmsg !== 'ok') {
|
||||
if (typeof response.errmsg === 'string') {
|
||||
throw response.errmsg;
|
||||
}
|
||||
else {
|
||||
throw 'Unknown error. Check debug log for more information.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var params = JSON.parse(value);
|
||||
// 判斷Webhook_url參數是否定義,未定義拋出錯誤。
|
||||
if (typeof params.URL === 'undefined') {
|
||||
throw 'Incorrect value is given for parameter "Webhook_url": parameter is missing';
|
||||
}
|
||||
|
||||
var currentTimestamp = Date.now();
|
||||
|
||||
// 轉換 zabbix 嚴重性到 HertzBeat 優先級
|
||||
function convertSeverity(severity) {
|
||||
var severityMap = {
|
||||
"信息": "info",
|
||||
"Information": "info",
|
||||
"警告": "warning",
|
||||
"Warning": "warning",
|
||||
"一般嚴重": "error",
|
||||
"Average": "error",
|
||||
"嚴重": "critical",
|
||||
"High": "critical",
|
||||
"災難": "emergency",
|
||||
"Disaster": "emergency"
|
||||
};
|
||||
return severityMap[severity] || "error";
|
||||
}
|
||||
// 構建指紋唯一標識
|
||||
var fingerprint = "zabbix-event:" + params.AlertId + "-" + params.HostName;
|
||||
|
||||
// 構建 labels
|
||||
var labels = {
|
||||
"alertname": params.AlertName,
|
||||
"source": "zabbix",
|
||||
"severity": convertSeverity(params.TriggerSeverity),
|
||||
"host": params.HostName,
|
||||
"hostip": params.HostIp,
|
||||
"itemname": params.ItemName
|
||||
};
|
||||
|
||||
// 解析事件標籤
|
||||
if (params.EventTags) {
|
||||
var tags = params.EventTags.split(',');
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
var tagParts = tags[i].split(':');
|
||||
if (tagParts.length == 2) {
|
||||
var key = tagParts[0].trim();
|
||||
var value = tagParts[1].trim();
|
||||
labels[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 構建註解信息
|
||||
var annotations = {
|
||||
"summary": params.AlertName,
|
||||
"description": params.TriggerDescription,
|
||||
"value": params.ItemValue,
|
||||
"lastValue": params.ItemLastValue
|
||||
};
|
||||
|
||||
// 構建告警內容
|
||||
var content = "主機: " + params.HostName +
|
||||
"\nIP: " + params.HostIp +
|
||||
"\n告警: " + params.AlertName +
|
||||
"\n級別: " + params.TriggerSeverity +
|
||||
"\n描述: " + params.TriggerDescription +
|
||||
"\n監控項: " + params.ItemName +
|
||||
"\n當前值: " + params.ItemValue +
|
||||
"\n上次值: " + params.ItemLastValue +
|
||||
"\n時間: " + params.EventDate + " " + params.EventTime;
|
||||
|
||||
// 確定狀態
|
||||
var status = params.TriggerStatus === "RESOLVED" ? "resolved" : "firing";
|
||||
|
||||
// 計算時間戳
|
||||
var startAt = currentTimestamp;
|
||||
var endAt = null;
|
||||
|
||||
// 如果是恢復事件,計算結束時間
|
||||
if (status === "resolved" && params.EventRecoveryDate && params.EventRecoveryTime) {
|
||||
endAt = currentTimestamp;
|
||||
}
|
||||
|
||||
// 構建發送到 HertzBeat 的 payload
|
||||
var hertzbeatAlert = {
|
||||
"fingerprint": fingerprint,
|
||||
"labels": labels,
|
||||
"annotations": annotations,
|
||||
"content": content,
|
||||
"status": status,
|
||||
"triggerTimes": 1,
|
||||
"startAt": startAt,
|
||||
"activeAt": startAt,
|
||||
"endAt": endAt
|
||||
};
|
||||
|
||||
// 記錄日誌
|
||||
Zabbix.Log(4, "HertzBeat webhook payload: " + JSON.stringify(hertzbeatAlert));
|
||||
|
||||
// 執行消息推送函數
|
||||
Hertzbeat.sendMessage(params.URL, hertzbeatAlert);
|
||||
// 返回給zabbix,ok 在 Zabbix 動作中會被用來標識成功執行。
|
||||
return 'OK';
|
||||
} catch (error) {
|
||||
Zabbix.Log(4, '[Hertzbeat Webhook] notification failed: ' + error);
|
||||
throw 'Sending failed: ' + error + '.';
|
||||
}
|
||||
```
|
||||
7. 點擊 **添加** 按鈕保存媒介類型
|
||||
|
||||
### 步驟二: 為用戶配置媒介
|
||||
1. 進入 **用戶** > **用戶** > **選擇要接收告警的用戶** (可以創建一個專門用於告警的用戶)
|
||||
2. 選擇 **報警媒介** 選項卡 > **添加**
|
||||
3. 選擇 **HertzBeat Webhook** 類型
|
||||
4. 啟用時間段按需選擇,報警嚴重性按需選擇,確保狀態為啟用
|
||||
5. 點擊 **添加** 按鈕保存媒介
|
||||
|
||||
### 步驟三: 配置告警動作
|
||||
1. 進入 **告警** > **動作** > **觸發器動作** > **創建動作**
|
||||
2. 配置動作選項卡信息
|
||||
- 名稱: HertzBeat Webhook
|
||||
- 條件: 根據需要配置觸發條件
|
||||
3. 配置操作選項卡信息
|
||||
- 操作步驟持續時間: 根據需要進行設置
|
||||
- 在**操作**部分添加,配置用戶或用戶組,選擇之前配置 HertzBeat Webhook 媒介的用戶,發送至媒體類型選擇 **HertzBeat Webhook**,選中自定義消息內容,確保所有宏都被正確傳遞
|
||||
- **恢復操作**以及**更新操作**可以根據上述進行類似配置
|
||||
4. 點擊 **添加** 按鈕保存告警動作
|
||||
|
||||
|
||||
### 常見問題
|
||||
|
||||
#### 未收到告警
|
||||
- 確保 Webhook URL 可以被 zabbix 服務訪問
|
||||
- 檢查服務器日誌是否有請求記錄
|
||||
|
||||
#### 告警未觸發
|
||||
- 確保告警策略的條件正確,並已綁定通知
|
||||
|
||||
更多信息請參考 [Zabbix Webhook](https://www.zabbix.com/documentation/current/manual/config/notifications/webhook) 以及 [Zabbix 宏](https://www.zabbix.com/documentation/current/zh/manual/appendix/macros)
|
||||
```
|
||||
@@ -99,6 +99,7 @@
|
||||
"alert.integration.source.webhook": "Default Webhook",
|
||||
"alert.integration.source.skywalking": "SkyWalking",
|
||||
"alert.integration.source.uptime-kuma": "Uptime Kuma",
|
||||
"alert.integration.source.zabbix": "Zabbix",
|
||||
"alert.integration.token.desc": "Token you generated that can be used to access the HertzBeat API.",
|
||||
"alert.integration.token.new": "Click to Generate Token",
|
||||
"alert.integration.token.notice": "Token only be displayed once. Please keep your token secure. Do not share it with others.",
|
||||
@@ -612,7 +613,7 @@
|
||||
"menu.monitor.service": "Service Monitor",
|
||||
"menu.monitor.webserver": "Webserver Monitor",
|
||||
"menu.more": "More",
|
||||
"menu.search.placeholder": "Search Monitoring Task Name、Host etc",
|
||||
"menu.search.placeholder": "Search Task Name、Host etc",
|
||||
"monitor": "Monitor",
|
||||
"monitor.advanced": "Advanced",
|
||||
"monitor.advanced.tip": "Advanced Setting Params",
|
||||
@@ -709,7 +710,7 @@
|
||||
"monitor.headerValue.tip": "Header Value",
|
||||
"monitor.holdingRegisterAddresses.tip": "Input RegisterAddress",
|
||||
"monitor.host": "Target Host",
|
||||
"monitor.host.tip": "The monitored peer ip or domain name",
|
||||
"monitor.host.tip": "The target peer ip or domain name",
|
||||
"monitor.icon.bigdata": "dot-chart",
|
||||
"monitor.icon.cache": "group",
|
||||
"monitor.icon.center": "laptop",
|
||||
@@ -728,7 +729,7 @@
|
||||
"monitor.keyword.tip": "Enter keyword which occurrences need to be monitored",
|
||||
"monitor.list": "Monitor List",
|
||||
"monitor.name": "Task Name",
|
||||
"monitor.name.tip": "Monitoring task name",
|
||||
"monitor.name.tip": "The unique name that identifies the task",
|
||||
"monitor.new": "New",
|
||||
"monitor.new-monitor": "New Monitor",
|
||||
"monitor.new.failed": "New Monitor Failed",
|
||||
@@ -759,6 +760,8 @@
|
||||
"monitor.sshPort.tip": "Required When Enabling SSH Tunnel",
|
||||
"monitor.sshUsername.tip": "Required When Enabling SSH Tunnel",
|
||||
"monitor.sshPrivateKey.tip": "BEGIN RSA PRIVATE KEY",
|
||||
"monitor.scrape.type.static": "Static Scrape",
|
||||
"monitor.scrape.type.http_sd": "Http Service Discovery",
|
||||
"placeholder.key": "Key",
|
||||
"placeholder.value": "Value",
|
||||
"plugin.delete": "Delete Plugin",
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
"alert.integration.source.webhook": "デフォルトWebhook",
|
||||
"alert.integration.source.skywalking": "SkyWalking",
|
||||
"alert.integration.source.uptime-kuma": "Uptime Kuma",
|
||||
"alert.integration.source.zabbix": "Zabbix",
|
||||
"alert.integration.token.desc": "HertzBeat APIにアクセスするために生成したトークン。",
|
||||
"alert.integration.token.new": "トークンを生成するにはクリック",
|
||||
"alert.integration.token.notice": "トークンは一度だけ表示されます。トークンを安全に保管し、他人と共有しないでください。",
|
||||
@@ -759,6 +760,8 @@
|
||||
"monitor.sshPort.tip": "SSHトンネルオープン時に必要",
|
||||
"monitor.sshUsername.tip": "SSHトンネルオープン時に必要",
|
||||
"monitor.sshPrivateKey.tip": "RSA秘密鍵の起動",
|
||||
"monitor.scrape.type.static": "静的",
|
||||
"monitor.scrape.type.http_sd": "Http Service Discovery",
|
||||
"placeholder.key": "キー",
|
||||
"placeholder.value": "値",
|
||||
"plugin.delete": "プラグインを削除",
|
||||
|
||||
@@ -571,6 +571,8 @@
|
||||
"monitor.sshPort.tip": "Obrigatório quando túnel SSH está ativo",
|
||||
"monitor.sshUsername.tip": "Obrigatório quando túnel SSH está ativo",
|
||||
"monitor.sshPrivateKey.tip": "Chave privada RSA",
|
||||
"monitor.scrape.type.static": "Estatico",
|
||||
"monitor.scrape.type.http_sd": "Http Service Discovery",
|
||||
"common.name": "Nome da Métrica",
|
||||
"common.value": "Valor da Métrica",
|
||||
"common.search": "Pesquisar",
|
||||
@@ -1124,6 +1126,7 @@
|
||||
"alert.export.use-type": "Exportar regras no formato de arquivo {{type}}",
|
||||
"alert.integration.source.skywalking": "SkyWalking",
|
||||
"alert.integration.source.uptime-kuma": "Uptime Kuma",
|
||||
"alert.integration.source.zabbix": "Zabbix",
|
||||
"dashboard.alerts.title": "Lista de Alarmes Recentes",
|
||||
"dashboard.alerts.title-no": "Alarmes Pendentes Recentes",
|
||||
"dashboard.alerts.no": "Nenhum Alarme Pendente",
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
"alert.integration.source.webhook": "默认Webhook",
|
||||
"alert.integration.source.skywalking": "SkyWalking",
|
||||
"alert.integration.source.uptime-kuma": "Uptime Kuma",
|
||||
"alert.integration.source.zabbix": "Zabbix",
|
||||
"alert.integration.token.desc": "生成的 Token 可用于访问 HertzBeat API",
|
||||
"alert.integration.token.new": "点击生成 Token",
|
||||
"alert.integration.token.notice": "此内容只会展示一次,请妥善保管您的 Token,不要泄露给他人",
|
||||
@@ -759,6 +760,8 @@
|
||||
"monitor.sshPort.tip": "SSH隧道开启时必填",
|
||||
"monitor.sshUsername.tip": "SSH隧道开启时必填",
|
||||
"monitor.sshPrivateKey.tip": "启动RSA私钥",
|
||||
"monitor.scrape.type.static": "静态配置",
|
||||
"monitor.scrape.type.http_sd": "Http 服务发现",
|
||||
"placeholder.key": "键",
|
||||
"placeholder.value": "值",
|
||||
"plugin.delete": "刪除插件",
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
"alert.integration.source.webhook": "默认Webhook",
|
||||
"alert.integration.source.skywalking": "SkyWalking",
|
||||
"alert.integration.source.uptime-kuma": "Uptime Kuma",
|
||||
"alert.integration.source.zabbix": "Zabbix",
|
||||
"alert.integration.token.desc": "生成的 Token 可用于访问 HertzBeat API",
|
||||
"alert.integration.token.new": "点击生成 Token",
|
||||
"alert.integration.token.notice": "此内容只会展示一次,请妥善保管您的 Token,不要泄露给他人",
|
||||
@@ -758,6 +759,8 @@
|
||||
"monitor.sshPort.tip": "SSH隧道開啓時必填",
|
||||
"monitor.sshUsername.tip": "SSH隧道開啓時必填",
|
||||
"monitor.sshPrivateKey.tip": "啟動RSA私鑰",
|
||||
"monitor.scrape.type.static": "靜態配置",
|
||||
"monitor.scrape.type.http_sd": "Http 服務發現",
|
||||
"placeholder.key": "鍵",
|
||||
"placeholder.value": "值",
|
||||
"plugin.delete": "刪除插件",
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="600px"
|
||||
viewBox="0 0 168 44" style="enable-background:new 0 0 168 44;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{clip-path:url(#SVGID_2_);fill:#D40000;}
|
||||
.st1{clip-path:url(#SVGID_2_);fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<defs>
|
||||
<rect id="SVGID_1_" y="0" width="168" height="44"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_2_">
|
||||
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<rect x="0" y="0" class="st0" width="168" height="44"/>
|
||||
<polygon class="st1" points="7.2,6.7 31.5,6.7 31.5,9.9 11.9,33.8 32,33.8 32,37.3 6.7,37.3 6.7,34.1 26.3,10.2 7.2,10.2 "/>
|
||||
<path class="st1" d="M48.4,10.8L42.7,26h11.4L48.4,10.8z M46,6.7h4.7l11.8,30.6h-4.3l-2.8-7.8H41.4l-2.8,7.8h-4.4L46,6.7z"/>
|
||||
<path class="st1" d="M71.2,22.7v11.2h6.7c2.3,0,3.9-0.5,5-1.4c1.1-0.9,1.6-2.3,1.6-4.2c0-1.9-0.5-3.3-1.6-4.2
|
||||
c-1.1-0.9-2.8-1.4-5-1.4H71.2z M71.2,10.1v9.2h6.2c2,0,3.6-0.4,4.6-1.1c1-0.8,1.5-1.9,1.5-3.5c0-1.5-0.5-2.7-1.5-3.5
|
||||
c-1-0.8-2.5-1.1-4.6-1.1H71.2z M67,6.7h10.7c3.2,0,5.6,0.7,7.4,2c1.7,1.3,2.6,3.2,2.6,5.6c0,1.9-0.4,3.4-1.3,4.5
|
||||
c-0.9,1.1-2.2,1.8-3.9,2.1c2.1,0.4,3.7,1.3,4.8,2.7c1.1,1.4,1.7,3.1,1.7,5.2c0,2.7-0.9,4.8-2.8,6.3c-1.9,1.5-4.5,2.2-8,2.2H67V6.7
|
||||
z"/>
|
||||
<path class="st1" d="M99.9,22.7v11.2h6.7c2.2,0,3.9-0.5,5-1.4c1.1-0.9,1.6-2.3,1.6-4.2c0-1.9-0.5-3.3-1.6-4.2
|
||||
c-1.1-0.9-2.8-1.4-5-1.4H99.9z M99.9,10.1v9.2h6.2c2,0,3.6-0.4,4.6-1.1c1-0.8,1.5-1.9,1.5-3.5c0-1.5-0.5-2.7-1.5-3.5
|
||||
c-1-0.8-2.5-1.1-4.6-1.1H99.9z M95.7,6.7h10.7c3.2,0,5.6,0.7,7.4,2c1.7,1.3,2.6,3.2,2.6,5.6c0,1.9-0.4,3.4-1.3,4.5
|
||||
c-0.9,1.1-2.2,1.8-3.9,2.1c2.1,0.4,3.7,1.3,4.8,2.7c1.1,1.4,1.7,3.1,1.7,5.2c0,2.7-0.9,4.8-2.8,6.3c-1.9,1.5-4.5,2.2-8,2.2H95.7
|
||||
V6.7z"/>
|
||||
<polygon class="st1" points="136.4,6.7 141,6.7 148.3,17.7 155.7,6.7 160.1,6.7 150.5,21 161.3,37.3 156.7,37.3 148.2,24.5
|
||||
139.7,37.3 135.2,37.3 146,21.1 "/>
|
||||
<rect x="124.3" y="6.7" class="st1" width="4.2" height="30.6"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
Reference in New Issue
Block a user