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")

Reply via email to