This is an automated email from the ASF dual-hosted git repository.

zhaoqingran pushed a commit to branch self-monitor
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git

commit 5850edb6718278266356c07bc9f85936ab1281d9
Author: Logic <zqr10...@dromara.org>
AuthorDate: Mon Jul 14 00:32:52 2025 +0800

    feat(otel): add Prometheus metrics support
    
    - Implement MetricsService for collecting and exporting internal metrics
    - Add PrometheusCollector and Meter beans in OpenTelemetryConfig
    - Create PrometheusMetricsController to expose metrics via /metrics endpoint
    - Update pom.xml to include Prometheus exporter dependency
---
 hertzbeat-otel/pom.xml                             |  9 +++
 .../hertzbeat/otel/config/OpenTelemetryConfig.java | 41 ++++++++++
 .../controller/PrometheusMetricsController.java    | 58 ++++++++++++++
 .../hertzbeat/otel/service/MetricsService.java     | 90 ++++++++++++++++++++++
 4 files changed, 198 insertions(+)

diff --git a/hertzbeat-otel/pom.xml b/hertzbeat-otel/pom.xml
index 43d1004bf2..bcf36c4bd4 100644
--- a/hertzbeat-otel/pom.xml
+++ b/hertzbeat-otel/pom.xml
@@ -32,6 +32,10 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
         <!-- hertzbeat common -->
         <dependency>
             <groupId>org.apache.hertzbeat</groupId>
@@ -51,6 +55,11 @@
             <groupId>io.opentelemetry.instrumentation</groupId>
             <artifactId>opentelemetry-spring-boot-starter</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.opentelemetry</groupId>
+            <artifactId>opentelemetry-exporter-prometheus</artifactId>
+            <version>1.52.0-alpha</version>
+        </dependency>
 
     </dependencies>
 </project>
diff --git 
a/hertzbeat-otel/src/main/java/org/apache/hertzbeat/otel/config/OpenTelemetryConfig.java
 
b/hertzbeat-otel/src/main/java/org/apache/hertzbeat/otel/config/OpenTelemetryConfig.java
index 44774dbd3a..6ec278f5f2 100644
--- 
a/hertzbeat-otel/src/main/java/org/apache/hertzbeat/otel/config/OpenTelemetryConfig.java
+++ 
b/hertzbeat-otel/src/main/java/org/apache/hertzbeat/otel/config/OpenTelemetryConfig.java
@@ -21,11 +21,13 @@ 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.api.metrics.Meter;
 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.metrics.SdkMeterProvider;
 import io.opentelemetry.sdk.resources.Resource;
 
 import java.util.Base64;
@@ -159,4 +161,43 @@ public class OpenTelemetryConfig {
                     return 
sdkLoggerProviderBuilder.addLogRecordProcessor(batchLogProcessor);
                 });
     }
+
+    /**
+     * Configures the SdkMeterProvider for Prometheus scraping.
+     * This bean is conditionally created if the 
otel.exporter.prometheus.enabled property is true.
+     *
+     * @return SdkMeterProvider
+     */
+    @Bean
+    @ConditionalOnProperty(name = "otel.exporter.prometheus.enabled", 
havingValue = "true")
+    public SdkMeterProvider sdkMeterProvider() {
+        return SdkMeterProvider.builder()
+                
.setResource(Resource.create(io.opentelemetry.api.common.Attributes.of(SERVICE_NAME,
 HERTZBEAT_SERVICE_NAME)))
+                .build();
+    }
+
+    /**
+     * Creates a PrometheusCollector bean that registers with the 
SdkMeterProvider.
+     * This collector is then used by the PrometheusMetricsController to 
scrape metrics.
+     *
+     * @param sdkMeterProvider the SdkMeterProvider
+     * @return a PrometheusCollector
+     */
+    @Bean
+    @ConditionalOnProperty(name = "otel.exporter.prometheus.enabled", 
havingValue = "true")
+    public PrometheusCollector prometheusCollector(SdkMeterProvider 
sdkMeterProvider) {
+        return 
PrometheusCollector.builder().setSdkMeterProvider(sdkMeterProvider).build();
+    }
+
+    /**
+     * Provides a Meter bean for dependency injection across the application 
(e.g., in MetricsService).
+     *
+     * @param sdkMeterProvider The SdkMeterProvider bean.
+     * @return Meter
+     */
+    @Bean
+    @ConditionalOnProperty(name = "otel.exporter.prometheus.enabled", 
havingValue = "true")
+    public Meter meter(SdkMeterProvider sdkMeterProvider) {
+        return sdkMeterProvider.get(HERTZBEAT_SERVICE_NAME);
+    }
 }
\ No newline at end of file
diff --git 
a/hertzbeat-otel/src/main/java/org/apache/hertzbeat/otel/controller/PrometheusMetricsController.java
 
