This is an automated email from the ASF dual-hosted git repository.
alexstocks pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/dubbo-go-samples.git
The following commit(s) were added to refs/heads/main by this push:
new 468ff5af feat: add OTLP HTTP tracing exporter example with
Dubbo/Triple/JsonRPC protocols (#926)
468ff5af is described below
commit 468ff5afaf4c8f2047157600f16011098bf98918
Author: Xuetao Li <[email protected]>
AuthorDate: Sun Oct 19 22:00:59 2025 +0800
feat: add OTLP HTTP tracing exporter example with Dubbo/Triple/JsonRPC
protocols (#926)
* feat: add OTLP HTTP tracing exporter example with Dubbo, Triple, and
JSON-RPC protocols
---
README.md | 2 +
README_CN.md | 2 +
go.mod | 9 +-
go.sum | 19 +-
.../tests/integration/otel_test.go | 216 ++++++++++++++++++
otel/tracing/otlp_http_exporter/README.md | 30 +++
otel/tracing/otlp_http_exporter/README_CN.md | 29 +++
.../otlp_http_exporter/go-server/cmd/main.go | 245 +++++++++++++++++++++
start_integrate_test.sh | 1 +
9 files changed, 535 insertions(+), 18 deletions(-)
diff --git a/README.md b/README.md
index cf5fb8bb..16a662c3 100644
--- a/README.md
+++ b/README.md
@@ -50,6 +50,8 @@
* metrics: How to collect and expose metrics from Dubbo-go services,
supporting both Prometheus Push and Pull modes , it also includes the tool
(pgw-cleaner) used to clear zombie metrics in Push mode.
* online_boutique: Microservices online boutique demo using Dubbo-go
* otel/tracing: Distributed tracing example using OpenTelemetry
+ * stdout: use stdout exporter to export tracing data example
+ * otlp_http_exporter: use otlpHttpExporter to export tracing data example,
including dubbo/triple/jsonrpc protocols
* registry: Examples of using different service registries (e.g., nacos,
zookeeper)
* retry: Demonstrates retry mechanisms in Dubbo-go RPC calls
* rpc: Various RPC protocol examples with Dubbo-go
diff --git a/README_CN.md b/README_CN.md
index 9fd65d67..0af628a3 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -46,6 +46,8 @@
* metrics:Dubbo-go 服务指标采集与暴露示例,支持 Prometheus Push 模式和 Pull 模式, 同时包含 Push
模式下僵尸指标清理工具 “pgw-cleaner”
* online_boutique:Dubbo-go 微服务电商示例
* otel/tracing:基于 OpenTelemetry 的分布式链路追踪示例
+ * stdout: 使用 stdout exporter 输出追踪数据示例
+ * otlp_http_exporter: 使用 otlpHttpExporter 输出追踪数据示例,含 dubbo/triple/jsonrpc 协议
* registry:多种服务注册中心(如 nacos、zookeeper)用法示例
* retry:Dubbo-go RPC 调用重试机制示例
* rpc:多种 RPC 协议示例
diff --git a/go.mod b/go.mod
index 7dcb669e..5c1592b2 100644
--- a/go.mod
+++ b/go.mod
@@ -23,7 +23,7 @@ require (
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5
github.com/openzipkin/zipkin-go v0.4.2
github.com/pkg/errors v0.9.1
- github.com/prometheus/client_golang v1.13.0
+ github.com/prometheus/client_golang v1.19.1
github.com/seata/seata-go v0.1.0-rc1
github.com/stretchr/testify v1.9.0
github.com/tmc/langchaingo v0.1.13
@@ -31,6 +31,7 @@ require (
go.opentelemetry.io/otel v1.26.0
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
go.opentelemetry.io/otel/sdk v1.21.0
+ go.opentelemetry.io/proto/otlp v1.0.0
google.golang.org/grpc v1.64.0
google.golang.org/protobuf v1.34.1
gopkg.in/yaml.v3 v3.0.1
@@ -106,7 +107,6 @@ require (
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -125,8 +125,8 @@ require (
github.com/polarismesh/polaris-go v1.3.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c //
indirect
github.com/prometheus/client_model v0.5.0 // indirect
- github.com/prometheus/common v0.37.0 // indirect
- github.com/prometheus/procfs v0.8.0 // indirect
+ github.com/prometheus/common v0.48.0 // indirect
+ github.com/prometheus/procfs v0.12.0 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
@@ -148,7 +148,6 @@ require (
go.opentelemetry.io/otel/exporters/zipkin v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.26.0 // indirect
go.opentelemetry.io/otel/trace v1.26.0 // indirect
- go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
diff --git a/go.sum b/go.sum
index 027bc322..1c9b0d3c 100644
--- a/go.sum
+++ b/go.sum
@@ -306,13 +306,11 @@ github.com/go-kit/kit v0.8.0/go.mod
h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod
h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod
h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/log v0.1.0/go.mod
h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-kit/log v0.2.0/go.mod
h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod
h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-ldap/ldap/v3 v3.1.10/go.mod
h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
github.com/go-logfmt/logfmt v0.3.0/go.mod
h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod
h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod
h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logfmt/logfmt v0.5.1/go.mod
h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod
h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -646,8 +644,6 @@ github.com/mattn/go-isatty v0.0.20
h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod
h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.2/go.mod
h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod
h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/matttproud/golang_protobuf_extensions v1.0.4
h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
-github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod
h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.0.14/go.mod
h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod
h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod
h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@@ -775,8 +771,8 @@ github.com/prometheus/client_golang v1.11.0/go.mod
h1:Z6t4BnS23TR94PD6BsDNk8yVqr
github.com/prometheus/client_golang v1.11.1/go.mod
h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod
h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.12.2/go.mod
h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
-github.com/prometheus/client_golang v1.13.0
h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
-github.com/prometheus/client_golang v1.13.0/go.mod
h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
+github.com/prometheus/client_golang v1.19.1
h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
+github.com/prometheus/client_golang v1.19.1/go.mod
h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod
h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod
h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod
h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -796,8 +792,8 @@ github.com/prometheus/common v0.15.0/go.mod
h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
github.com/prometheus/common v0.26.0/go.mod
h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.28.0/go.mod
h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.32.1/go.mod
h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
-github.com/prometheus/common v0.37.0
h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
-github.com/prometheus/common v0.37.0/go.mod
h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
+github.com/prometheus/common v0.48.0
h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
+github.com/prometheus/common v0.48.0/go.mod
h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod
h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod
h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod
h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -807,8 +803,8 @@ github.com/prometheus/procfs v0.1.3/go.mod
h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.2.0/go.mod
h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod
h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod
h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.8.0
h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
-github.com/prometheus/procfs v0.8.0/go.mod
h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
+github.com/prometheus/procfs v0.12.0
h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod
h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/prometheus/statsd_exporter v0.21.0/go.mod
h1:rbT83sZq2V+p73lHhPZfMc3MLCHmSHelCh9hSGYNLTQ=
github.com/prometheus/tsdb v0.7.1/go.mod
h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod
h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -1136,8 +1132,6 @@ golang.org/x/net
v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod
h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod
h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod
h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod
h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
@@ -1149,7 +1143,6 @@ golang.org/x/oauth2
v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod
h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod
h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod
h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod
h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git
a/integrate_test/otel/tracing/otlp_http_exporter/tests/integration/otel_test.go
b/integrate_test/otel/tracing/otlp_http_exporter/tests/integration/otel_test.go
new file mode 100644
index 00000000..6b7f9453
--- /dev/null
+++
b/integrate_test/otel/tracing/otlp_http_exporter/tests/integration/otel_test.go
@@ -0,0 +1,216 @@
+/*
+ * 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 integration
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "os"
+ "testing"
+ "time"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3"
+ "dubbo.apache.org/dubbo-go/v3/client"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ _ "dubbo.apache.org/dubbo-go/v3/imports"
+ "dubbo.apache.org/dubbo-go/v3/otel/trace"
+
+ "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+ greetpb "github.com/apache/dubbo-go-samples/otel/tracing/stdout/proto"
+)
+
+// ---- Test configuration ----
+const (
+ defaultOtelEndpoint = "127.0.0.1:4318"
+
+ tripleAddr = "127.0.0.1:20000"
+ dubboAddr = "127.0.0.1:20001"
+ jsonRPCAddr = "127.0.0.1:20002"
+
+ testTimeout = 8 * time.Second
+ exporterGrace = 5 * time.Second // best-effort to allow OTLP export
+)
+
+var ins *dubbo.Instance
+
+// TestMain handles global setup/teardown once for all integration tests.
+func TestMain(m *testing.M) {
+ var err error
+
+ otel := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
+ if otel == "" {
+ otel = defaultOtelEndpoint
+ }
+
+ ins, err = dubbo.NewInstance(
+ dubbo.WithName("dubbo_otel_client_it"),
+ dubbo.WithTracing(
+ trace.WithEnabled(),
+ trace.WithOtlpHttpExporter(), // OTLP/HTTP -> /v1/traces
+ trace.WithW3cPropagator(),
+ trace.WithAlwaysMode(),
+ trace.WithEndpoint(otel),
+ trace.WithInsecure(),
+ ),
+ )
+ if err != nil {
+ fmt.Printf("init dubbo instance failed: %v\n", err)
+ os.Exit(1)
+ }
+
+ code := m.Run()
+
+ // Give exporter a short window to flush spans (best-effort).
+ time.Sleep(exporterGrace)
+ os.Exit(code)
+}
+
+func waitPort(addr string, deadline time.Duration) error {
+ end := time.Now().Add(deadline)
+ for {
+ c, err := net.DialTimeout("tcp", addr, 1*time.Second)
+ if err == nil {
+ c.Close()
+ return nil
+ }
+ if time.Now().After(end) {
+ return fmt.Errorf("port %s not ready: %w", addr, err)
+ }
+ time.Sleep(200 * time.Millisecond)
+ }
+}
+
+func TestMultiProtocols(t *testing.T) {
+
+ must := func(err error) {
+ if err != nil {
+ panic(err)
+ }
+ }
+ must(waitPort("127.0.0.1:20000", 10*time.Second)) // triple
+ must(waitPort("127.0.0.1:20001", 10*time.Second)) // dubbo(getty)
+ must(waitPort("127.0.0.1:20002", 10*time.Second)) // jsonrpc
+
+ t.Run("triple", testTriple)
+ t.Run("dubbo", testDubbo)
+ t.Run("jsonrpc", testJSONRPC)
+}
+
+// ----- Subtests -----
+
+func testTriple(t *testing.T) {
+ t.Helper()
+
+ cli, err := ins.NewClient(
+ client.WithClientProtocolTriple(),
+ client.WithClientURL(tripleAddr),
+ )
+ if err != nil {
+ t.Fatalf("create triple client: %v", err)
+ }
+
+ svc, err := greetpb.NewGreetService(cli)
+ if err != nil {
+ t.Fatalf("create greet service: %v", err)
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+ defer cancel()
+
+ resp, err := svc.Greet(ctx, &greetpb.GreetRequest{Name: "hello world"})
+ if err != nil {
+ t.Fatalf("triple call failed: %v", err)
+ }
+
+ if resp == nil || resp.Greeting == "" {
+ t.Fatalf("unexpected triple response: %#v", resp)
+ }
+ logger.Infof("[triple] Greet response: %s", resp.Greeting)
+
+ time.Sleep(exporterGrace) // allow spans to export
+}
+
+func testDubbo(t *testing.T) {
+ t.Helper()
+
+ cli, err := ins.NewClient(
+ client.WithClientProtocolDubbo(),
+ client.WithClientSerialization(constant.Hessian2Serialization),
+ client.WithClientURL(dubboAddr),
+ )
+ if err != nil {
+ t.Fatalf("create dubbo client: %v", err)
+ }
+
+ conn, err := cli.Dial("GreetProvider")
+ if err != nil {
+ t.Fatalf("dial dubbo service: %v", err)
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+ defer cancel()
+
+ var resp string
+ if err = conn.CallUnary(ctx, []interface{}{"hello", "new", "dubbo"},
&resp, "SayHello"); err != nil {
+ t.Fatalf("dubbo call failed: %v", err)
+ }
+ if resp == "" {
+ t.Fatalf("empty dubbo response")
+ }
+ logger.Infof("[dubbo] SayHello response: %s", resp)
+
+ time.Sleep(exporterGrace)
+}
+
+func testJSONRPC(t *testing.T) {
+ t.Helper()
+
+ cli, err := ins.NewClient(
+ client.WithClientProtocolJsonRPC(),
+ client.WithClientSerialization(constant.JSONSerialization),
+ client.WithClientURL(jsonRPCAddr),
+ )
+ if err != nil {
+ t.Fatalf("create jsonrpc client: %v", err)
+ }
+
+ conn, err := cli.Dial("GreetProvider")
+ if err != nil {
+ t.Fatalf("dial jsonrpc service: %v", err)
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+ defer cancel()
+
+ var resp string
+ if err = conn.CallUnary(ctx, []interface{}{"hello", "new", "jsonrpc"},
&resp, "SayHello"); err != nil {
+ t.Fatalf("jsonrpc call failed: %v", err)
+ }
+ if resp == "" {
+ t.Fatalf("empty jsonrpc response")
+ }
+ logger.Infof("[jsonrpc] SayHello response: %s", resp)
+
+ time.Sleep(exporterGrace)
+}
diff --git a/otel/tracing/otlp_http_exporter/README.md
b/otel/tracing/otlp_http_exporter/README.md
new file mode 100644
index 00000000..dbb15918
--- /dev/null
+++ b/otel/tracing/otlp_http_exporter/README.md
@@ -0,0 +1,30 @@
+# otlp http tracing exporter
+
+English | [中文](README_CN.md)
+
+The integration checks dubbo/triple/jsonrpc tracing feature, and uses
OpenTelemetry's otlp http exporter to export the tracing data.
+
+The test exports the tracing data to a local mock http server.
+
+## How to run
+
+### Run server
+
+```shell
+go run ./go-server/cmd/main.go
+```
+
+### Run test (client)
+
+```shell
+go test -tags integration -v ./tests/integration/...
+```
+
+If test success, you will see the log like below in the server terminal:
+
+```shell
+2025-09-18 16:22:29 INFO cmd/main.go:127 server count: 3, client count: 3
+```
+
+If the test fails, you will see the panic in the server terminal.
+
diff --git a/otel/tracing/otlp_http_exporter/README_CN.md
b/otel/tracing/otlp_http_exporter/README_CN.md
new file mode 100644
index 00000000..59819fcc
--- /dev/null
+++ b/otel/tracing/otlp_http_exporter/README_CN.md
@@ -0,0 +1,29 @@
+# otlp http tracing exporter
+
+[English](README.md) | 中文
+
+该集成测试检查了 dubbo/triple/jsonrpc 的链路追踪功能,并使用 OpenTelemetry 的 otlp http exporter
来导出追踪数据。
+
+测试会将追踪数据导出到本地的 mock http 服务器。
+
+## 如何运行
+
+### 启动服务端
+
+```shell
+go run ./go-server/cmd/main.go
+```
+
+### 运行测试(客户端)
+
+```shell
+go test -tags integration -v ./tests/integration/...
+```
+
+如果测试成功,你会在服务端终端看到类似如下的日志:
+
+```shell
+2025-09-18 16:22:29 INFO cmd/main.go:127 server count: 3, client count: 3
+```
+
+如果测试失败,你会在服务端终端看到 panic。
diff --git a/otel/tracing/otlp_http_exporter/go-server/cmd/main.go
b/otel/tracing/otlp_http_exporter/go-server/cmd/main.go
new file mode 100644
index 00000000..e7a69fbc
--- /dev/null
+++ b/otel/tracing/otlp_http_exporter/go-server/cmd/main.go
@@ -0,0 +1,245 @@
+/*
+ * 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 main
+
+import (
+ "compress/gzip"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "strings"
+ "time"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3"
+ "dubbo.apache.org/dubbo-go/v3/common"
+ _ "dubbo.apache.org/dubbo-go/v3/imports"
+ "dubbo.apache.org/dubbo-go/v3/otel/trace"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+ "dubbo.apache.org/dubbo-go/v3/server"
+
+ "github.com/dubbogo/gost/log/logger"
+
+ "github.com/golang/protobuf/proto"
+
+ collecttracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
+)
+
+import (
+ greet "github.com/apache/dubbo-go-samples/otel/tracing/stdout/proto"
+)
+
+type GreetMultiRPCServer struct {
+}
+
+func (srv *GreetMultiRPCServer) Greet(ctx context.Context, req
*greet.GreetRequest) (*greet.GreetResponse, error) {
+ resp := &greet.GreetResponse{Greeting: req.Name}
+ return resp, nil
+}
+
+type GreetProvider struct {
+}
+
+func (*GreetProvider) SayHello(req string, req1 string, req2 string) (string,
error) {
+ return req + " " + req1 + " " + req2, nil
+}
+
+var (
+ // triple + dubbo + jsonrpc
+ serverReceivesChan = make(chan bool, 3)
+ clientReceivesChan = make(chan bool, 3)
+ errChan = make(chan error, 6)
+)
+
+func mockOtlpReceiver() {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/v1/traces", func(w http.ResponseWriter, r
*http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "method not allowed",
http.StatusMethodNotAllowed)
+ return
+ }
+ ct := strings.ToLower(r.Header.Get("Content-Type"))
+ if ct != "" && !strings.HasPrefix(ct, "application/x-protobuf")
{
+ http.Error(w, "unsupported content type",
http.StatusUnsupportedMediaType)
+ return
+ }
+
+ body := http.MaxBytesReader(w, r.Body, 10<<20) // 10MiB
+ defer body.Close()
+
+ var reader io.Reader = body
+ if strings.EqualFold(r.Header.Get("Content-Encoding"), "gzip") {
+ gr, err := gzip.NewReader(body)
+ if err != nil {
+ select {
+ case errChan <- err:
+ default:
+ }
+ http.Error(w, err.Error(),
http.StatusBadRequest)
+ return
+ }
+ defer gr.Close()
+ reader = gr
+ }
+
+ raw, err := io.ReadAll(reader)
+ if err != nil {
+ select {
+ case errChan <- err:
+ default:
+ }
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ var req collecttracepb.ExportTraceServiceRequest
+ if err := proto.Unmarshal(raw, &req); err != nil {
+ select {
+ case errChan <- err:
+ default:
+ }
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ reqStr := req.String()
+ switch {
+ case strings.Contains(reqStr, "dubbo_otel_server"):
+ serverReceivesChan <- true
+ case strings.Contains(reqStr, "dubbo_otel_client"):
+ clientReceivesChan <- true
+ default:
+ select {
+ case errChan <- errors.New("unknown trace: " + reqStr):
+ default:
+ }
+ }
+
+ respBytes, _ :=
proto.Marshal(&collecttracepb.ExportTraceServiceResponse{})
+ w.Header().Set("Content-Type", "application/x-protobuf")
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write(respBytes)
+ })
+
+ ln, err := net.Listen("tcp", "127.0.0.1:4318")
+ if err != nil {
+ panic(fmt.Errorf("mock OTLP receiver listen failed: %w", err))
+ }
+
+ srv := &http.Server{
+ Handler: mux,
+ ReadHeaderTimeout: 5 * time.Second,
+ }
+
+ go func() {
+ logger.Infof("[mock-otlp] listening on %s", ln.Addr().String())
+ if err := srv.Serve(ln); err != nil && !errors.Is(err,
http.ErrServerClosed) {
+ select {
+ case errChan <- err:
+ default:
+ }
+ }
+ }()
+
+ deadline := time.Now().Add(5 * time.Second)
+ for {
+ conn, dErr := net.DialTimeout("tcp", "127.0.0.1:4318",
500*time.Millisecond)
+ if dErr == nil {
+ _ = conn.Close()
+ break
+ }
+ if time.Now().After(deadline) {
+ panic(fmt.Errorf("mock OTLP not ready in time: %w",
dErr))
+ }
+ time.Sleep(100 * time.Millisecond)
+ }
+}
+
+func main() {
+ mockOtlpReceiver()
+ go func() {
+ var (
+ serverCount = 0
+ clientCount = 0
+ )
+ for i := 0; i < 6; i++ {
+ select {
+ case <-serverReceivesChan:
+ serverCount++
+ case <-clientReceivesChan:
+ clientCount++
+ case err := <-errChan:
+ panic(err)
+ case <-time.After(20 * time.Second):
+ panic("timeout")
+ }
+ }
+
+ logger.Infof("server count: %d, client count: %d", serverCount,
clientCount)
+ if serverCount != 3 || clientCount != 3 {
+ panic("trace received count not match")
+ }
+ }()
+
+ ins, err := dubbo.NewInstance(
+ dubbo.WithName("dubbo_otel_server"),
+ dubbo.WithTracing(
+ trace.WithEnabled(),
+ trace.WithOtlpHttpExporter(),
+ trace.WithW3cPropagator(),
+ trace.WithAlwaysMode(),
+ trace.WithEndpoint("127.0.0.1:4318"),
+ trace.WithInsecure(),
+ ),
+ dubbo.WithProtocol(
+ protocol.WithTriple(),
+ protocol.WithPort(20000)),
+ dubbo.WithProtocol(
+ protocol.WithDubbo(),
+ protocol.WithPort(20001)),
+ dubbo.WithProtocol(
+ protocol.WithJSONRPC(),
+ protocol.WithPort(20002)),
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ srv, err := ins.NewServer()
+ if err != nil {
+ panic(err)
+ }
+
+ //Triple
+ if err = greet.RegisterGreetServiceHandler(srv,
&GreetMultiRPCServer{}); err != nil {
+ panic(err)
+ }
+
+ //Dubbo & JsonRPC
+ if err = srv.Register(&GreetProvider{}, &common.ServiceInfo{},
server.WithInterface("GreetProvider")); err != nil {
+ panic(err)
+ }
+
+ if err = srv.Serve(); err != nil {
+ logger.Error(err)
+ }
+}
diff --git a/start_integrate_test.sh b/start_integrate_test.sh
index bf2a8d28..821bcf89 100755
--- a/start_integrate_test.sh
+++ b/start_integrate_test.sh
@@ -37,6 +37,7 @@ array+=("compatibility/metrics")
# tracing
array+=("otel/tracing/stdout")
+array+=("otel/tracing/otlp_http_exporter")
# direct
array+=("compatibility/direct")