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

luoyuxia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss.git


The following commit(s) were added to refs/heads/main by this push:
     new c108476ab [metric] Support influxdb reporter (#2839)
c108476ab is described below

commit c108476ab4d9f883da84b02ca2f3ac29ba4644e9
Author: Tom <[email protected]>
AuthorDate: Fri May 15 11:29:02 2026 +0800

    [metric] Support influxdb reporter (#2839)
---
 .../org/apache/fluss/config/ConfigOptions.java     |  44 ++++++
 fluss-dist/pom.xml                                 |   7 +
 fluss-dist/src/main/assemblies/plugins.xml         |   7 +
 fluss-metrics/fluss-metrics-influxdb/pom.xml       | 154 ++++++++++++++++++++
 .../metrics/influxdb/InfluxdbPointProducer.java    | 106 ++++++++++++++
 .../fluss/metrics/influxdb/InfluxdbReporter.java   | 162 +++++++++++++++++++++
 .../metrics/influxdb/InfluxdbReporterPlugin.java   |  81 +++++++++++
 .../src/main/resources/META-INF/NOTICE             |  56 +++++++
 ...che.fluss.metrics.reporter.MetricReporterPlugin |  20 +++
 .../influxdb/InfluxdbPointProducerTest.java        | 157 ++++++++++++++++++++
 .../influxdb/InfluxdbReporterPluginTest.java       |  58 ++++++++
 .../metrics/influxdb/InfluxdbReporterTest.java     | 135 +++++++++++++++++
 .../org.junit.jupiter.api.extension.Extension      |  19 +++
 .../src/test/resources/log4j2-test.properties      |  28 ++++
 fluss-metrics/pom.xml                              |   1 +
 .../maintenance/observability/metric-reporters.md  |  37 +++++
 16 files changed, 1072 insertions(+)

diff --git 
a/fluss-common/src/main/java/org/apache/fluss/config/ConfigOptions.java 
b/fluss-common/src/main/java/org/apache/fluss/config/ConfigOptions.java
index 9f4a1145e..1818e16ef 100644
--- a/fluss-common/src/main/java/org/apache/fluss/config/ConfigOptions.java
+++ b/fluss-common/src/main/java/org/apache/fluss/config/ConfigOptions.java
@@ -2086,6 +2086,50 @@ public class ConfigOptions {
                                     + "the CoordinatorServer) it is advisable 
to use a port range "
                                     + "like 9990-9999.");
 
+    // ------------------------------------------------------------------------
+    //  ConfigOptions for influxdb reporter
+    // ------------------------------------------------------------------------
+    public static final ConfigOption<String> METRICS_REPORTER_INFLUXDB_VERSION 
=
+            key("metrics.reporter.influxdb.version")
+                    .stringType()
+                    .defaultValue("v3")
+                    .withDescription(
+                            "The InfluxDB version to connect to. Supported 
values are 'v2' and 'v3'. "
+                                    + "For InfluxDB v2, the 'org' 
configuration is required; for InfluxDB v3, it is not needed.");
+
+    public static final ConfigOption<String> 
METRICS_REPORTER_INFLUXDB_HOST_URL =
+            key("metrics.reporter.influxdb.host-url")
+                    .stringType()
+                    .noDefaultValue()
+                    .withDescription(
+                            "The InfluxDB server host URL including scheme, 
host name, and port.");
+
+    public static final ConfigOption<String> METRICS_REPORTER_INFLUXDB_BUCKET =
+            key("metrics.reporter.influxdb.bucket")
+                    .stringType()
+                    .noDefaultValue()
+                    .withFallbackKeys("metrics.reporter.influxdb.database")
+                    .withDescription("The InfluxDB bucket/database name.");
+
+    public static final ConfigOption<String> METRICS_REPORTER_INFLUXDB_ORG =
+            key("metrics.reporter.influxdb.org")
+                    .stringType()
+                    .noDefaultValue()
+                    .withDescription(
+                            "The InfluxDB organization name. Required for 
InfluxDB v2, not needed for InfluxDB v3.");
+
+    public static final ConfigOption<String> METRICS_REPORTER_INFLUXDB_TOKEN =
+            key("metrics.reporter.influxdb.token")
+                    .stringType()
+                    .noDefaultValue()
+                    .withDescription("The InfluxDB authentication token.");
+
+    public static final ConfigOption<Duration> 
METRICS_REPORTER_INFLUXDB_PUSH_INTERVAL =
+            key("metrics.reporter.influxdb.push-interval")
+                    .durationType()
+                    .defaultValue(Duration.ofSeconds(10))
+                    .withDescription("The interval of reporting metrics to 
InfluxDB.");
+
     // ------------------------------------------------------------------------
     //  ConfigOptions for lakehouse storage
     // ------------------------------------------------------------------------
diff --git a/fluss-dist/pom.xml b/fluss-dist/pom.xml
index 63e08b864..117f0a646 100644
--- a/fluss-dist/pom.xml
+++ b/fluss-dist/pom.xml
@@ -76,6 +76,13 @@
             <scope>provided</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.fluss</groupId>
+            <artifactId>fluss-metrics-influxdb</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.fluss</groupId>
             <artifactId>fluss-lake-paimon</artifactId>
diff --git a/fluss-dist/src/main/assemblies/plugins.xml 
b/fluss-dist/src/main/assemblies/plugins.xml
index 02459902c..158d12c97 100644
--- a/fluss-dist/src/main/assemblies/plugins.xml
+++ b/fluss-dist/src/main/assemblies/plugins.xml
@@ -80,6 +80,13 @@
             <fileMode>0644</fileMode>
         </file>
 
+        <file>
+            
<source>../fluss-metrics/fluss-metrics-influxdb/target/fluss-metrics-influxdb-${project.version}.jar</source>
+            <outputDirectory>plugins/influxdb/</outputDirectory>
+            <destName>fluss-metrics-influxdb-${project.version}.jar</destName>
+            <fileMode>0644</fileMode>
+        </file>
+
         <!-- lake formats -->
         <file>
             
<source>../fluss-lake/fluss-lake-paimon/target/fluss-lake-paimon-${project.version}.jar</source>
diff --git a/fluss-metrics/fluss-metrics-influxdb/pom.xml 
b/fluss-metrics/fluss-metrics-influxdb/pom.xml
new file mode 100644
index 000000000..a0e97032d
--- /dev/null
+++ b/fluss-metrics/fluss-metrics-influxdb/pom.xml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.fluss</groupId>
+        <artifactId>fluss-metrics</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>fluss-metrics-influxdb</artifactId>
+    <name>Fluss : Metrics : InfluxDB</name>
+
+    <properties>
+        <influxdb3.version>1.8.0</influxdb3.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.fluss</groupId>
+            <artifactId>fluss-common</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.influxdb</groupId>
+            <artifactId>influxdb3-java</artifactId>
+            <version>${influxdb3.version}</version>
+        </dependency>
+
+        <!-- test dependencies -->
+        <dependency>
+            <groupId>org.apache.fluss</groupId>
+            <artifactId>fluss-test-utils</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.fluss</groupId>
+            <artifactId>fluss-common</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.tomakehurst</groupId>
+            <artifactId>wiremock-jre8</artifactId>
+            <version>2.32.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <!-- compilation of main sources -->
+                    <skipMain>${skip.on.java8}</skipMain>
+                    <!-- compilation of test sources -->
+                    <skip>${skip.on.java8}</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>shade-fluss</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <artifactSet>
+                                <includes>
+                                    <include>com.influxdb:*</include>
+                                    <include>org.apache.arrow:*</include>
+                                    <include>io.grpc:*</include>
+                                    <include>io.netty:*</include>
+                                    <include>com.google.protobuf:*</include>
+                                    <include>com.google.guava:*</include>
+                                    
<include>com.fasterxml.jackson.core:*</include>
+                                    <include>com.google.flatbuffers:*</include>
+                                    <include>io.perfmark:*</include>
+                                    
<include>com.google.code.findbugs:*</include>
+                                    <include>org.codehaus.mojo:*</include>
+                                </includes>
+                            </artifactSet>
+                            <relocations>
+                                <relocation>
+                                    <pattern>com.influxdb</pattern>
+                                    
<shadedPattern>org.apache.fluss.metrics.influxdb.shaded.com.influxdb</shadedPattern>
+                                </relocation>
+                                <relocation>
+                                    <pattern>org.apache.arrow</pattern>
+                                    
<shadedPattern>org.apache.fluss.metrics.influxdb.shaded.org.apache.arrow</shadedPattern>
+                                </relocation>
+                                <relocation>
+                                    <pattern>io.grpc</pattern>
+                                    
<shadedPattern>org.apache.fluss.metrics.influxdb.shaded.io.grpc</shadedPattern>
+                                </relocation>
+                                <relocation>
+                                    <pattern>io.netty</pattern>
+                                    
<shadedPattern>org.apache.fluss.metrics.influxdb.shaded.io.netty</shadedPattern>
+                                </relocation>
+                                <relocation>
+                                    <pattern>com.google.protobuf</pattern>
+                                    
<shadedPattern>org.apache.fluss.metrics.influxdb.shaded.com.google.protobuf</shadedPattern>
+                                </relocation>
+                                <relocation>
+                                    <pattern>com.google.common</pattern>
+                                    
<shadedPattern>org.apache.fluss.metrics.influxdb.shaded.com.google.common</shadedPattern>
+                                </relocation>
+                                <relocation>
+                                    <pattern>com.fasterxml.jackson</pattern>
+                                    
<shadedPattern>org.apache.fluss.metrics.influxdb.shaded.com.fasterxml.jackson</shadedPattern>
+                                </relocation>
+                                <relocation>
+                                    <pattern>com.google.flatbuffers</pattern>
+                                    
<shadedPattern>org.apache.fluss.metrics.influxdb.shaded.com.google.flatbuffers</shadedPattern>
+                                </relocation>
+                                <relocation>
+                                    
<pattern>org.codehaus.mojo.annotations</pattern>
+                                    
<shadedPattern>org.apache.fluss.metrics.influxdb.shaded.org.codehaus.mojo.annotations</shadedPattern>
+                                </relocation>
+                            </relocations>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git 
a/fluss-metrics/fluss-metrics-influxdb/src/main/java/org/apache/fluss/metrics/influxdb/InfluxdbPointProducer.java
 
b/fluss-metrics/fluss-metrics-influxdb/src/main/java/org/apache/fluss/metrics/influxdb/InfluxdbPointProducer.java
new file mode 100644
index 000000000..5ae2b1ebb
--- /dev/null
+++ 
b/fluss-metrics/fluss-metrics-influxdb/src/main/java/org/apache/fluss/metrics/influxdb/InfluxdbPointProducer.java
@@ -0,0 +1,106 @@
+/*
+ * 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.fluss.metrics.influxdb;
+
+import org.apache.fluss.metrics.Counter;
+import org.apache.fluss.metrics.Gauge;
+import org.apache.fluss.metrics.Histogram;
+import org.apache.fluss.metrics.HistogramStatistics;
+import org.apache.fluss.metrics.Meter;
+import org.apache.fluss.metrics.Metric;
+
+import com.influxdb.v3.client.Point;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+
+/** Producer that creates InfluxDB {@link Point Points} from Fluss {@link 
Metric Metrics}. */
+public class InfluxdbPointProducer {
+
+    private static final InfluxdbPointProducer INSTANCE = new 
InfluxdbPointProducer();
+
+    public static InfluxdbPointProducer getInstance() {
+        return INSTANCE;
+    }
+
+    public Point createPoint(
+            Metric metric, String metricName, List<Map.Entry<String, String>> 
tags, Instant time) {
+        Point point = Point.measurement(metricName).setTimestamp(time);
+
+        if (tags != null) {
+            for (Map.Entry<String, String> tag : tags) {
+                point.setTag(tag.getKey(), tag.getValue());
+            }
+        }
+
+        if (metric instanceof Counter) {
+            return createPointForCounter((Counter) metric, point);
+        }
+
+        if (metric instanceof Gauge) {
+            return createPointForGauge((Gauge<?>) metric, point);
+        }
+
+        if (metric instanceof Meter) {
+            return createPointForMeter((Meter) metric, point);
+        }
+
+        if (metric instanceof Histogram) {
+            return createPointForHistogram((Histogram) metric, point);
+        }
+
+        throw new IllegalArgumentException("Unknown metric type: " + 
metric.getClass());
+    }
+
+    private Point createPointForCounter(Counter counter, Point point) {
+        return point.setField("count", counter.getCount());
+    }
+
+    private Point createPointForGauge(Gauge<?> gauge, Point point) {
+        Object value = gauge.getValue();
+
+        if (value instanceof Number) {
+            return point.setField("value", (Number) value);
+        } else if (value instanceof Boolean) {
+            return point.setField("value", ((Boolean) value).booleanValue());
+        } else {
+            return point.setField("value", String.valueOf(value));
+        }
+    }
+
+    private Point createPointForMeter(Meter meter, Point point) {
+        return point.setField("rate", meter.getRate()).setField("count", 
meter.getCount());
+    }
+
+    private Point createPointForHistogram(Histogram histogram, Point point) {
+        HistogramStatistics stats = histogram.getStatistics();
+        return point.setField("count", histogram.getCount())
+                .setField("mean", stats.getMean())
+                .setField("stddev", stats.getStdDev())
+                .setField("min", stats.getMin())
+                .setField("max", stats.getMax())
+                .setField("p50", stats.getQuantile(0.5))
+                .setField("p75", stats.getQuantile(0.75))
+                .setField("p95", stats.getQuantile(0.95))
+                .setField("p98", stats.getQuantile(0.98))
+                .setField("p99", stats.getQuantile(0.99))
+                .setField("p999", stats.getQuantile(0.999));
+    }
+}
diff --git 
a/fluss-metrics/fluss-metrics-influxdb/src/main/java/org/apache/fluss/metrics/influxdb/InfluxdbReporter.java
 
b/fluss-metrics/fluss-metrics-influxdb/src/main/java/org/apache/fluss/metrics/influxdb/InfluxdbReporter.java
new file mode 100644
index 000000000..d36f9beb1
--- /dev/null
+++ 
b/fluss-metrics/fluss-metrics-influxdb/src/main/java/org/apache/fluss/metrics/influxdb/InfluxdbReporter.java
@@ -0,0 +1,162 @@
+/*
+ * 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.fluss.metrics.influxdb;
+
+import org.apache.fluss.config.Configuration;
+import org.apache.fluss.metrics.Metric;
+import org.apache.fluss.metrics.groups.MetricGroup;
+import org.apache.fluss.metrics.reporter.ScheduledMetricReporter;
+import org.apache.fluss.utils.StringUtils;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.config.ClientConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+/** {@link ScheduledMetricReporter} that exports {@link Metric Metrics} via 
InfluxDB. */
+public class InfluxdbReporter implements ScheduledMetricReporter {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(InfluxdbReporter.class);
+
+    private static final char SCOPE_SEPARATOR = '_';
+    private static final String SCOPE_PREFIX = "fluss" + SCOPE_SEPARATOR;
+    private static final Pattern INVALID_CHAR_PATTERN = 
Pattern.compile("[^a-zA-Z0-9_:]");
+
+    final Map<Metric, String> metricNames;
+    final Map<Metric, List<Map.Entry<String, String>>> metricTags;
+    private final InfluxDBClient client;
+    private final Duration pushInterval;
+    private final InfluxdbPointProducer pointProducer;
+
+    public InfluxdbReporter(
+            String hostUrl, String org, String bucket, String token, Duration 
pushInterval) {
+        ClientConfig.Builder clientConfigBuilder =
+                new 
ClientConfig.Builder().host(hostUrl).token(token.toCharArray());
+
+        if (!StringUtils.isNullOrWhitespaceOnly(org)) {
+            clientConfigBuilder.organization(org);
+        }
+
+        ClientConfig clientConfig = 
clientConfigBuilder.database(bucket).build();
+
+        this.client = InfluxDBClient.getInstance(clientConfig);
+        this.pushInterval = pushInterval;
+        this.metricNames = new ConcurrentHashMap<>();
+        this.metricTags = new ConcurrentHashMap<>();
+        this.pointProducer = InfluxdbPointProducer.getInstance();
+
+        LOG.info("Started InfluxDB reporter connecting to {}", hostUrl);
+    }
+
+    @Override
+    public void open(Configuration config) {
+        // do nothing
+    }
+
+    @Override
+    public void close() {
+        if (client != null) {
+            try {
+                client.close();
+            } catch (Exception e) {
+                LOG.warn("Failed to close InfluxDB client", e);
+            }
+        }
+    }
+
+    @Override
+    public void report() {
+        List<Point> points = new ArrayList<>();
+        Instant now = Instant.now();
+
+        for (Map.Entry<Metric, String> entry : metricNames.entrySet()) {
+            Metric metric = entry.getKey();
+            String metricName = entry.getValue();
+            List<Map.Entry<String, String>> tags =
+                    metricTags.getOrDefault(metric, Collections.emptyList());
+
+            try {
+                Point point = pointProducer.createPoint(metric, metricName, 
tags, now);
+                points.add(point);
+            } catch (Exception e) {
+                LOG.warn("Failed to create point for metric {}", metricName, 
e);
+            }
+        }
+
+        if (!points.isEmpty()) {
+            try {
+                client.writePoints(points);
+            } catch (Exception e) {
+                LOG.warn("Failed to write points to InfluxDB", e);
+            }
+        }
+    }
+
+    @Override
+    public Duration scheduleInterval() {
+        return pushInterval;
+    }
+
+    @Override
+    public void notifyOfAddedMetric(Metric metric, String metricName, 
MetricGroup group) {
+        String scopedMetricName = getScopedName(metricName, group);
+        List<Map.Entry<String, String>> tags = getTags(group);
+
+        metricNames.put(metric, scopedMetricName);
+        metricTags.put(metric, tags);
+    }
+
+    @Override
+    public void notifyOfRemovedMetric(Metric metric, String metricName, 
MetricGroup group) {
+        metricNames.remove(metric);
+        metricTags.remove(metric);
+    }
+
+    private String getScopedName(String metricName, MetricGroup group) {
+        return SCOPE_PREFIX
+                + group.getLogicalScope(this::filterCharacters, 
SCOPE_SEPARATOR)
+                + SCOPE_SEPARATOR
+                + filterCharacters(metricName);
+    }
+
+    private List<Map.Entry<String, String>> getTags(MetricGroup group) {
+        List<Map.Entry<String, String>> tags = new ArrayList<>();
+        for (Map.Entry<String, String> entry : 
group.getAllVariables().entrySet()) {
+            tags.add(
+                    new AbstractMap.SimpleEntry<>(
+                            filterCharacters(entry.getKey()), 
entry.getValue()));
+        }
+        return tags;
+    }
+
+    private String filterCharacters(String input) {
+        return 
INVALID_CHAR_PATTERN.matcher(input).replaceAll(String.valueOf(SCOPE_SEPARATOR));
+    }
+}
diff --git 
a/fluss-metrics/fluss-metrics-influxdb/src/main/java/org/apache/fluss/metrics/influxdb/InfluxdbReporterPlugin.java
 
b/fluss-metrics/fluss-metrics-influxdb/src/main/java/org/apache/fluss/metrics/influxdb/InfluxdbReporterPlugin.java
new file mode 100644
index 000000000..eccc9f95a
--- /dev/null
+++ 
b/fluss-metrics/fluss-metrics-influxdb/src/main/java/org/apache/fluss/metrics/influxdb/InfluxdbReporterPlugin.java
@@ -0,0 +1,81 @@
+/*
+ * 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.fluss.metrics.influxdb;
+
+import org.apache.fluss.config.Configuration;
+import org.apache.fluss.metrics.reporter.MetricReporter;
+import org.apache.fluss.metrics.reporter.MetricReporterPlugin;
+import org.apache.fluss.utils.StringUtils;
+
+import java.time.Duration;
+
+import static 
org.apache.fluss.config.ConfigOptions.METRICS_REPORTER_INFLUXDB_BUCKET;
+import static 
org.apache.fluss.config.ConfigOptions.METRICS_REPORTER_INFLUXDB_HOST_URL;
+import static 
org.apache.fluss.config.ConfigOptions.METRICS_REPORTER_INFLUXDB_ORG;
+import static 
org.apache.fluss.config.ConfigOptions.METRICS_REPORTER_INFLUXDB_PUSH_INTERVAL;
+import static 
org.apache.fluss.config.ConfigOptions.METRICS_REPORTER_INFLUXDB_TOKEN;
+import static 
org.apache.fluss.config.ConfigOptions.METRICS_REPORTER_INFLUXDB_VERSION;
+
+/** {@link MetricReporterPlugin} for {@link InfluxdbReporter}. */
+public class InfluxdbReporterPlugin implements MetricReporterPlugin {
+
+    private static final String PLUGIN_NAME = "influxdb";
+
+    @Override
+    public MetricReporter createMetricReporter(Configuration configuration) {
+        String version = 
configuration.getString(METRICS_REPORTER_INFLUXDB_VERSION);
+        String hostUrl = 
configuration.getString(METRICS_REPORTER_INFLUXDB_HOST_URL);
+        String org = configuration.getString(METRICS_REPORTER_INFLUXDB_ORG);
+        String bucket = 
configuration.getString(METRICS_REPORTER_INFLUXDB_BUCKET);
+        String token = 
configuration.getString(METRICS_REPORTER_INFLUXDB_TOKEN);
+        Duration pushInterval = 
configuration.get(METRICS_REPORTER_INFLUXDB_PUSH_INTERVAL);
+
+        if (StringUtils.isNullOrWhitespaceOnly(hostUrl)) {
+            throw new IllegalArgumentException(
+                    "InfluxDB host URL must be configured via '"
+                            + METRICS_REPORTER_INFLUXDB_HOST_URL.key()
+                            + "'");
+        }
+        if ("v2".equalsIgnoreCase(version) && 
StringUtils.isNullOrWhitespaceOnly(org)) {
+            throw new IllegalArgumentException(
+                    "InfluxDB organization must be configured via '"
+                            + METRICS_REPORTER_INFLUXDB_ORG.key()
+                            + "' when using InfluxDB v2");
+        }
+        if (StringUtils.isNullOrWhitespaceOnly(bucket)) {
+            throw new IllegalArgumentException(
+                    "InfluxDB bucket must be configured via '"
+                            + METRICS_REPORTER_INFLUXDB_BUCKET.key()
+                            + "'");
+        }
+        if (StringUtils.isNullOrWhitespaceOnly(token)) {
+            throw new IllegalArgumentException(
+                    "InfluxDB token must be configured via '"
+                            + METRICS_REPORTER_INFLUXDB_TOKEN.key()
+                            + "'");
+        }
+
+        return new InfluxdbReporter(hostUrl, org, bucket, token, pushInterval);
+    }
+
+    @Override
+    public String identifier() {
+        return PLUGIN_NAME;
+    }
+}
diff --git 
a/fluss-metrics/fluss-metrics-influxdb/src/main/resources/META-INF/NOTICE 
b/fluss-metrics/fluss-metrics-influxdb/src/main/resources/META-INF/NOTICE
new file mode 100644
index 000000000..0ff27d6c8
--- /dev/null
+++ b/fluss-metrics/fluss-metrics-influxdb/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,56 @@
+fluss-metrics-influxdb
+Copyright 2025-2026 The Apache Software Foundation
+
+This project includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This project bundles the following dependencies under the Apache Software 
License 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
+
+- com.fasterxml.jackson.core:jackson-annotations:2.15.3
+- com.fasterxml.jackson.core:jackson-core:2.15.3
+- com.fasterxml.jackson.core:jackson-databind:2.15.3
+- com.google.flatbuffers:flatbuffers-java:25.2.10
+- com.google.guava:failureaccess:1.0.3
+- com.google.guava:guava:33.5.0-jre
+- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
+- com.google.protobuf:protobuf-java-util:4.30.2
+- com.google.protobuf:protobuf-java:4.33.5
+- com.influxdb:influxdb3-java:1.8.0
+- io.grpc:grpc-api:1.71.0
+- io.grpc:grpc-context:1.71.0
+- io.grpc:grpc-core:1.71.0
+- io.grpc:grpc-netty:1.71.0
+- io.grpc:grpc-protobuf-lite:1.71.0
+- io.grpc:grpc-protobuf:1.71.0
+- io.grpc:grpc-stub:1.71.0
+- io.grpc:grpc-util:1.71.0
+- io.netty:netty-buffer:4.2.10.Final
+- io.netty:netty-codec-base:4.2.10.Final
+- io.netty:netty-codec-compression:4.2.10.Final
+- io.netty:netty-codec-http2:4.2.10.Final
+- io.netty:netty-codec-http:4.2.10.Final
+- io.netty:netty-codec-marshalling:4.2.10.Final
+- io.netty:netty-codec-protobuf:4.2.10.Final
+- io.netty:netty-codec-socks:4.1.110.Final
+- io.netty:netty-codec:4.2.10.Final
+- io.netty:netty-common:4.2.10.Final
+- io.netty:netty-handler-proxy:4.1.110.Final
+- io.netty:netty-handler:4.2.10.Final
+- io.netty:netty-resolver:4.2.10.Final
+- io.netty:netty-tcnative-boringssl-static:2.0.75.Final
+- io.netty:netty-tcnative-boringssl-static:linux-aarch_64:2.0.75.Final
+- io.netty:netty-tcnative-boringssl-static:linux-x86_64:2.0.75.Final
+- io.netty:netty-tcnative-boringssl-static:osx-aarch_64:2.0.75.Final
+- io.netty:netty-tcnative-boringssl-static:osx-x86_64:2.0.75.Final
+- io.netty:netty-tcnative-boringssl-static:windows-x86_64:2.0.75.Final
+- io.netty:netty-tcnative-classes:2.0.75.Final
+- io.netty:netty-transport-native-unix-common:4.2.10.Final
+- io.netty:netty-transport:4.2.10.Final
+- io.perfmark:perfmark-api:0.27.0
+- org.apache.arrow:arrow-format:18.3.0
+- org.apache.arrow:arrow-memory-core:18.3.0
+- org.apache.arrow:arrow-memory-netty-buffer-patch:18.3.0
+- org.apache.arrow:arrow-memory-netty:18.3.0
+- org.apache.arrow:arrow-vector:18.3.0
+- org.apache.arrow:flight-core:18.3.0
+- org.codehaus.mojo:animal-sniffer-annotations:1.24
diff --git 
a/fluss-metrics/fluss-metrics-influxdb/src/main/resources/META-INF/services/org.apache.fluss.metrics.reporter.MetricReporterPlugin
 
b/fluss-metrics/fluss-metrics-influxdb/src/main/resources/META-INF/services/org.apache.fluss.metrics.reporter.MetricReporterPlugin
new file mode 100644
index 000000000..e2b8a6eae
--- /dev/null
+++ 
b/fluss-metrics/fluss-metrics-influxdb/src/main/resources/META-INF/services/org.apache.fluss.metrics.reporter.MetricReporterPlugin
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+
+org.apache.fluss.metrics.influxdb.InfluxdbReporterPlugin
diff --git 
a/fluss-metrics/fluss-metrics-influxdb/src/test/java/org/apache/fluss/metrics/influxdb/InfluxdbPointProducerTest.java
 
b/fluss-metrics/fluss-metrics-influxdb/src/test/java/org/apache/fluss/metrics/influxdb/InfluxdbPointProducerTest.java
new file mode 100644
index 000000000..9cb999c03
--- /dev/null
+++ 
b/fluss-metrics/fluss-metrics-influxdb/src/test/java/org/apache/fluss/metrics/influxdb/InfluxdbPointProducerTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.fluss.metrics.influxdb;
+
+import org.apache.fluss.metrics.Counter;
+import org.apache.fluss.metrics.Gauge;
+import org.apache.fluss.metrics.SimpleCounter;
+import org.apache.fluss.metrics.util.TestHistogram;
+import org.apache.fluss.metrics.util.TestMeter;
+
+import com.influxdb.v3.client.Point;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+import java.time.Instant;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for the {@link InfluxdbPointProducer}. */
+class InfluxdbPointProducerTest {
+
+    private final InfluxdbPointProducer pointProducer = 
InfluxdbPointProducer.getInstance();
+
+    @Test
+    void testCreatePointForCounter() {
+        Counter counter = new SimpleCounter();
+        counter.inc(42L);
+
+        List<Map.Entry<String, String>> tags = new ArrayList<>();
+        tags.add(new AbstractMap.SimpleEntry<>("host", "localhost"));
+        tags.add(new AbstractMap.SimpleEntry<>("server", "test"));
+
+        Instant time = Instant.now();
+        Point point = pointProducer.createPoint(counter, "test_counter", tags, 
time);
+
+        assertThat(point).isNotNull();
+        assertThat(point.getMeasurement()).isEqualTo("test_counter");
+        assertThat(point.getField("count")).isEqualTo(42L);
+        assertThat(point.getTimestamp())
+                .isEqualTo(
+                        BigInteger.valueOf(
+                                time.getEpochSecond() * 1_000_000_000L + 
time.getNano()));
+    }
+
+    @Test
+    void testCreatePointForGauge() {
+        List<Map.Entry<String, String>> tags = new ArrayList<>();
+        tags.add(new AbstractMap.SimpleEntry<>("host", "localhost"));
+        Instant time = Instant.now();
+
+        // Test with Number (Integer)
+        Gauge<Integer> intGauge = () -> 123;
+        Point intPoint = pointProducer.createPoint(intGauge, "test_gauge_int", 
tags, time);
+        assertThat(intPoint).isNotNull();
+        assertThat(intPoint.getMeasurement()).isEqualTo("test_gauge_int");
+        assertThat(intPoint.getField("value")).isEqualTo(123);
+        assertThat(intPoint.getTimestamp())
+                .isEqualTo(
+                        BigInteger.valueOf(
+                                time.getEpochSecond() * 1_000_000_000L + 
time.getNano()));
+
+        // Test with Number (Double)
+        Gauge<Double> doubleGauge = () -> 123.456;
+        Point doublePoint = pointProducer.createPoint(doubleGauge, 
"test_gauge_double", tags, time);
+        assertThat(doublePoint).isNotNull();
+        
assertThat(doublePoint.getMeasurement()).isEqualTo("test_gauge_double");
+        assertThat(doublePoint.getField("value")).isEqualTo(123.456);
+
+        // Test with Boolean
+        Gauge<Boolean> boolGauge = () -> true;
+        Point boolPoint = pointProducer.createPoint(boolGauge, 
"test_gauge_boolean", tags, time);
+        assertThat(boolPoint).isNotNull();
+        assertThat(boolPoint.getMeasurement()).isEqualTo("test_gauge_boolean");
+        assertThat(boolPoint.getField("value")).isEqualTo(true);
+
+        // Test with String
+        Gauge<String> stringGauge = () -> "test_value";
+        Point stringPoint = pointProducer.createPoint(stringGauge, 
"test_gauge_string", tags, time);
+        assertThat(stringPoint).isNotNull();
+        
assertThat(stringPoint.getMeasurement()).isEqualTo("test_gauge_string");
+        assertThat(stringPoint.getField("value")).isEqualTo("test_value");
+
+        // Test with Null
+        Gauge<Object> nullGauge = () -> null;
+        Point nullPoint = pointProducer.createPoint(nullGauge, 
"test_gauge_null", tags, time);
+        assertThat(nullPoint).isNotNull();
+        assertThat(nullPoint.getMeasurement()).isEqualTo("test_gauge_null");
+        assertThat(nullPoint.getField("value")).isEqualTo("null");
+    }
+
+    @Test
+    void testCreatePointForMeter() {
+        TestMeter meter = new TestMeter(1000, 5.0);
+
+        List<Map.Entry<String, String>> tags = new ArrayList<>();
+        tags.add(new AbstractMap.SimpleEntry<>("host", "localhost"));
+
+        Instant time = Instant.now();
+        Point point = pointProducer.createPoint(meter, "test_meter", tags, 
time);
+
+        assertThat(point).isNotNull();
+        assertThat(point.getMeasurement()).isEqualTo("test_meter");
+        assertThat(point.getField("rate")).isEqualTo(5.0);
+        assertThat(point.getField("count")).isEqualTo(1000L);
+        assertThat(point.getTimestamp())
+                .isEqualTo(
+                        BigInteger.valueOf(
+                                time.getEpochSecond() * 1_000_000_000L + 
time.getNano()));
+    }
+
+    @Test
+    void testCreatePointForHistogram() {
+        TestHistogram histogram = new TestHistogram();
+        histogram.setCount(80);
+        histogram.setMean(50.5);
+        histogram.setStdDev(10.2);
+        histogram.setMax(100);
+        histogram.setMin(1);
+
+        List<Map.Entry<String, String>> tags = new ArrayList<>();
+        tags.add(new AbstractMap.SimpleEntry<>("host", "localhost"));
+
+        Instant time = Instant.now();
+        Point point = pointProducer.createPoint(histogram, "test_histogram", 
tags, time);
+
+        assertThat(point).isNotNull();
+        assertThat(point.getMeasurement()).isEqualTo("test_histogram");
+        assertThat(point.getField("count")).isEqualTo(80L);
+        assertThat(point.getField("mean")).isEqualTo(50.5);
+        assertThat(point.getField("stddev")).isEqualTo(10.2);
+        assertThat(point.getField("max")).isEqualTo(100L);
+        assertThat(point.getField("min")).isEqualTo(1L);
+        assertThat(point.getTimestamp())
+                .isEqualTo(
+                        BigInteger.valueOf(
+                                time.getEpochSecond() * 1_000_000_000L + 
time.getNano()));
+    }
+}
diff --git 
a/fluss-metrics/fluss-metrics-influxdb/src/test/java/org/apache/fluss/metrics/influxdb/InfluxdbReporterPluginTest.java
 
b/fluss-metrics/fluss-metrics-influxdb/src/test/java/org/apache/fluss/metrics/influxdb/InfluxdbReporterPluginTest.java
new file mode 100644
index 000000000..4df8f1abf
--- /dev/null
+++ 
b/fluss-metrics/fluss-metrics-influxdb/src/test/java/org/apache/fluss/metrics/influxdb/InfluxdbReporterPluginTest.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.fluss.metrics.influxdb;
+
+import org.apache.fluss.config.ConfigOptions;
+import org.apache.fluss.config.Configuration;
+import org.apache.fluss.metrics.util.MetricReporterTestUtils;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for the {@link InfluxdbReporterPlugin}. */
+class InfluxdbReporterPluginTest {
+
+    @Test
+    void testCreateMetricReporterWithValidConfig() {
+        Configuration config = new Configuration();
+        config.setString(ConfigOptions.METRICS_REPORTER_INFLUXDB_HOST_URL, 
"http://localhost:8086";);
+        config.setString(ConfigOptions.METRICS_REPORTER_INFLUXDB_ORG, 
"test-org");
+        config.setString(ConfigOptions.METRICS_REPORTER_INFLUXDB_BUCKET, 
"test-bucket");
+        config.setString(ConfigOptions.METRICS_REPORTER_INFLUXDB_TOKEN, 
"test-token");
+        config.set(ConfigOptions.METRICS_REPORTER_INFLUXDB_PUSH_INTERVAL, 
Duration.ofSeconds(15));
+
+        InfluxdbReporterPlugin plugin = new InfluxdbReporterPlugin();
+        InfluxdbReporter reporter = (InfluxdbReporter) 
plugin.createMetricReporter(config);
+
+        try {
+            assertThat(reporter).isNotNull();
+            
assertThat(reporter.scheduleInterval()).isEqualTo(Duration.ofSeconds(15));
+        } finally {
+            reporter.close();
+        }
+    }
+
+    @Test
+    void testMetricReporterSetupViaSPI() {
+        
MetricReporterTestUtils.testMetricReporterSetupViaSPI(InfluxdbReporterPlugin.class);
+    }
+}
diff --git 
a/fluss-metrics/fluss-metrics-influxdb/src/test/java/org/apache/fluss/metrics/influxdb/InfluxdbReporterTest.java
 
b/fluss-metrics/fluss-metrics-influxdb/src/test/java/org/apache/fluss/metrics/influxdb/InfluxdbReporterTest.java
new file mode 100644
index 000000000..ebcf2dc90
--- /dev/null
+++ 
b/fluss-metrics/fluss-metrics-influxdb/src/test/java/org/apache/fluss/metrics/influxdb/InfluxdbReporterTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.fluss.metrics.influxdb;
+
+import org.apache.fluss.config.Configuration;
+import org.apache.fluss.metrics.Counter;
+import org.apache.fluss.metrics.SimpleCounter;
+import org.apache.fluss.metrics.groups.MetricGroup;
+import org.apache.fluss.metrics.util.TestMetricGroup;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.containing;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static 
com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for the {@link InfluxdbReporter}. */
+class InfluxdbReporterTest {
+
+    private static final String METRIC_HOSTNAME = "localhost";
+
+    private static final Map<String, String> variables;
+    private static final MetricGroup metricGroup;
+
+    static {
+        variables = new HashMap<>();
+        variables.put("<host>", METRIC_HOSTNAME);
+        variables.put("table", "test_table");
+
+        metricGroup =
+                TestMetricGroup.newBuilder()
+                        .setLogicalScopeFunction((characterFilter, character) 
-> "tabletServer")
+                        .setVariables(variables)
+                        .build();
+    }
+
+    private WireMockServer wireMockServer;
+    private InfluxdbReporter reporter;
+
+    @BeforeEach
+    void setUp() {
+        wireMockServer = new WireMockServer(wireMockConfig().dynamicPort());
+        wireMockServer.start();
+    }
+
+    @AfterEach
+    void tearDown() {
+        if (reporter != null) {
+            reporter.close();
+        }
+        if (wireMockServer != null) {
+            wireMockServer.stop();
+        }
+    }
+
+    @Test
+    void testMetricRegistration() {
+        reporter = createReporter();
+
+        String metricName = "TestCounter";
+        Counter counter = new SimpleCounter();
+        reporter.notifyOfAddedMetric(counter, metricName, metricGroup);
+
+        String name = reporter.metricNames.get(counter);
+        assertThat(name).isNotNull();
+        assertThat(name).isEqualTo("fluss_tabletServer_" + metricName);
+
+        List<Map.Entry<String, String>> tags = 
reporter.metricTags.get(counter);
+        assertThat(tags).isNotNull();
+        assertThat(tags)
+                .anyMatch(e -> e.getKey().equals("_host_") && 
e.getValue().equals(METRIC_HOSTNAME));
+        assertThat(tags)
+                .anyMatch(e -> e.getKey().equals("table") && 
e.getValue().equals("test_table"));
+    }
+
+    @Test
+    void testMetricReporting() {
+        wireMockServer.stubFor(
+                
post(urlPathEqualTo("/api/v2/write")).willReturn(aResponse().withStatus(200)));
+
+        reporter = createReporter();
+
+        String metricName = "TestCounter";
+        Counter counter = new SimpleCounter();
+        reporter.notifyOfAddedMetric(counter, metricName, metricGroup);
+        counter.inc(42);
+
+        reporter.report();
+
+        wireMockServer.verify(
+                postRequestedFor(urlPathEqualTo("/api/v2/write"))
+                        .withRequestBody(containing("fluss_tabletServer_" + 
metricName))
+                        .withRequestBody(containing("count=42i")));
+    }
+
+    private InfluxdbReporter createReporter() {
+        InfluxdbReporter reporter =
+                new InfluxdbReporter(
+                        wireMockServer.baseUrl(),
+                        "test-org",
+                        "test-bucket",
+                        "test-token",
+                        Duration.ofSeconds(10));
+        reporter.open(new Configuration());
+        return reporter;
+    }
+}
diff --git 
a/fluss-metrics/fluss-metrics-influxdb/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
 
b/fluss-metrics/fluss-metrics-influxdb/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 000000000..ca0e907f6
--- /dev/null
+++ 
b/fluss-metrics/fluss-metrics-influxdb/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+org.apache.fluss.testutils.common.TestLoggerExtension
\ No newline at end of file
diff --git 
a/fluss-metrics/fluss-metrics-influxdb/src/test/resources/log4j2-test.properties
 
b/fluss-metrics/fluss-metrics-influxdb/src/test/resources/log4j2-test.properties
new file mode 100644
index 000000000..288e00f66
--- /dev/null
+++ 
b/fluss-metrics/fluss-metrics-influxdb/src/test/resources/log4j2-test.properties
@@ -0,0 +1,28 @@
+#
+# 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.
+#
+
+# Set root logger level to OFF to not flood build logs
+# set manually to INFO for debugging purposes
+rootLogger.level = OFF
+rootLogger.appenderRef.test.ref = TestLogger
+
+appender.testlogger.name = TestLogger
+appender.testlogger.type = CONSOLE
+appender.testlogger.target = SYSTEM_ERR
+appender.testlogger.layout.type = PatternLayout
+appender.testlogger.layout.pattern = %-4r [%t] %-5p %c %x - %m%n
diff --git a/fluss-metrics/pom.xml b/fluss-metrics/pom.xml
index 482cca3fa..202993ea3 100644
--- a/fluss-metrics/pom.xml
+++ b/fluss-metrics/pom.xml
@@ -34,6 +34,7 @@
     <modules>
         <module>fluss-metrics-prometheus</module>
         <module>fluss-metrics-jmx</module>
+        <module>fluss-metrics-influxdb</module>
     </modules>
 
     <!-- override these root dependencies as 'provided', so they don't end up
diff --git a/website/docs/maintenance/observability/metric-reporters.md 
b/website/docs/maintenance/observability/metric-reporters.md
index 43ce9473b..4afdca5b4 100644
--- a/website/docs/maintenance/observability/metric-reporters.md
+++ b/website/docs/maintenance/observability/metric-reporters.md
@@ -105,3 +105,40 @@ metrics.reporter.prometheus-push.random-job-name-suffix: 
true
 metrics.reporter.prometheus-push.delete-on-shutdown: true
 metrics.reporter.prometheus-push.grouping-key: 
instance=instance01;cluster=clusterA
 ```
+
+### InfluxDB
+
+Type: push
+
+InfluxDB reporter supports both InfluxDB v2 and InfluxDB v3. The default 
version is v3.
+
+Parameters:
+
+- `metrics.reporter.influxdb.version` - (Optional) The InfluxDB version to 
connect to, defaults to `v3`. Supported values are `v2` and `v3`.
+- `metrics.reporter.influxdb.host-url` - The InfluxDB server host URL 
including scheme, host name, and port.
+- `metrics.reporter.influxdb.bucket` - The InfluxDB bucket/database name.
+- `metrics.reporter.influxdb.org` - The InfluxDB organization name. Required 
for InfluxDB v2, not needed for InfluxDB v3.
+- `metrics.reporter.influxdb.token` - The InfluxDB authentication token.
+- `metrics.reporter.influxdb.push-interval` - (Optional) The interval of 
reporting metrics to InfluxDB, defaults to 10 SECONDS.
+
+Example configuration for InfluxDB v3 (default):
+
+```yaml
+metrics.reporters: influxdb
+metrics.reporter.influxdb.host-url: http://localhost:8181
+metrics.reporter.influxdb.bucket: fluss_metrics
+metrics.reporter.influxdb.token: your-influxdb-token
+metrics.reporter.influxdb.push-interval: 10 SECONDS
+```
+
+Example configuration for InfluxDB v2:
+
+```yaml
+metrics.reporters: influxdb
+metrics.reporter.influxdb.version: v2
+metrics.reporter.influxdb.host-url: http://localhost:8086
+metrics.reporter.influxdb.bucket: fluss_metrics
+metrics.reporter.influxdb.org: fluss_org
+metrics.reporter.influxdb.token: your-influxdb-token
+metrics.reporter.influxdb.push-interval: 10 SECONDS
+```

Reply via email to