b/hertzbeat-otel/src/main/java/org/apache/hertzbeat/otel/controller/PrometheusMetricsController.java
new file mode 100644
index 0000000000..0c751b8601
--- /dev/null
+++ 
b/hertzbeat-otel/src/main/java/org/apache/hertzbeat/otel/controller/PrometheusMetricsController.java
@@ -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.controller;
+
+import io.opentelemetry.exporter.prometheus.PrometheusCollector;
+import io.prometheus.client.exporter.common.TextFormat;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * A Spring MVC controller to expose Prometheus metrics.
+ * This controller is enabled when `otel.exporter.prometheus.enabled` is set 
to `true`.
+ * It provides a `/metrics` endpoint that scrapes the OpenTelemetry metrics
+ * and returns them in the Prometheus text format.
+ */
+@RestController
+@ConditionalOnProperty(name = "otel.exporter.prometheus.enabled", havingValue 
= "true")
+public class PrometheusMetricsController {
+
+    private final PrometheusCollector prometheusCollector;
+
+    public PrometheusMetricsController(PrometheusCollector 
prometheusCollector) {
+        this.prometheusCollector = prometheusCollector;
+    }
+
+    /**
+     * Handles GET requests to the /metrics endpoint.
+     *
+     * @param response the HttpServletResponse to write the metrics to
+     * @throws IOException if an I/O error occurs
+     */
+    @GetMapping(value = "/metrics", produces = TextFormat.CONTENT_TYPE_004)
+    public void metrics(HttpServletResponse response) throws IOException {
+        try (Writer writer = response.getWriter()) {
+            TextFormat.write004(writer, prometheusCollector.collect());
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/hertzbeat-otel/src/main/java/org/apache/hertzbeat/otel/service/MetricsService.java
 
b/hertzbeat-otel/src/main/java/org/apache/hertzbeat/otel/service/MetricsService.java
new file mode 100644
index 0000000000..7becbcc5b8
--- /dev/null
+++ 
b/hertzbeat-otel/src/main/java/org/apache/hertzbeat/otel/service/MetricsService.java
@@ -0,0 +1,90 @@
+/*
+ * 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.service;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.DoubleHistogram;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.Meter;
+import org.apache.hertzbeat.common.entity.manager.Monitor;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Service for collecting and exporting HertzBeat's internal metrics using 
OpenTelemetry.
+ */
+@Service
+public class MetricsService {
+
+    private static final String STATUS_SUCCESS = "success";
+    private static final String STATUS_FAIL = "fail";
+    private static final String LABEL_STATUS = "status";
+    private static final String LABEL_MONITOR_TYPE = "monitor_type";
+    private static final String LABEL_MONITOR_ID = "monitor_id";
+    private static final String LABEL_MONITOR_NAME = "monitor_name";
+    private static final String LABEL_MONITOR_TARGET = "monitor_target";
+
+    private final LongCounter collectTotalCounter;
+    private final DoubleHistogram collectDurationHistogram;
+
+    public MetricsService(Meter meter) {
+        this.collectTotalCounter = 
meter.counterBuilder("hertzbeat_collect_total")
+                .setDescription("The total number of collection tasks 
executed.")
+                .build();
+
+        this.collectDurationHistogram = 
meter.histogramBuilder("hertzbeat_collect_duration_seconds")
+                .setDescription("The duration of collection task executions, 
in seconds.")
+                .setUnit("s")
+                .build();
+    }
+
+    /**
+     * Records a successful collection task.
+     *
+     * @param monitor   The monitor instance.
+     * @param duration  The duration of the collection in milliseconds.
+     */
+    public void recordCollect(Monitor monitor, long duration) {
+        Attributes attributes = buildAttributes(monitor, STATUS_SUCCESS);
+        collectTotalCounter.add(1, attributes);
+        
collectDurationHistogram.record(TimeUnit.MILLISECONDS.toSeconds(duration), 
attributes);
+    }
+
+    /**
+     * Records a failed collection task.
+     *
+     * @param monitor   The monitor instance.
+     * @param duration  The duration of the collection in milliseconds.
+     */
+    public void recordCollect(Monitor monitor, long duration, Throwable 
throwable) {
+        Attributes attributes = buildAttributes(monitor, STATUS_FAIL);
+        collectTotalCounter.add(1, attributes);
+        
collectDurationHistogram.record(TimeUnit.MILLISECONDS.toSeconds(duration), 
attributes);
+    }
+
+    private Attributes buildAttributes(Monitor monitor, String status) {
+        return Attributes.builder()
+                .put(LABEL_STATUS, status)
+                .put(LABEL_MONITOR_ID, String.valueOf(monitor.getId()))
+                .put(LABEL_MONITOR_NAME, monitor.getName())
+                .put(LABEL_MONITOR_TYPE, monitor.getApp())
+                .put(LABEL_MONITOR_TARGET, monitor.getHost())
+                .build();
+    }
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscr...@hertzbeat.apache.org
For additional commands, e-mail: notifications-h...@hertzbeat.apache.org

Reply via email to