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

Alanxtl 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 629ce66f feat(observability): add end-to-end sample (#1128)
629ce66f is described below

commit 629ce66fb974b9e3bb018009cfbf32f6a02ccc3c
Author: Tsukikage <[email protected]>
AuthorDate: Fri Aug 7 01:09:32 2026 -0700

    feat(observability): add end-to-end sample (#1128)
    
    * feat(observability): add end-to-end observability sample
    
    * docs(observability): trim integration README scope
---
 Makefile                                           |   5 +-
 README.md                                          |   7 +-
 README_CN.md                                       |   7 +-
 integrate_test.sh                                  | 273 ++++++++++++++++++++-
 observability/integration/README.md                | 139 +++++++++++
 observability/integration/README_CN.md             | 130 ++++++++++
 observability/integration/docker-compose.yaml      |  55 +++++
 observability/integration/go-client/cmd/main.go    | 192 +++++++++++++++
 observability/integration/go-server/cmd/main.go    | 142 +++++++++++
 .../grafana/dashboards/dubbo-observability.json    |  96 ++++++++
 .../provisioning/dashboards/dashboards.yaml        |  11 +
 .../provisioning/datasources/prometheus.yaml       |  10 +
 .../internal/tracefields/tracefields.go            |  29 ++-
 .../internal/tracefields/tracefields_test.go       |  52 ++++
 .../integration/internal/verify/verify.go          |  71 ++++++
 .../integration/internal/verify/verify_test.go     |  91 +++++++
 .../integration/otel-collector-config.yaml         |  23 ++
 observability/integration/prometheus.yml           |  13 +
 .../integration}/proto/greet.pb.go                 |  31 +--
 observability/integration/proto/greet.proto        |  32 +++
 .../integration}/proto/greet.triple.go             |  18 +-
 {metrics => observability}/probe/README.md         |  10 +-
 {metrics => observability}/probe/README_CN.md      |  10 +-
 .../probe/deploy/server-deployment.yml             |   0
 .../probe/go-client/cmd/main.go                    |   0
 .../probe/go-server/Dockerfile                     |   2 +-
 .../probe/go-server/build.sh                       |   4 +-
 .../probe/go-server/cmd/main.go                    |   0
 .../prometheus_grafana/Deployment.yml              |   0
 .../prometheus_grafana/README.md                   |   8 +-
 .../prometheus_grafana/README_CN.md                |   8 +-
 .../prometheus_grafana/assert/grafana.png          | Bin
 .../prometheus_grafana/docker-compose.yml          |   0
 .../prometheus_grafana/go-client/Dockerfile        |   0
 .../prometheus_grafana/go-client/build.sh          |   0
 .../prometheus_grafana/go-client/cmd/main.go       |   0
 .../prometheus_grafana/go-server/Dockerfile        |   0
 .../prometheus_grafana/go-server/build.sh          |   0
 .../prometheus_grafana/go-server/cmd/main.go       |   0
 .../prometheus_grafana/grafana.json                |   0
 .../prometheus_grafana/prometheus_pull.yml         |   0
 .../prometheus_grafana/prometheus_push.yml         |   0
 .../prometheus_grafana/proto/greet.pb.go           |   2 +-
 .../prometheus_grafana/proto/greet.proto           |   4 +-
 .../prometheus_grafana/proto/greet.triple.go       |   0
 start_integrate_test.sh                            |   7 +-
 46 files changed, 1407 insertions(+), 75 deletions(-)

diff --git a/Makefile b/Makefile
index b0c483a3..f02c8eaa 100644
--- a/Makefile
+++ b/Makefile
@@ -78,6 +78,7 @@ $(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME): $(SOURCES)
        @mkdir -p $(OUT_DIR)
        @CGO_ENABLED=$(CGO) GOOS=$(GOOS) GOARCH=$(GOARCH) $(GO) build 
$(GCFLAGS) -ldflags=$(LDFLAGS) -o $(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME) 
$(SOURCES)
 
+# Port 4318 is owned by sample-level OpenTelemetry dependencies on 
macOS/OrbStack.
 ## start: Start the application (for server)
 .PHONY: start
 start: export DUBBO_GO_CONFIG_PATH ?= $(PROJECT_DIR)/go-server/conf/dubbogo.yml
@@ -85,7 +86,7 @@ start: build
        $(info   >  Starting application $(PROJECT_NAME), output is redirected 
to $(LOG_FILE))
        @ls $(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME) >/dev/null
        @-pkill -f "$(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME)" 2>/dev/null || true
-       @-command -v lsof >/dev/null 2>&1 && lsof -P -sTCP:LISTEN -tiTCP:20000 
-iTCP:20001 -iTCP:20002 -iTCP:20022 -iTCP:4318 -iTCP:50051 -iTCP:50052 | xargs 
-r kill -9 || true
+       @-command -v lsof >/dev/null 2>&1 && lsof -P -sTCP:LISTEN -tiTCP:20000 
-iTCP:20001 -iTCP:20002 -iTCP:20022 -iTCP:50051 -iTCP:50052 | xargs -r kill -9 
|| true
        @sleep 1
        @-cd $(PROJECT_DIR) && $(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME) > 
$(LOG_FILE) 2>&1 & echo $$! > $(PID)
        @sed 's/^/  \>  PID: /' $(PID)
@@ -98,4 +99,4 @@ stop:
        @-test -f $(PID) && kill `cat $(PID)` 2>/dev/null || true
        @-pkill -f "$(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME)" 2>/dev/null || true
        @-rm -f $(PID)
-       @-command -v lsof >/dev/null 2>&1 && lsof -P -sTCP:LISTEN -tiTCP:20000 
-iTCP:20001 -iTCP:20002 -iTCP:20022 -iTCP:4318 -iTCP:50051 -iTCP:50052 | xargs 
-r kill -9 || true
+       @-command -v lsof >/dev/null 2>&1 && lsof -P -sTCP:LISTEN -tiTCP:20000 
-iTCP:20001 -iTCP:20002 -iTCP:20022 -iTCP:50051 -iTCP:50052 | xargs -r kill -9 
|| true
diff --git a/README.md b/README.md
index d6923e27..0a22719d 100644
--- a/README.md
+++ b/README.md
@@ -47,9 +47,10 @@ Please refer to [HOWTO.md](HOWTO.md) for detailed 
instructions on running the sa
   * `logger/rolling`: Output to file.
   * `logger/custom`: Custom logger.
   * `logger/trace-integration`: Integrate OpenTelemetry trace information, 
automatically injecting trace_id, span_id and other information into logs.
-* `metrics`: Observability-related samples.
-  * `metrics/prometheus_grafana`: Shows how to collect and expose metrics from 
Dubbo-go services, supporting both Prometheus Push and Pull modes. Also 
includes the `pgw-cleaner` tool for cleaning zombie metrics in Push mode.
-  * `metrics/probe`: Demonstrates Dubbo-go Kubernetes probe endpoints 
(`/live`, `/ready`, `/startup`) and deployment usage.
+* `observability`: Observability samples for Dubbo-go.
+  * `observability/integration`: End-to-end integration-validation sample 
combining Nacos discovery, Dubbo Triple tracing, an OpenTelemetry Collector, 
Prometheus/Grafana, and trace-correlated logs.
+  * `observability/prometheus_grafana`: Shows how to collect and expose 
metrics from Dubbo-go services, supporting both Prometheus Push and Pull modes. 
Also includes the `pgw-cleaner` tool for cleaning zombie metrics in Push mode.
+  * `observability/probe`: Demonstrates Dubbo-go Kubernetes probe endpoints 
(`/live`, `/ready`, `/startup`) and deployment usage.
 * `mesh`: Proxy-based service mesh example showing how to deploy Dubbo-go 
services with Envoy on Kubernetes.
 * `online_boutique`: Microservices “online boutique” demo built with Dubbo-go.
 * `otel/tracing`: Distributed tracing examples using OpenTelemetry.
diff --git a/README_CN.md b/README_CN.md
index 841d90c8..210f39b3 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -47,9 +47,10 @@
   * `logger/rolling`:输出到文件。
   * `logger/custom`:自定义 logger。
   * `logger/trace-integration`:集成 OpenTelemetry trace 信息,自动将 trace_id、span_id 
等信息注入日志。
-* `metrics`:可观测性相关示例。
-  * `metrics/prometheus_grafana`:展示如何采集并暴露 Dubbo-go 服务指标,支持 Prometheus Push 和 
Pull 两种模式;同时包含用于清理 Push 模式僵尸指标的 `pgw-cleaner` 工具。
-  * `metrics/probe`:演示 Dubbo-go 在 Kubernetes 
场景下的探针端点(`/live`、`/ready`、`/startup`)及部署方式。
+* `observability`:Dubbo-go 可观测性相关样例。
+  * `observability/integration`:端到端可观测性集成验证样例,组合 Nacos 服务发现、Dubbo Triple 
链路追踪、OpenTelemetry Collector、Prometheus/Grafana 以及带 Trace 关联的日志。
+  * `observability/prometheus_grafana`:展示如何采集并暴露 Dubbo-go 服务指标,支持 Prometheus 
Push 和 Pull 两种模式;同时包含用于清理 Push 模式僵尸指标的 `pgw-cleaner` 工具。
+  * `observability/probe`:演示 Dubbo-go 在 Kubernetes 
场景下的探针端点(`/live`、`/ready`、`/startup`)及部署方式。
 * `mesh`:基于代理的服务网格示例,展示如何在 Kubernetes 上结合 Envoy 部署 Dubbo-go 服务。
 * `online_boutique`:基于 Dubbo-go 构建的微服务 “在线商城” 演示项目。
 * `otel/tracing`:使用 OpenTelemetry 的分布式链路追踪示例。
diff --git a/integrate_test.sh b/integrate_test.sh
index 81ef2965..43d3d81f 100755
--- a/integrate_test.sh
+++ b/integrate_test.sh
@@ -44,6 +44,14 @@ JAVA_SERVER_RUN_SH="$(find "$P_DIR" -type f -path 
'*/java-server*/run.sh' -print
 JAVA_CLIENT_RUN_SH="$(find "$P_DIR" -type f -path '*/java-client*/run.sh' 
-print -quit || true)"
 JAVA_SERVER_PID=""
 GO_AUX_PIDS=()
+SAMPLE_COMPOSE_FILE=""
+SAMPLE_COMPOSE_SERVICES=()
+DOCKER_COMPOSE_CMD=()
+
+if [ "$SAMPLE" = "observability/integration" ]; then
+  SAMPLE_COMPOSE_FILE="$P_DIR/docker-compose.yaml"
+  SAMPLE_COMPOSE_SERVICES=(jaeger otel-collector prometheus grafana)
+fi
 
 JAVA_ENABLED=true
 if { [ -n "$JAVA_SERVER_RUN_SH" ] || [ -n "$JAVA_CLIENT_RUN_SH" ]; } && ! 
command -v mvn >/dev/null 2>&1; then
@@ -77,10 +85,184 @@ cleanup() {
     kill_if_running "$server_pid"
     rm -f "$PID_FILE"
   fi
+  stop_sample_dependencies
+  if [ -n "$SAMPLE_COMPOSE_FILE" ]; then
+    wait_for_tcp_port_closed "127.0.0.1" "4318" 30 || true
+  fi
   run_make_target stop >/dev/null 2>&1 || true
 }
 trap cleanup EXIT
 
+start_sample_dependencies() {
+  if [ -z "$SAMPLE_COMPOSE_FILE" ]; then
+    return 0
+  fi
+
+  if ! wait_for_http_url 
"http://127.0.0.1:8848/nacos/v1/console/health/liveness"; 90 5; then
+    echo "Root Nacos liveness did not remain healthy on 127.0.0.1:8848"
+    return 1
+  fi
+  if ! wait_for_tcp_port "127.0.0.1" "9848" 60; then
+    echo "Root Nacos gRPC endpoint did not become ready on 127.0.0.1:9848"
+    return 1
+  fi
+
+  if docker compose version >/dev/null 2>&1; then
+    DOCKER_COMPOSE_CMD=(docker compose)
+  elif command -v docker-compose >/dev/null 2>&1; then
+    DOCKER_COMPOSE_CMD=(docker-compose)
+  else
+    echo "Docker Compose is required for sample dependencies: $SAMPLE"
+    return 1
+  fi
+
+  echo "Starting sample dependencies: ${SAMPLE_COMPOSE_SERVICES[*]}"
+  "${DOCKER_COMPOSE_CMD[@]}" -f "$SAMPLE_COMPOSE_FILE" up -d 
"${SAMPLE_COMPOSE_SERVICES[@]}"
+
+  if ! wait_for_tcp_port "127.0.0.1" "4318" 60; then
+    echo "OpenTelemetry Collector did not become ready on 127.0.0.1:4318"
+    return 1
+  fi
+  if ! wait_for_tcp_port "127.0.0.1" "9090" 60; then
+    echo "Prometheus did not become ready on 127.0.0.1:9090"
+    return 1
+  fi
+}
+
+stop_sample_dependencies() {
+  if [ -z "$SAMPLE_COMPOSE_FILE" ] || [ "${#DOCKER_COMPOSE_CMD[@]}" -eq 0 ]; 
then
+    return 0
+  fi
+
+  "${DOCKER_COMPOSE_CMD[@]}" -f "$SAMPLE_COMPOSE_FILE" stop 
"${SAMPLE_COMPOSE_SERVICES[@]}" >/dev/null 2>&1 || true
+  "${DOCKER_COMPOSE_CMD[@]}" -f "$SAMPLE_COMPOSE_FILE" rm -f 
"${SAMPLE_COMPOSE_SERVICES[@]}" >/dev/null 2>&1 || true
+}
+
+# verify_observability_semantics asserts that the telemetry pipeline actually
+# received and stored the expected signals before the sample stack is torn
+# down: Prometheus scrape targets are up and have scraped RPC metrics, Jaeger
+# holds a cross-service trace (consumer and provider spans of the same trace),
+# and Grafana reports the provisioned data source and dashboard. Any failed
+# check exits non-zero so a broken scrape target, OTLP endpoint, or dashboard
+# provisioning cannot silently pass the integration.
+verify_observability_semantics() {
+  if [ "$SAMPLE" != "observability/integration" ]; then
+    return 0
+  fi
+
+  echo "Verifying observability telemetry semantics before teardown..."
+  if ! python3 <<'PY'
+import base64
+import json
+import time
+import urllib.request
+
+PROMETHEUS = "http://127.0.0.1:9090";
+JAEGER = "http://127.0.0.1:16686";
+GRAFANA = "http://127.0.0.1:3000";
+GRAFANA_HEADERS = {
+    "Authorization": "Basic " + 
base64.b64encode(b"admin:admin").decode("ascii"),
+}
+
+
+def fetch(url, headers=None):
+    request = urllib.request.Request(url, headers=headers or {})
+    with urllib.request.urlopen(request, timeout=2) as response:
+        return response.status, response.read()
+
+
+def prometheus_targets_up():
+    _, body = fetch(PROMETHEUS + "/api/v1/targets")
+    jobs = {}
+    for target in json.loads(body)["data"].get("activeTargets", []):
+        jobs.setdefault(target["labels"].get("job"), 
[]).append(target["health"])
+    for job in ("dubbo-observability-server", "dubbo-observability-client"):
+        if "up" not in jobs.get(job, []):
+            return False
+    return True
+
+
+def prometheus_metrics_scraped():
+    _, body = fetch(PROMETHEUS + 
"/api/v1/query?query=dubbo_provider_requests_succeed_total")
+    result = json.loads(body).get("data", {}).get("result", [])
+    return any(float(series["value"][1]) > 0 for series in result)
+
+
+def jaeger_cross_service_trace():
+    _, body = fetch(JAEGER + 
"/api/traces?service=dubbo-observability-client&lookback=1h&limit=5")
+    for trace in json.loads(body).get("data", []):
+        processes = {process.get("serviceName") for process in 
trace.get("processes", {}).values()}
+        if {"dubbo-observability-client", "dubbo-observability-server"} <= 
processes:
+            return True
+    return False
+
+
+def grafana_provisioned():
+    status, _ = fetch(GRAFANA + "/api/health")
+    if status != 200:
+        return False
+    _, body = fetch(GRAFANA + "/api/datasources/uid/prometheus", 
GRAFANA_HEADERS)
+    datasource = json.loads(body)
+    if datasource.get("uid") != "prometheus" or datasource.get("type") != 
"prometheus":
+        return False
+    _, body = fetch(GRAFANA + "/api/datasources/uid/prometheus/health", 
GRAFANA_HEADERS)
+    if json.loads(body).get("status") != "OK":
+        return False
+    _, body = fetch(
+        GRAFANA + "/api/datasources/proxy/uid/prometheus/api/v1/query"
+        "?query=dubbo_provider_requests_succeed_total",
+        GRAFANA_HEADERS,
+    )
+    query_result = json.loads(body).get("data", {}).get("result", [])
+    if not any(float(series["value"][1]) > 0 for series in query_result):
+        return False
+    status, _ = fetch(
+        GRAFANA + "/api/dashboards/uid/dubbo-go-observability",
+        GRAFANA_HEADERS,
+    )
+    return status == 200
+
+
+checks = [
+    (prometheus_targets_up, "Prometheus scrape targets are up"),
+    (prometheus_metrics_scraped, "Prometheus has scraped 
dubbo_provider_requests_succeed_total"),
+    (jaeger_cross_service_trace, "Jaeger holds a consumer/provider 
cross-service trace"),
+    (grafana_provisioned, "Grafana data source and dashboard are provisioned"),
+]
+pending = checks
+last_errors = {}
+deadline = time.monotonic() + 90
+while pending and time.monotonic() < deadline:
+    next_pending = []
+    for check, description in pending:
+        try:
+            if check():
+                print("  ok: " + description)
+                continue
+        except Exception as exc:  # noqa: BLE001
+            last_errors[description] = exc
+        next_pending.append((check, description))
+    pending = next_pending
+    if pending:
+        time.sleep(2)
+
+if pending:
+    for _, description in pending:
+        last_error = last_errors.get(description)
+        suffix = " (last error: %s)" % last_error if last_error else ""
+        print("  failed: " + description + suffix)
+    print("observability semantic verification failed for: " + ", ".join(
+        description for _, description in pending
+    ))
+    raise SystemExit(1)
+PY
+  then
+    echo "Observability telemetry semantic verification failed for: $SAMPLE"
+    return 1
+  fi
+  echo "Observability telemetry semantics verified"
+}
+
 resolve_config_path() {
   local role="$1"
   local conf_dir="$P_DIR/$role/conf"
@@ -138,6 +320,79 @@ PY
   return 1
 }
 
+wait_for_tcp_port_closed() {
+  local host="$1"
+  local port="$2"
+  local timeout_seconds="$3"
+  local elapsed=0
+
+  while [ "$elapsed" -lt "$timeout_seconds" ]; do
+    if python3 - "$host" "$port" <<'PY' >/dev/null 2>&1
+import socket
+import sys
+
+host = sys.argv[1]
+port = int(sys.argv[2])
+
+for af, socktype, proto, _, sockaddr in socket.getaddrinfo(host, port, 
socket.AF_UNSPEC, socket.SOCK_STREAM):
+    sock = None
+    try:
+        sock = socket.socket(af, socktype, proto)
+        sock.settimeout(1.0)
+        sock.connect(sockaddr)
+        sys.exit(1)
+    except OSError:
+        continue
+    finally:
+        if sock is not None:
+            sock.close()
+
+sys.exit(0)
+PY
+    then
+      return 0
+    fi
+    sleep 1
+    elapsed=$((elapsed + 1))
+  done
+
+  return 1
+}
+
+wait_for_http_url() {
+  local url="$1"
+  local timeout_seconds="$2"
+  local required_successes="${3:-1}"
+  local elapsed=0
+  local successes=0
+
+  while [ "$elapsed" -lt "$timeout_seconds" ]; do
+    if python3 - "$url" <<'PY' >/dev/null 2>&1
+import sys
+import urllib.request
+
+try:
+    with urllib.request.urlopen(sys.argv[1], timeout=1) as response:
+        if response.status != 200:
+            raise RuntimeError(f"unexpected HTTP status: {response.status}")
+except Exception:
+    sys.exit(1)
+PY
+    then
+      successes=$((successes + 1))
+      if [ "$successes" -ge "$required_successes" ]; then
+        return 0
+      fi
+    else
+      successes=0
+    fi
+    sleep 1
+    elapsed=$((elapsed + 1))
+  done
+
+  return 1
+}
+
 wait_for_process_exit() {
   local pid="$1"
   local timeout_seconds="$2"
@@ -447,11 +702,27 @@ main() {
   fi
 
   start_go_server
+  start_sample_dependencies
   start_aux_go_servers
 
-  run_go_client
+  if [ "$SAMPLE" = "observability/integration" ]; then
+    if ! run_go_client; then
+      echo "Observability integration client validation failed for: $SAMPLE"
+      return 1
+    fi
+  else
+    run_go_client
+  fi
   run_java_client_if_present
 
+  if ! verify_observability_semantics; then
+    return 1
+  fi
+
+  if [ -n "$SAMPLE_COMPOSE_FILE" ]; then
+    stop_sample_dependencies
+    wait_for_tcp_port_closed "127.0.0.1" "4318" 30 || true
+  fi
   stop_go_server
 
   if start_java_server_if_present; then
diff --git a/observability/integration/README.md 
b/observability/integration/README.md
new file mode 100644
index 00000000..4ead00e8
--- /dev/null
+++ b/observability/integration/README.md
@@ -0,0 +1,139 @@
+# Dubbo Go Observability Integration Validation Sample
+
+English | [中文](README_CN.md)
+
+This is an integration-validation sample. It combines Dubbo Go's existing
+observability capabilities into one runnable flow:
+
+```text
+Nacos registry
+       |
+Dubbo Triple client -> Dubbo Triple server
+        |                    |
+        +-- Trace --> OpenTelemetry Collector --> Jaeger
+        +-- Metrics ---------------------------> Prometheus -> Grafana
+        +-- Logs ------------------------------> trace_id / span_id in stdout
+```
+
+## Layout
+
+```text
+observability/
+├── integration/                    # This end-to-end integration-validation 
sample
+│   ├── go-client/cmd/main.go       # Triple consumer and request generator
+│   ├── go-server/cmd/main.go       # Triple provider with a forced error path
+│   ├── proto/                      # Self-contained service contract and stubs
+│   ├── docker-compose.yaml         # Nacos, Collector, Jaeger, Prometheus, 
Grafana
+│   ├── otel-collector-config.yaml  # OTLP Collector pipeline to Jaeger
+│   ├── prometheus.yml              # Scrapes the two local metrics endpoints
+│   └── grafana/                    # Provisioned datasource and dashboard
+├── probe/                          # Kubernetes liveness/readiness/startup 
sample
+└── prometheus_grafana/             # Push/Pull Metrics and Grafana sample
+```
+
+## Prerequisites
+
+- Go 1.25 or newer;
+- Docker with Compose;
+- a local checkout of this repository.
+
+The Compose stack is pinned to Nacos `v2.5.2`, OpenTelemetry Collector
+`0.104.0`, Jaeger `1.57`, Prometheus `v2.55.1`, and Grafana `11.2.0` so that
+the validation can be reproduced with a known toolchain.
+
+For the repository-standard integration path, start the root dependencies and
+run this sample through the root harness. The harness uses the root Makefile to
+build and stop `go-server/cmd`, runs `go-client/cmd`, and starts the additional
+observability services defined by this sample:
+
+```bash
+docker compose -f docker-compose.yml up -d
+./integrate_test.sh observability/integration
+```
+
+The sample is also included in `start_integrate_test.sh`.
+
+## Run the sample
+
+Start Nacos, the OpenTelemetry Collector, Jaeger, Prometheus, and Grafana:
+
+```bash
+cd observability/integration
+docker compose up -d
+cd ../..
+```
+
+Wait for Nacos to become healthy before starting the applications:
+
+```bash
+docker compose -f observability/integration/docker-compose.yaml ps
+```
+
+The provider registers itself in Nacos and the consumer discovers it from the
+registry. The default registry address is `127.0.0.1:8848`; override it with
+`DUBBO_OBSERVABILITY_REGISTRY_ADDRESS` when the registry is elsewhere.
+
+Grafana is published on the standard host port `3000`. If port `3000` is
+already occupied in your local environment, stop the conflicting process; the
+sample itself keeps the repository-wide standard port mapping.
+
+In a second terminal, start the Dubbo provider:
+
+```bash
+go run ./observability/integration/go-server/cmd
+```
+
+In a third terminal, run the client. Every fifth request intentionally uses the
+name `error` so that the failure path can be inspected in Metrics, Trace, and
+logs:
+
+```bash
+go run ./observability/integration/go-client/cmd -requests 20 -interval 500ms
+```
+
+To keep generating traffic for dashboard exploration, pass `-requests 0`:
+
+```bash
+go run ./observability/integration/go-client/cmd -requests 0 -interval 500ms
+```
+
+To reproduce a timeout, set a shorter per-request deadline than the provider's
+intentional 20 ms processing delay:
+
+```bash
+go run ./observability/integration/go-client/cmd -requests 5 -timeout 1ms
+```
+
+To reproduce an explicit cancellation rather than a deadline expiry:
+
+```bash
+go run ./observability/integration/go-client/cmd -requests 5 -cancel-after 1ms
+```
+
+The default client run is finite so that it can be used by the repository
+integration harness. Pass `-requests 0` when continuous traffic is needed for
+dashboard exploration.
+
+Stop the client with `Ctrl-C`, stop the server with `Ctrl-C`, and remove the
+monitoring stack when finished:
+
+```bash
+docker compose -f observability/integration/docker-compose.yaml down
+```
+
+## Inspect the signals
+
+- Jaeger: <http://localhost:16686>
+- Nacos: <http://localhost:8848/nacos>
+- Prometheus: <http://localhost:9090>
+- Grafana: <http://localhost:3000> (`admin` / `admin`)
+- Provider metrics: <http://localhost:9099/prometheus>
+- Consumer metrics: <http://localhost:9098/prometheus>
+
+Grafana provisions the Prometheus datasource and the `Dubbo Go Observability`
+dashboard automatically. The application logs remain in the provider and
+client terminals; each application log reads the active OpenTelemetry
+`SpanContext` and includes the same `trace_id` and `span_id` that can be found
+in Jaeger. This sample deliberately uses the published module API. The
+Dubbo-Go `CtxLogger` integration is covered by the dedicated
+`logger/trace-integration` sample and is not reimplemented here.
diff --git a/observability/integration/README_CN.md 
b/observability/integration/README_CN.md
new file mode 100644
index 00000000..8278041b
--- /dev/null
+++ b/observability/integration/README_CN.md
@@ -0,0 +1,130 @@
+# Dubbo Go 可观测性集成验证样例
+
+[English](README.md) | 中文
+
+这个样例是一个集成验证样例,把 Dubbo Go 现有的可观测性能力组合成一条可运行链路:
+
+```text
+Nacos 注册中心
+       |
+Dubbo Triple 客户端 -> Dubbo Triple 服务端
+        |                    |
+        +-- Trace --> OpenTelemetry Collector --> Jaeger
+        +-- Metrics ---------------------------> Prometheus -> Grafana
+        +-- 日志 ------------------------------> stdout 中的 trace_id / span_id
+```
+
+## 目录结构
+
+```text
+observability/
+├── integration/                    # 本端到端集成验证样例
+│   ├── go-client/cmd/main.go       # Triple 消费端和请求生成器
+│   ├── go-server/cmd/main.go       # Triple 提供端,包含可复现错误路径
+│   ├── proto/                      # 独立的服务契约和生成代码
+│   ├── docker-compose.yaml         # Nacos、Collector、Jaeger、Prometheus、Grafana
+│   ├── otel-collector-config.yaml  # 转发到 Jaeger 的 OTLP Collector 管道
+│   ├── prometheus.yml              # 抓取两个本地 Metrics endpoint
+│   └── grafana/                    # 自动配置数据源和 Dashboard
+├── probe/                          # Kubernetes 存活、就绪和启动探针样例
+└── prometheus_grafana/             # Push/Pull Metrics 和 Grafana 样例
+```
+
+## 前置条件
+
+- Go 1.25 或更新版本;
+- Docker 和 Compose;
+- 本仓库的本地 checkout。
+
+Compose 中固定了 Nacos `v2.5.2`、OpenTelemetry Collector `0.104.0`、Jaeger
+`1.57`、Prometheus `v2.55.1` 和 Grafana `11.2.0`,以便使用确定的工具链复现
+这条验证链路。
+
+按仓库标准进行集成验证时,先启动根目录依赖,再使用根级集成测试入口。该入口会
+通过根目录 Makefile 构建和停止 `go-server/cmd`,运行 `go-client/cmd`,并启动本样例
+额外声明的可观测性服务:
+
+```bash
+docker compose -f docker-compose.yml up -d
+./integrate_test.sh observability/integration
+```
+
+本样例也已经加入 `start_integrate_test.sh` 的集成测试列表。
+
+## 启动样例
+
+启动 Nacos、OpenTelemetry Collector、Jaeger、Prometheus 和 Grafana:
+
+```bash
+cd observability/integration
+docker compose up -d
+cd ../..
+```
+
+启动应用前,先等待 Nacos 健康:
+
+```bash
+docker compose -f observability/integration/docker-compose.yaml ps
+```
+
+服务端会注册到 Nacos,客户端从 Nacos 发现服务。默认注册中心地址是
+`127.0.0.1:8848`;如果注册中心在其他主机,可以通过
+`DUBBO_OBSERVABILITY_REGISTRY_ADDRESS` 覆盖。
+
+Grafana 默认映射到标准宿主机 `3000` 端口。如果你的本地环境已经占用
+`3000`,请停止冲突进程;样例本身保持仓库统一的标准端口映射。
+
+在第二个终端启动 Dubbo 服务端:
+
+```bash
+go run ./observability/integration/go-server/cmd
+```
+
+在第三个终端启动客户端。客户端每 5 个请求会故意发送一个 `error` 请求,
+用于在 Metrics、Trace 和日志中检查失败路径:
+
+```bash
+go run ./observability/integration/go-client/cmd -requests 20 -interval 500ms
+```
+
+如果希望持续产生流量以观察 Dashboard,请传入 `-requests 0`:
+
+```bash
+go run ./observability/integration/go-client/cmd -requests 0 -interval 500ms
+```
+
+如果要复现超时,可以设置一个小于服务端故意延迟 20 毫秒的请求超时:
+
+```bash
+go run ./observability/integration/go-client/cmd -requests 5 -timeout 1ms
+```
+
+如果要复现显式取消而不是 deadline 到期:
+
+```bash
+go run ./observability/integration/go-client/cmd -requests 5 -cancel-after 1ms
+```
+
+客户端默认执行有限次数,这样可以直接接入仓库集成测试;如果希望持续产生流量观察
+Dashboard,请显式传入 `-requests 0`。
+
+测试完成后使用 `Ctrl-C` 停止客户端和服务端,再回收监控栈:
+
+```bash
+docker compose -f observability/integration/docker-compose.yaml down
+```
+
+## 查看信号
+
+- Jaeger:<http://localhost:16686>
+- Nacos:<http://localhost:8848/nacos>
+- Prometheus:<http://localhost:9090>
+- Grafana:<http://localhost:3000>(`admin` / `admin`)
+- 服务端 Metrics:<http://localhost:9099/prometheus>
+- 客户端 Metrics:<http://localhost:9098/prometheus>
+
+Grafana 会自动配置 Prometheus 数据源和 `Dubbo Go Observability` Dashboard。
+应用日志保留在客户端和服务端终端中;每条应用日志都会读取当前 OpenTelemetry
+`SpanContext`,并包含和 Jaeger 中相同的 `trace_id`、`span_id`。这个样例刻意只使用
+已发布的 module API;Dubbo-Go 的 `CtxLogger` 集成由专门的
+`logger/trace-integration` 样例覆盖,这里不重复实现。
diff --git a/observability/integration/docker-compose.yaml 
b/observability/integration/docker-compose.yaml
new file mode 100644
index 00000000..ef70f80d
--- /dev/null
+++ b/observability/integration/docker-compose.yaml
@@ -0,0 +1,55 @@
+services:
+  nacos:
+    image: nacos/nacos-server:v2.5.2
+    environment:
+      PREFER_HOST_MODE: hostname
+      MODE: standalone
+    ports:
+      - "8848:8848"
+      - "9848:9848"
+      - "9849:9849"
+    healthcheck:
+      test: ["CMD", "curl", "-fsS", 
"http://localhost:8848/nacos/v1/console/health/liveness";]
+      interval: 7s
+      timeout: 5s
+      retries: 10
+      start_period: 30s
+
+  jaeger:
+    image: jaegertracing/all-in-one:1.57
+    environment:
+      COLLECTOR_OTLP_ENABLED: "true"
+    ports:
+      - "16686:16686"
+
+  otel-collector:
+    image: otel/opentelemetry-collector-contrib:0.104.0
+    command: ["--config=/etc/otelcol-contrib/config.yaml"]
+    depends_on:
+      - jaeger
+    volumes:
+      - ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro
+    ports:
+      - "4318:4318"
+
+  prometheus:
+    image: prom/prometheus:v2.55.1
+    extra_hosts:
+      - "host.docker.internal:host-gateway"
+    volumes:
+      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
+    ports:
+      - "9090:9090"
+
+  grafana:
+    image: grafana/grafana:11.2.0
+    environment:
+      GF_SECURITY_ADMIN_USER: admin
+      GF_SECURITY_ADMIN_PASSWORD: admin
+    depends_on:
+      - prometheus
+    volumes:
+      - ./grafana/provisioning:/etc/grafana/provisioning:ro
+      - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
+    ports:
+      - "3000:3000"
diff --git a/observability/integration/go-client/cmd/main.go 
b/observability/integration/go-client/cmd/main.go
new file mode 100644
index 00000000..7b9df779
--- /dev/null
+++ b/observability/integration/go-client/cmd/main.go
@@ -0,0 +1,192 @@
+/*
+ * 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 (
+       "context"
+       "flag"
+       "fmt"
+       "os"
+       "strconv"
+       "time"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3"
+       _ "dubbo.apache.org/dubbo-go/v3/imports"
+       dubbolog "dubbo.apache.org/dubbo-go/v3/logger"
+       "dubbo.apache.org/dubbo-go/v3/metrics"
+       "dubbo.apache.org/dubbo-go/v3/otel/trace"
+       "dubbo.apache.org/dubbo-go/v3/registry"
+
+       "github.com/dubbogo/gost/log/logger"
+
+       "go.opentelemetry.io/otel"
+       "go.opentelemetry.io/otel/codes"
+       sdktrace "go.opentelemetry.io/otel/sdk/trace"
+)
+
+import (
+       
"github.com/apache/dubbo-go-samples/observability/integration/internal/tracefields"
+       
"github.com/apache/dubbo-go-samples/observability/integration/internal/verify"
+       observability 
"github.com/apache/dubbo-go-samples/observability/integration/proto"
+)
+
+var (
+       requests    = flag.Int("requests", 5, "number of requests to send; 0 
runs until interrupted")
+       interval    = flag.Duration("interval", time.Second, "delay between 
requests")
+       timeout     = flag.Duration("timeout", 0, "per-request timeout; 0 
disables the timeout")
+       cancelAfter = flag.Duration("cancel-after", 0, "cancel each request 
after this duration; 0 disables cancellation")
+)
+
+func traceOptions() []trace.Option {
+       return []trace.Option{
+               trace.WithEnabled(),
+               trace.WithOtlpHttpExporter(),
+               trace.WithW3cPropagator(),
+               trace.WithAlwaysMode(),
+               trace.WithEndpoint(getEnv("DUBBO_OBSERVABILITY_OTLP_ENDPOINT", 
"127.0.0.1:4318")),
+               trace.WithInsecure(),
+       }
+}
+
+func metricsOptions() []metrics.Option {
+       port, err := strconv.Atoi(getEnv("DUBBO_OBSERVABILITY_METRICS_PORT", 
"9098"))
+       if err != nil {
+               port = 9098
+       }
+       return []metrics.Option{
+               metrics.WithEnabled(),
+               metrics.WithPrometheus(),
+               metrics.WithPrometheusExporterEnabled(),
+               metrics.WithRegistryEnabled(),
+               metrics.WithMetadataEnabled(),
+               metrics.WithPort(port),
+               metrics.WithPath("/prometheus"),
+       }
+}
+
+func registryOptions() []registry.Option {
+       return []registry.Option{
+               registry.WithNacos(),
+               
registry.WithAddress(getEnv("DUBBO_OBSERVABILITY_REGISTRY_ADDRESS", 
"127.0.0.1:8848")),
+       }
+}
+
+func getEnv(key, fallback string) string {
+       if value := os.Getenv(key); value != "" {
+               return value
+       }
+       return fallback
+}
+
+func main() {
+       flag.Parse()
+
+       ins, err := dubbo.NewInstance(
+               dubbo.WithName("dubbo-observability-client"),
+               dubbo.WithRegistry(registryOptions()...),
+               dubbo.WithTracing(traceOptions()...),
+               dubbo.WithMetrics(metricsOptions()...),
+               dubbo.WithLogger(
+                       dubbolog.WithZap(),
+                       dubbolog.WithLevel("debug"),
+               ),
+       )
+       if err != nil {
+               panic(err)
+       }
+
+       cli, err := ins.NewClient()
+       if err != nil {
+               panic(err)
+       }
+
+       svc, err := observability.NewGreetService(cli)
+       if err != nil {
+               panic(err)
+       }
+
+       tracer := otel.Tracer("dubbo-observability-client")
+       strict := *timeout == 0 && *cancelAfter == 0
+       contractViolations := 0
+
+       for i := 1; *requests == 0 || i <= *requests; i++ {
+               requestCtx := context.Background()
+               cancel := context.CancelFunc(func() {})
+               if *timeout > 0 {
+                       requestCtx, cancel = context.WithTimeout(requestCtx, 
*timeout)
+               }
+               var cancelTimer *time.Timer
+               if *cancelAfter > 0 {
+                       parentCancel := cancel
+                       var cancelRequest context.CancelFunc
+                       requestCtx, cancelRequest = 
context.WithCancel(requestCtx)
+                       cancel = func() {
+                               cancelRequest()
+                               parentCancel()
+                       }
+                       cancelTimer = time.AfterFunc(*cancelAfter, 
cancelRequest)
+               }
+               ctx, span := tracer.Start(requestCtx, "observability.request")
+               name := fmt.Sprintf("request-%d", i)
+               if i%5 == 0 {
+                       name = "error"
+               }
+               logger.Infof("sending greet request: name=%s%s", name, 
tracefields.Fields(ctx))
+
+               resp, callErr := svc.Greet(ctx, 
&observability.GreetRequest{Name: name})
+               if strict {
+                       if err := verify.GreetRequestExpected(name, resp, 
callErr); err != nil {
+                               contractViolations++
+                               if callErr != nil {
+                                       span.RecordError(callErr)
+                               }
+                               span.SetStatus(codes.Error, err.Error())
+                               logger.Errorf("greet request contract 
violation: %v%s", err, tracefields.Fields(ctx))
+                       } else if callErr == nil {
+                               logger.Infof("greet response: %s%s", 
resp.GetGreeting(), tracefields.Fields(ctx))
+                       } else {
+                               logger.Infof("forced error observed as 
expected: %v%s", callErr, tracefields.Fields(ctx))
+                       }
+               } else if callErr != nil {
+                       span.RecordError(callErr)
+                       span.SetStatus(codes.Error, callErr.Error())
+                       logger.Errorf("greet request failed: %v%s", callErr, 
tracefields.Fields(ctx))
+               } else {
+                       logger.Infof("greet response: %s%s", 
resp.GetGreeting(), tracefields.Fields(ctx))
+               }
+               if cancelTimer != nil {
+                       cancelTimer.Stop()
+               }
+               span.End()
+               cancel()
+
+               if *requests != 0 && i == *requests {
+                       break
+               }
+               time.Sleep(*interval)
+       }
+
+       if provider, ok := otel.GetTracerProvider().(*sdktrace.TracerProvider); 
ok {
+               _ = provider.Shutdown(context.Background())
+       }
+       if contractViolations > 0 {
+               logger.Fatalf("observability integration observed %d contract 
violations", contractViolations)
+       }
+}
diff --git a/observability/integration/go-server/cmd/main.go 
b/observability/integration/go-server/cmd/main.go
new file mode 100644
index 00000000..5c69e497
--- /dev/null
+++ b/observability/integration/go-server/cmd/main.go
@@ -0,0 +1,142 @@
+/*
+ * 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 (
+       "context"
+       "errors"
+       "os"
+       "strconv"
+       "time"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3"
+       _ "dubbo.apache.org/dubbo-go/v3/imports"
+       dubbolog "dubbo.apache.org/dubbo-go/v3/logger"
+       "dubbo.apache.org/dubbo-go/v3/metrics"
+       "dubbo.apache.org/dubbo-go/v3/otel/trace"
+       "dubbo.apache.org/dubbo-go/v3/protocol"
+       "dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
+       "dubbo.apache.org/dubbo-go/v3/registry"
+
+       "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+       
"github.com/apache/dubbo-go-samples/observability/integration/internal/tracefields"
+       observability 
"github.com/apache/dubbo-go-samples/observability/integration/proto"
+)
+
+const (
+       serverPort        = 20000
+       serverMetricsPort = 9099
+)
+
+type GreetServer struct{}
+
+func (s *GreetServer) Greet(ctx context.Context, req 
*observability.GreetRequest) (*observability.GreetResponse, error) {
+       if req == nil {
+               err := errors.New("observability sample received nil request")
+               logger.Errorf("greet request failed: %v%s", err, 
tracefields.Fields(ctx))
+               return nil, err
+       }
+
+       logger.Infof("greet request received: name=%s%s", req.GetName(), 
tracefields.Fields(ctx))
+
+       if req.GetName() == "error" {
+               err := triple_protocol.NewError(triple_protocol.CodeBizError, 
errors.New("observability sample forced error"))
+               logger.Errorf("observability sample forced error: %v%s", err, 
tracefields.Fields(ctx))
+               return nil, err
+       }
+
+       time.Sleep(20 * time.Millisecond)
+       logger.Infof("greet request completed%s", tracefields.Fields(ctx))
+       return &observability.GreetResponse{Greeting: "hello " + 
req.GetName()}, nil
+}
+
+func traceOptions() []trace.Option {
+       return []trace.Option{
+               trace.WithEnabled(),
+               trace.WithOtlpHttpExporter(),
+               trace.WithW3cPropagator(),
+               trace.WithAlwaysMode(),
+               trace.WithEndpoint(getEnv("DUBBO_OBSERVABILITY_OTLP_ENDPOINT", 
"127.0.0.1:4318")),
+               trace.WithInsecure(),
+       }
+}
+
+func metricsOptions() []metrics.Option {
+       port, err := strconv.Atoi(getEnv("DUBBO_OBSERVABILITY_METRICS_PORT", 
strconv.Itoa(serverMetricsPort)))
+       if err != nil {
+               port = serverMetricsPort
+       }
+       return []metrics.Option{
+               metrics.WithEnabled(),
+               metrics.WithPrometheus(),
+               metrics.WithPrometheusExporterEnabled(),
+               metrics.WithRegistryEnabled(),
+               metrics.WithMetadataEnabled(),
+               metrics.WithPort(port),
+               metrics.WithPath("/prometheus"),
+       }
+}
+
+func registryOptions() []registry.Option {
+       return []registry.Option{
+               registry.WithNacos(),
+               
registry.WithAddress(getEnv("DUBBO_OBSERVABILITY_REGISTRY_ADDRESS", 
"127.0.0.1:8848")),
+       }
+}
+
+func getEnv(key, fallback string) string {
+       if value := os.Getenv(key); value != "" {
+               return value
+       }
+       return fallback
+}
+
+func main() {
+       ins, err := dubbo.NewInstance(
+               dubbo.WithName("dubbo-observability-server"),
+               dubbo.WithRegistry(registryOptions()...),
+               dubbo.WithProtocol(protocol.WithPort(serverPort), 
protocol.WithTriple()),
+               dubbo.WithTracing(traceOptions()...),
+               dubbo.WithMetrics(metricsOptions()...),
+               dubbo.WithLogger(
+                       dubbolog.WithZap(),
+                       dubbolog.WithLevel("debug"),
+               ),
+       )
+       if err != nil {
+               panic(err)
+       }
+
+       srv, err := ins.NewServer()
+       if err != nil {
+               panic(err)
+       }
+
+       if err := observability.RegisterGreetServiceHandler(srv, 
&GreetServer{}); err != nil {
+               panic(err)
+       }
+
+       if err := srv.Serve(); err != nil {
+               logger.Fatalf("server serve failed: %v", err)
+       }
+}
diff --git 
a/observability/integration/grafana/dashboards/dubbo-observability.json 
b/observability/integration/grafana/dashboards/dubbo-observability.json
new file mode 100644
index 00000000..c860f8c7
--- /dev/null
+++ b/observability/integration/grafana/dashboards/dubbo-observability.json
@@ -0,0 +1,96 @@
+{
+  "annotations": {"list": []},
+  "editable": true,
+  "fiscalYearStartMonth": 0,
+  "graphTooltip": 0,
+  "links": [],
+  "panels": [
+    {
+      "datasource": {"type": "prometheus", "uid": "prometheus"},
+      "fieldConfig": {"defaults": {"unit": "reqps"}, "overrides": []},
+      "gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
+      "id": 1,
+      "options": {"legend": {"displayMode": "list", "placement": "bottom"}, 
"tooltip": {"mode": "multi"}},
+      "targets": [
+        {"expr": "sum(rate(dubbo_provider_requests_succeed_total[1m]))", 
"legendFormat": "provider success", "refId": "A"},
+        {"expr": "sum(rate(dubbo_consumer_requests_succeed_total[1m]))", 
"legendFormat": "consumer success", "refId": "B"}
+      ],
+      "title": "Successful RPC requests",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {"type": "prometheus", "uid": "prometheus"},
+      "fieldConfig": {"defaults": {"unit": "ms"}, "overrides": []},
+      "gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
+      "id": 2,
+      "options": {"legend": {"displayMode": "list", "placement": "bottom"}, 
"tooltip": {"mode": "multi"}},
+      "targets": [
+        {"expr": "avg(dubbo_provider_rt_milliseconds_p95)", "legendFormat": 
"provider p95", "refId": "A"}
+      ],
+      "title": "Provider latency p95",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {"type": "prometheus", "uid": "prometheus"},
+      "fieldConfig": {"defaults": {"unit": "reqps"}, "overrides": []},
+      "gridPos": {"h": 8, "w": 12, "x": 0, "y": 8},
+      "id": 3,
+      "options": {"legend": {"displayMode": "list", "placement": "bottom"}, 
"tooltip": {"mode": "multi"}},
+      "targets": [
+        {"expr": 
"sum(rate(dubbo_provider_requests_business_failed_total[1m]))", "legendFormat": 
"provider business failures", "refId": "A"},
+        {"expr": 
"sum(rate(dubbo_consumer_requests_business_failed_total[1m]))", "legendFormat": 
"consumer business failures", "refId": "B"}
+      ],
+      "title": "Business failures",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {"type": "prometheus", "uid": "prometheus"},
+      "fieldConfig": {"defaults": {"unit": "short"}, "overrides": []},
+      "gridPos": {"h": 8, "w": 12, "x": 12, "y": 8},
+      "id": 4,
+      "options": {"legend": {"displayMode": "list", "placement": "bottom"}, 
"tooltip": {"mode": "multi"}},
+      "targets": [
+        {"expr": "sum(dubbo_provider_requests_processing_total)", 
"legendFormat": "provider in-flight", "refId": "A"},
+        {"expr": "sum(dubbo_consumer_requests_processing_total)", 
"legendFormat": "consumer in-flight", "refId": "B"}
+      ],
+      "title": "In-flight RPC requests",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {"type": "prometheus", "uid": "prometheus"},
+      "fieldConfig": {"defaults": {"unit": "reqps"}, "overrides": []},
+      "gridPos": {"h": 8, "w": 12, "x": 0, "y": 16},
+      "id": 5,
+      "options": {"legend": {"displayMode": "list", "placement": "bottom"}, 
"tooltip": {"mode": "multi"}},
+      "targets": [
+        {"expr": 
"sum(rate(dubbo_registry_register_requests_failed_total[1m]))", "legendFormat": 
"registry register failures", "refId": "A"},
+        {"expr": "sum(rate(dubbo_registry_subscribe_num_failed_total[1m]))", 
"legendFormat": "registry subscribe failures", "refId": "B"}
+      ],
+      "title": "Registry failures",
+      "type": "timeseries"
+    },
+    {
+      "datasource": {"type": "prometheus", "uid": "prometheus"},
+      "fieldConfig": {"defaults": {"unit": "reqps"}, "overrides": []},
+      "gridPos": {"h": 8, "w": 12, "x": 12, "y": 16},
+      "id": 6,
+      "options": {"legend": {"displayMode": "list", "placement": "bottom"}, 
"tooltip": {"mode": "multi"}},
+      "targets": [
+        {"expr": 
"sum(rate(dubbo_provider_requests_failed_service_unavailable_total[1m]))", 
"legendFormat": "provider unavailable", "refId": "A"},
+        {"expr": 
"sum(rate(dubbo_consumer_requests_failed_service_unavailable_total[1m]))", 
"legendFormat": "consumer unavailable", "refId": "B"}
+      ],
+      "title": "Service unavailable failures",
+      "type": "timeseries"
+    }
+  ],
+  "refresh": "5s",
+  "schemaVersion": 39,
+  "tags": ["dubbo-go", "observability"],
+  "templating": {"list": []},
+  "time": {"from": "now-15m", "to": "now"},
+  "timepicker": {},
+  "timezone": "browser",
+  "title": "Dubbo Go Observability",
+  "uid": "dubbo-go-observability",
+  "version": 1
+}
diff --git 
a/observability/integration/grafana/provisioning/dashboards/dashboards.yaml 
b/observability/integration/grafana/provisioning/dashboards/dashboards.yaml
new file mode 100644
index 00000000..f788de07
--- /dev/null
+++ b/observability/integration/grafana/provisioning/dashboards/dashboards.yaml
@@ -0,0 +1,11 @@
+apiVersion: 1
+
+providers:
+  - name: Dubbo Go observability
+    orgId: 1
+    folder: Dubbo Go
+    type: file
+    disableDeletion: true
+    editable: true
+    options:
+      path: /var/lib/grafana/dashboards
diff --git 
a/observability/integration/grafana/provisioning/datasources/prometheus.yaml 
b/observability/integration/grafana/provisioning/datasources/prometheus.yaml
new file mode 100644
index 00000000..0b304bc9
--- /dev/null
+++ b/observability/integration/grafana/provisioning/datasources/prometheus.yaml
@@ -0,0 +1,10 @@
+apiVersion: 1
+
+datasources:
+  - name: Prometheus
+    uid: prometheus
+    type: prometheus
+    access: proxy
+    url: http://prometheus:9090
+    isDefault: true
+    editable: true
diff --git a/metrics/prometheus_grafana/proto/greet.proto 
b/observability/integration/internal/tracefields/tracefields.go
similarity index 63%
copy from metrics/prometheus_grafana/proto/greet.proto
copy to observability/integration/internal/tracefields/tracefields.go
index 9546a73e..73d4b3a6 100644
--- a/metrics/prometheus_grafana/proto/greet.proto
+++ b/observability/integration/internal/tracefields/tracefields.go
@@ -15,20 +15,23 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
+package tracefields
 
-package greet;
+import (
+       "context"
+       "fmt"
+)
 
-option go_package = 
"github.com/apache/dubbo-go-samples/metrics/prometheus_grafana/proto;greet";
+import (
+       oteltrace "go.opentelemetry.io/otel/trace"
+)
 
-message GreetRequest {
-  string name = 1;
+// Fields returns trace identifiers from the active OpenTelemetry context in a
+// log-friendly format.
+func Fields(ctx context.Context) string {
+       spanContext := oteltrace.SpanContextFromContext(ctx)
+       if !spanContext.IsValid() {
+               return " trace_id=- span_id=-"
+       }
+       return fmt.Sprintf(" trace_id=%s span_id=%s", spanContext.TraceID(), 
spanContext.SpanID())
 }
-
-message GreetResponse {
-  string greeting = 1;
-}
-
-service GreetService {
-  rpc Greet(GreetRequest) returns (GreetResponse) {}
-}
\ No newline at end of file
diff --git a/observability/integration/internal/tracefields/tracefields_test.go 
b/observability/integration/internal/tracefields/tracefields_test.go
new file mode 100644
index 00000000..ec2758b9
--- /dev/null
+++ b/observability/integration/internal/tracefields/tracefields_test.go
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tracefields
+
+import (
+       "context"
+       "strings"
+       "testing"
+)
+
+import (
+       "go.opentelemetry.io/otel/trace"
+)
+
+func TestFieldsWithoutSpan(t *testing.T) {
+       if got := Fields(context.Background()); got != " trace_id=- span_id=-" {
+               t.Fatalf("Fields() without span = %q, want empty identifiers", 
got)
+       }
+}
+
+func TestFieldsWithSpan(t *testing.T) {
+       traceID := trace.TraceID{1, 2, 3}
+       spanID := trace.SpanID{4, 5, 6}
+       ctx := trace.ContextWithSpanContext(context.Background(), 
trace.NewSpanContext(trace.SpanContextConfig{
+               TraceID:    traceID,
+               SpanID:     spanID,
+               TraceFlags: trace.FlagsSampled,
+       }))
+
+       got := Fields(ctx)
+       if !strings.Contains(got, "trace_id="+traceID.String()) {
+               t.Fatalf("Fields() = %q, missing trace ID", got)
+       }
+       if !strings.Contains(got, "span_id="+spanID.String()) {
+               t.Fatalf("Fields() = %q, missing span ID", got)
+       }
+}
diff --git a/observability/integration/internal/verify/verify.go 
b/observability/integration/internal/verify/verify.go
new file mode 100644
index 00000000..18d8af99
--- /dev/null
+++ b/observability/integration/internal/verify/verify.go
@@ -0,0 +1,71 @@
+/*
+ * 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 verify validates that a Greet call outcome matches the business
+// contract of the observability integration sample. The validation is
+// deterministic so that a provider regression - a wrong or empty greeting,
+// a removed error branch, or a forced-error request that silently succeeds -
+// fails the client run instead of being accepted.
+package verify
+
+import (
+       "errors"
+       "fmt"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
+)
+
+import (
+       observability 
"github.com/apache/dubbo-go-samples/observability/integration/proto"
+)
+
+// GreetRequestExpected reports how the observed Greet outcome deviates from
+// the sample contract. A request with name "error" must fail with a typed
+// CodeBizError business error; any other name must succeed and return the
+// exact greeting "hello <name>". It returns nil when the outcome matches.
+func GreetRequestExpected(name string, resp *observability.GreetResponse, 
callErr error) error {
+       if name == "error" {
+               return forcedErrorExpected(callErr)
+       }
+       return successExpected(name, resp, callErr)
+}
+
+func successExpected(name string, resp *observability.GreetResponse, callErr 
error) error {
+       if callErr != nil {
+               return fmt.Errorf("unexpected error for request %q: %w", name, 
callErr)
+       }
+       if resp == nil {
+               return fmt.Errorf("nil response for request %q", name)
+       }
+       want := "hello " + name
+       if got := resp.GetGreeting(); got != want {
+               return fmt.Errorf("unexpected greeting for request %q: got %q, 
want %q", name, got, want)
+       }
+       return nil
+}
+
+func forcedErrorExpected(callErr error) error {
+       if callErr == nil {
+               return errors.New(`request with name "error" succeeded; 
expected the forced business error`)
+       }
+       if code := triple_protocol.CodeOf(callErr); code != 
triple_protocol.CodeBizError {
+               return fmt.Errorf("forced error request reported code %s (%v), 
want %s", code, callErr, triple_protocol.CodeBizError)
+       }
+       return nil
+}
diff --git a/observability/integration/internal/verify/verify_test.go 
b/observability/integration/internal/verify/verify_test.go
new file mode 100644
index 00000000..5fe2d441
--- /dev/null
+++ b/observability/integration/internal/verify/verify_test.go
@@ -0,0 +1,91 @@
+/*
+ * 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 verify
+
+import (
+       "errors"
+       "strings"
+       "testing"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
+)
+
+import (
+       observability 
"github.com/apache/dubbo-go-samples/observability/integration/proto"
+)
+
+func TestGreetRequestExpectedSuccess(t *testing.T) {
+       resp := &observability.GreetResponse{Greeting: "hello alice"}
+       if err := GreetRequestExpected("alice", resp, nil); err != nil {
+               t.Fatalf("expected success, got %v", err)
+       }
+}
+
+func TestGreetRequestExpectedNilResponse(t *testing.T) {
+       if err := GreetRequestExpected("alice", nil, nil); err == nil {
+               t.Fatal("nil response must be rejected")
+       }
+}
+
+// Mutation: the provider returns an empty or wrong greeting.
+func TestGreetRequestExpectedWrongGreeting(t *testing.T) {
+       for _, greeting := range []string{"", "hello bob", "HELLO alice"} {
+               resp := &observability.GreetResponse{Greeting: greeting}
+               if err := GreetRequestExpected("alice", resp, nil); err == nil {
+                       t.Fatalf("greeting %q must be rejected for request %q", 
greeting, "alice")
+               }
+       }
+}
+
+// Mutation: the provider fails an otherwise successful request.
+func TestGreetRequestExpectedUnexpectedError(t *testing.T) {
+       err := errors.New("boom")
+       got := GreetRequestExpected("alice", nil, err)
+       if got == nil {
+               t.Fatal("unexpected RPC error must be rejected")
+       }
+       if !strings.Contains(got.Error(), "boom") {
+               t.Fatalf("rejection must preserve the original cause, got %v", 
got)
+       }
+}
+
+func TestGreetRequestExpectedForcedError(t *testing.T) {
+       err := triple_protocol.NewError(triple_protocol.CodeBizError, 
errors.New("forced error"))
+       if got := GreetRequestExpected("error", nil, err); got != nil {
+               t.Fatalf("expected typed forced error to be accepted, got %v", 
got)
+       }
+}
+
+// Mutation: the provider error branch is removed and the forced request
+// succeeds like a normal request.
+func TestGreetRequestExpectedForcedErrorSucceeded(t *testing.T) {
+       resp := &observability.GreetResponse{Greeting: "hello error"}
+       if err := GreetRequestExpected("error", resp, nil); err == nil {
+               t.Fatal("a succeeded forced-error request must be rejected")
+       }
+}
+
+// Mutation: the provider returns an untyped or wrong-code error.
+func TestGreetRequestExpectedForcedErrorWrongCode(t *testing.T) {
+       err := triple_protocol.NewError(triple_protocol.CodeUnknown, 
errors.New("forced error"))
+       if got := GreetRequestExpected("error", nil, err); got == nil {
+               t.Fatal("forced error with a wrong code must be rejected")
+       }
+}
diff --git a/observability/integration/otel-collector-config.yaml 
b/observability/integration/otel-collector-config.yaml
new file mode 100644
index 00000000..a3b7316f
--- /dev/null
+++ b/observability/integration/otel-collector-config.yaml
@@ -0,0 +1,23 @@
+receivers:
+  otlp:
+    protocols:
+      grpc:
+        endpoint: 0.0.0.0:4317
+      http:
+        endpoint: 0.0.0.0:4318
+
+processors:
+  batch:
+
+exporters:
+  otlp/jaeger:
+    endpoint: jaeger:4317
+    tls:
+      insecure: true
+
+service:
+  pipelines:
+    traces:
+      receivers: [otlp]
+      processors: [batch]
+      exporters: [otlp/jaeger]
diff --git a/observability/integration/prometheus.yml 
b/observability/integration/prometheus.yml
new file mode 100644
index 00000000..ed1900ab
--- /dev/null
+++ b/observability/integration/prometheus.yml
@@ -0,0 +1,13 @@
+global:
+  scrape_interval: 5s
+
+scrape_configs:
+  - job_name: dubbo-observability-server
+    metrics_path: /prometheus
+    static_configs:
+      - targets: ["host.docker.internal:9099"]
+
+  - job_name: dubbo-observability-client
+    metrics_path: /prometheus
+    static_configs:
+      - targets: ["host.docker.internal:9098"]
diff --git a/metrics/prometheus_grafana/proto/greet.pb.go 
b/observability/integration/proto/greet.pb.go
similarity index 88%
copy from metrics/prometheus_grafana/proto/greet.pb.go
copy to observability/integration/proto/greet.pb.go
index e1102c03..bd2f4756 100644
--- a/metrics/prometheus_grafana/proto/greet.pb.go
+++ b/observability/integration/proto/greet.pb.go
@@ -1,4 +1,3 @@
-//
 // 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.
@@ -16,24 +15,20 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-//     protoc-gen-go v1.36.6
-//     protoc        v6.31.1
+//     protoc-gen-go v1.36.11
+//     protoc        v7.34.1
 // source: greet.proto
 
-package greet
+package observability
 
 import (
+       protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+       protoimpl "google.golang.org/protobuf/runtime/protoimpl"
        reflect "reflect"
        sync "sync"
        unsafe "unsafe"
 )
 
-import (
-       protoreflect "google.golang.org/protobuf/reflect/protoreflect"
-
-       protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-)
-
 const (
        // Verify that this generated code is sufficiently up-to-date.
        _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
@@ -133,13 +128,13 @@ var File_greet_proto protoreflect.FileDescriptor
 
 const file_greet_proto_rawDesc = "" +
        "\n" +
-       "\vgreet.proto\x12\x05greet\"\"\n" +
+       "\vgreet.proto\x12\robservability\"\"\n" +
        "\fGreetRequest\x12\x12\n" +
        "\x04name\x18\x01 \x01(\tR\x04name\"+\n" +
        "\rGreetResponse\x12\x1a\n" +
-       "\bgreeting\x18\x01 \x01(\tR\bgreeting2D\n" +
-       "\fGreetService\x124\n" +
-       
"\x05Greet\x12\x13.greet.GreetRequest\x1a\x14.greet.GreetResponse\"\x00B8Z6github.com/apache/dubbo-go-samples/metrics/prometheus_grafana/proto;greetb\x06proto3"
+       "\bgreeting\x18\x01 \x01(\tR\bgreeting2T\n" +
+       "\fGreetService\x12D\n" +
+       
"\x05Greet\x12\x1b.observability.GreetRequest\x1a\x1c.observability.GreetResponse\"\x00BRZPgithub.com/apache/dubbo-go-samples/observability/integration/proto;observabilityb\x06proto3"
 
 var (
        file_greet_proto_rawDescOnce sync.Once
@@ -155,12 +150,12 @@ func file_greet_proto_rawDescGZIP() []byte {
 
 var file_greet_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
 var file_greet_proto_goTypes = []any{
-       (*GreetRequest)(nil),  // 0: greet.GreetRequest
-       (*GreetResponse)(nil), // 1: greet.GreetResponse
+       (*GreetRequest)(nil),  // 0: observability.GreetRequest
+       (*GreetResponse)(nil), // 1: observability.GreetResponse
 }
 var file_greet_proto_depIdxs = []int32{
-       0, // 0: greet.GreetService.Greet:input_type -> greet.GreetRequest
-       1, // 1: greet.GreetService.Greet:output_type -> greet.GreetResponse
+       0, // 0: observability.GreetService.Greet:input_type -> 
observability.GreetRequest
+       1, // 1: observability.GreetService.Greet:output_type -> 
observability.GreetResponse
        1, // [1:2] is the sub-list for method output_type
        0, // [0:1] is the sub-list for method input_type
        0, // [0:0] is the sub-list for extension type_name
diff --git a/observability/integration/proto/greet.proto 
b/observability/integration/proto/greet.proto
new file mode 100644
index 00000000..528b469f
--- /dev/null
+++ b/observability/integration/proto/greet.proto
@@ -0,0 +1,32 @@
+// 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.
+
+syntax = "proto3";
+
+package observability;
+
+option go_package = 
"github.com/apache/dubbo-go-samples/observability/integration/proto;observability";
+
+message GreetRequest {
+  string name = 1;
+}
+
+message GreetResponse {
+  string greeting = 1;
+}
+
+service GreetService {
+  rpc Greet(GreetRequest) returns (GreetResponse) {}
+}
diff --git a/metrics/prometheus_grafana/proto/greet.triple.go 
b/observability/integration/proto/greet.triple.go
similarity index 86%
copy from metrics/prometheus_grafana/proto/greet.triple.go
copy to observability/integration/proto/greet.triple.go
index 6f906938..ac6d4469 100644
--- a/metrics/prometheus_grafana/proto/greet.triple.go
+++ b/observability/integration/proto/greet.triple.go
@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-triple. DO NOT EDIT.
 //
 // Source: greet.proto
-package greet
+package observability
 
 import (
        "context"
@@ -25,7 +25,7 @@ const _ = triple_protocol.IsAtLeastVersion0_1_0
 
 const (
        // GreetServiceName is the fully-qualified name of the GreetService 
service.
-       GreetServiceName = "greet.GreetService"
+       GreetServiceName = "observability.GreetService"
 )
 
 // These constants are the fully-qualified names of the RPCs defined in this 
package. They're
@@ -37,21 +37,21 @@ const (
 // period.
 const (
        // GreetServiceGreetProcedure is the fully-qualified name of the 
GreetService's Greet RPC.
-       GreetServiceGreetProcedure = "/greet.GreetService/Greet"
+       GreetServiceGreetProcedure = "/observability.GreetService/Greet"
 )
 
 var (
        _ GreetService = (*GreetServiceImpl)(nil)
 )
 
-// GreetService is a client for the greet.GreetService service.
+// GreetService is a client for the observability.GreetService service.
 type GreetService interface {
        Greet(ctx context.Context, req *GreetRequest, opts 
...client.CallOption) (*GreetResponse, error)
 }
 
-// NewGreetService constructs a client for the greet.GreetService service.
+// NewGreetService constructs a client for the observability.GreetService 
service.
 func NewGreetService(cli *client.Client, opts ...client.ReferenceOption) 
(GreetService, error) {
-       conn, err := cli.DialWithInfo("greet.GreetService", 
&GreetService_ClientInfo, opts...)
+       conn, err := cli.DialWithInfo("observability.GreetService", 
&GreetService_ClientInfo, opts...)
        if err != nil {
                return nil, err
        }
@@ -78,7 +78,7 @@ func (c *GreetServiceImpl) Greet(ctx context.Context, req 
*GreetRequest, opts ..
 }
 
 var GreetService_ClientInfo = client.ClientInfo{
-       InterfaceName: "greet.GreetService",
+       InterfaceName: "observability.GreetService",
        MethodNames:   []string{"Greet"},
        ConnectionInjectFunc: func(dubboCliRaw interface{}, conn 
*client.Connection) {
                dubboCli := dubboCliRaw.(*GreetServiceImpl)
@@ -86,7 +86,7 @@ var GreetService_ClientInfo = client.ClientInfo{
        },
 }
 
-// GreetServiceHandler is an implementation of the greet.GreetService service.
+// GreetServiceHandler is an implementation of the observability.GreetService 
service.
 type GreetServiceHandler interface {
        Greet(context.Context, *GreetRequest) (*GreetResponse, error)
 }
@@ -100,7 +100,7 @@ func SetProviderGreetService(srv common.RPCService) {
 }
 
 var GreetService_ServiceInfo = server.ServiceInfo{
-       InterfaceName: "greet.GreetService",
+       InterfaceName: "observability.GreetService",
        ServiceType:   (*GreetServiceHandler)(nil),
        Methods: []server.MethodInfo{
                {
diff --git a/metrics/probe/README.md b/observability/probe/README.md
similarity index 93%
rename from metrics/probe/README.md
rename to observability/probe/README.md
index fc7d43ca..260dc3ab 100644
--- a/metrics/probe/README.md
+++ b/observability/probe/README.md
@@ -11,7 +11,7 @@ It showcases how to expose dedicated probe endpoints and how 
Kubernetes reacts d
 ## Project Layout
 
 ```
-metrics/probe/
+observability/probe/
 ├── go-client/
 │   └── cmd/main.go        # Integration checker for ports/probes/RPC
 ├── go-server/
@@ -49,13 +49,13 @@ metrics/probe/
 ### 1️Start the server
 
 ```bash
-go run ./metrics/probe/go-server/cmd/main.go
+go run ./observability/probe/go-server/cmd/main.go
 ```
 
 ### 2️Run the go-client integration checks
 
 ```bash
-go run ./metrics/probe/go-client/cmd/main.go
+go run ./observability/probe/go-client/cmd/main.go
 ```
 
 The `go-client` validates:
@@ -119,7 +119,7 @@ startupProbe:
 From the repository root:
 
 ```bash
-./metrics/probe/go-server/build.sh
+./observability/probe/go-server/build.sh
 ```
 
 
@@ -135,7 +135,7 @@ minikube image load dubbo-go-probe-server:latest
 ### Deploy to Kubernetes
 
 ```bash
-kubectl apply -f metrics/probe/deploy/server-deployment.yml
+kubectl apply -f observability/probe/deploy/server-deployment.yml
 kubectl rollout status deploy/dubbo-go-probe-server
 kubectl get pod -l app=dubbo-go-probe-server
 ```
diff --git a/metrics/probe/README_CN.md b/observability/probe/README_CN.md
similarity index 93%
rename from metrics/probe/README_CN.md
rename to observability/probe/README_CN.md
index 2678bc06..6794d6cb 100644
--- a/metrics/probe/README_CN.md
+++ b/observability/probe/README_CN.md
@@ -12,7 +12,7 @@
 ## 项目结构
 
 ```
-metrics/probe/
+observability/probe/
 ├── go-client/
 │   └── cmd/main.go              # 集成测试客户端(端口/探针/RPC 校验)
 ├── go-server/
@@ -51,13 +51,13 @@ metrics/probe/
 ### 启动服务
 
 ```bash
-go run ./metrics/probe/go-server/cmd/main.go
+go run ./observability/probe/go-server/cmd/main.go
 ```
 
 ### 运行 go-client 做集成检查
 
 ```bash
-go run ./metrics/probe/go-client/cmd/main.go
+go run ./observability/probe/go-client/cmd/main.go
 ```
 
 `go-client` 会依次检查:
@@ -127,7 +127,7 @@ startupProbe:
 在仓库根目录执行:
 
 ```bash
-./metrics/probe/go-server/build.sh
+./observability/probe/go-server/build.sh
 ```
 
 
@@ -143,7 +143,7 @@ minikube image load dubbo-go-probe-server:latest
 ### 部署到 Kubernetes
 
 ```bash
-kubectl apply -f metrics/probe/deploy/server-deployment.yml
+kubectl apply -f observability/probe/deploy/server-deployment.yml
 kubectl rollout status deploy/dubbo-go-probe-server
 kubectl get pod -l app=dubbo-go-probe-server
 ```
diff --git a/metrics/probe/deploy/server-deployment.yml 
b/observability/probe/deploy/server-deployment.yml
similarity index 100%
rename from metrics/probe/deploy/server-deployment.yml
rename to observability/probe/deploy/server-deployment.yml
diff --git a/metrics/probe/go-client/cmd/main.go 
b/observability/probe/go-client/cmd/main.go
similarity index 100%
rename from metrics/probe/go-client/cmd/main.go
rename to observability/probe/go-client/cmd/main.go
diff --git a/metrics/probe/go-server/Dockerfile 
b/observability/probe/go-server/Dockerfile
similarity index 57%
rename from metrics/probe/go-server/Dockerfile
rename to observability/probe/go-server/Dockerfile
index c58271e5..b350976f 100644
--- a/metrics/probe/go-server/Dockerfile
+++ b/observability/probe/go-server/Dockerfile
@@ -1,6 +1,6 @@
 FROM alpine:3.19
 WORKDIR /app
-COPY metrics/probe/go-server/probeApp /app/server
+COPY observability/probe/go-server/probeApp /app/server
 
 EXPOSE 20000 22222
 ENTRYPOINT ["/app/server"]
diff --git a/metrics/probe/go-server/build.sh 
b/observability/probe/go-server/build.sh
similarity index 86%
rename from metrics/probe/go-server/build.sh
rename to observability/probe/go-server/build.sh
index d21e9afe..0f776355 100644
--- a/metrics/probe/go-server/build.sh
+++ b/observability/probe/go-server/build.sh
@@ -27,7 +27,7 @@ cd "$(dirname "$0")/../../.."
 TARGET_OS=${TARGET_OS:-linux}
 TARGET_ARCH=${TARGET_ARCH:-amd64}
 
-CGO_ENABLED=0 GOOS="$TARGET_OS" GOARCH="$TARGET_ARCH" go build -o 
metrics/probe/go-server/probeApp ./metrics/probe/go-server/cmd/main.go
-docker build -f metrics/probe/go-server/Dockerfile -t 
dubbo-go-probe-server:latest .
+CGO_ENABLED=0 GOOS="$TARGET_OS" GOARCH="$TARGET_ARCH" go build -o 
observability/probe/go-server/probeApp 
./observability/probe/go-server/cmd/main.go
+docker build -f observability/probe/go-server/Dockerfile -t 
dubbo-go-probe-server:latest .
 
 echo "Build completed successfully."
diff --git a/metrics/probe/go-server/cmd/main.go 
b/observability/probe/go-server/cmd/main.go
similarity index 100%
rename from metrics/probe/go-server/cmd/main.go
rename to observability/probe/go-server/cmd/main.go
diff --git a/metrics/prometheus_grafana/Deployment.yml 
b/observability/prometheus_grafana/Deployment.yml
similarity index 100%
rename from metrics/prometheus_grafana/Deployment.yml
rename to observability/prometheus_grafana/Deployment.yml
diff --git a/metrics/prometheus_grafana/README.md 
b/observability/prometheus_grafana/README.md
similarity index 96%
rename from metrics/prometheus_grafana/README.md
rename to observability/prometheus_grafana/README.md
index fc8c0847..126e7c9c 100644
--- a/metrics/prometheus_grafana/README.md
+++ b/observability/prometheus_grafana/README.md
@@ -75,8 +75,8 @@ Please follow the steps below to run this example.
 First, start the Grafana, Prometheus, and Pushgateway services. We use 
`docker-compose` to do this with a single command.
 
 ```bash
-# Enter the metrics directory
-cd metrics/prometheus_grafana
+# Enter the observability metrics directory
+cd observability/prometheus_grafana
 # Start all monitoring services in the background
 docker-compose up -d
 ```
@@ -168,7 +168,7 @@ For 
details:[tools/pgw-cleaner](../tools/pgw-cleaner/README.md)
 
 - **Cannot connect to `host.docker.internal`**
 
-    - `host.docker.internal` is a built-in feature of Docker. If this address 
is not accessible, replace the IP address in 
`metrics/prometheus_grafana/prometheus_pull.yml` and the Grafana data source 
address with your actual IP address.
+    - `host.docker.internal` is a built-in feature of Docker. If this address 
is not accessible, replace the IP address in 
`observability/prometheus_grafana/prometheus_pull.yml` and the Grafana data 
source address with your actual IP address.
 
 -----
 
@@ -231,4 +231,4 @@ Set the service type in `prometheus-service.yaml` to 
`NodePort`.
 
 2.  Execute `kubectl apply -f Deployment.yaml`
 
-3.  Open the Prometheus web interface, for example, 
`http://localhost:9090/targets`
\ No newline at end of file
+3.  Open the Prometheus web interface, for example, 
`http://localhost:9090/targets`
diff --git a/metrics/prometheus_grafana/README_CN.md 
b/observability/prometheus_grafana/README_CN.md
similarity index 97%
rename from metrics/prometheus_grafana/README_CN.md
rename to observability/prometheus_grafana/README_CN.md
index 9b7d8332..72a0e6a0 100644
--- a/metrics/prometheus_grafana/README_CN.md
+++ b/observability/prometheus_grafana/README_CN.md
@@ -78,8 +78,8 @@ go run ./go-server/cmd/main.go --push=false
 首先,启动 Grafana, Prometheus 和 Pushgateway 服务。我们使用 `docker-compose` 来一键完成。
 
 ```bash
-# 进入 metrics 目录
-cd metrics/prometheus_grafana
+# 进入 observability metrics 目录
+cd observability/prometheus_grafana
 # 以后台模式启动所有监控服务
 docker-compose up -d
 ```
@@ -170,7 +170,7 @@ Pushgateway 设计初衷:为短生命周期进程(batch job、cron job)提
     - 在 Prometheus 的查询栏中输入 `dubbo_consumer_requests_succeed_total`,确认能查询到数据。
 
 - **`host.docker.internal` 无法连接**
-    - `host.docker.internal` 是 Docker 的内置功能,如果该地址无法访问,请将 
`metrics/prometheus_grafana/prometheus_pull.yml`
+    - `host.docker.internal` 是 Docker 的内置功能,如果该地址无法访问,请将 
`observability/prometheus_grafana/prometheus_pull.yml`
       中的Ip地址以及Grafana的数据源地址换为实际的Ip地址。
 
 -----
@@ -234,4 +234,4 @@ Pushgateway 设计初衷:为短生命周期进程(batch job、cron job)提
 
 2.  执行 `kubectl apply -f Deployment.yaml`
 
-3.  打开 Prometheus 的网页界面,例如 `http://localhost:9090/targets`
\ No newline at end of file
+3.  打开 Prometheus 的网页界面,例如 `http://localhost:9090/targets`
diff --git a/metrics/prometheus_grafana/assert/grafana.png 
b/observability/prometheus_grafana/assert/grafana.png
similarity index 100%
rename from metrics/prometheus_grafana/assert/grafana.png
rename to observability/prometheus_grafana/assert/grafana.png
diff --git a/metrics/prometheus_grafana/docker-compose.yml 
b/observability/prometheus_grafana/docker-compose.yml
similarity index 100%
rename from metrics/prometheus_grafana/docker-compose.yml
rename to observability/prometheus_grafana/docker-compose.yml
diff --git a/metrics/prometheus_grafana/go-client/Dockerfile 
b/observability/prometheus_grafana/go-client/Dockerfile
similarity index 100%
rename from metrics/prometheus_grafana/go-client/Dockerfile
rename to observability/prometheus_grafana/go-client/Dockerfile
diff --git a/metrics/prometheus_grafana/go-client/build.sh 
b/observability/prometheus_grafana/go-client/build.sh
similarity index 100%
rename from metrics/prometheus_grafana/go-client/build.sh
rename to observability/prometheus_grafana/go-client/build.sh
diff --git a/metrics/prometheus_grafana/go-client/cmd/main.go 
b/observability/prometheus_grafana/go-client/cmd/main.go
similarity index 100%
rename from metrics/prometheus_grafana/go-client/cmd/main.go
rename to observability/prometheus_grafana/go-client/cmd/main.go
diff --git a/metrics/prometheus_grafana/go-server/Dockerfile 
b/observability/prometheus_grafana/go-server/Dockerfile
similarity index 100%
rename from metrics/prometheus_grafana/go-server/Dockerfile
rename to observability/prometheus_grafana/go-server/Dockerfile
diff --git a/metrics/prometheus_grafana/go-server/build.sh 
b/observability/prometheus_grafana/go-server/build.sh
similarity index 100%
rename from metrics/prometheus_grafana/go-server/build.sh
rename to observability/prometheus_grafana/go-server/build.sh
diff --git a/metrics/prometheus_grafana/go-server/cmd/main.go 
b/observability/prometheus_grafana/go-server/cmd/main.go
similarity index 100%
rename from metrics/prometheus_grafana/go-server/cmd/main.go
rename to observability/prometheus_grafana/go-server/cmd/main.go
diff --git a/metrics/prometheus_grafana/grafana.json 
b/observability/prometheus_grafana/grafana.json
similarity index 100%
rename from metrics/prometheus_grafana/grafana.json
rename to observability/prometheus_grafana/grafana.json
diff --git a/metrics/prometheus_grafana/prometheus_pull.yml 
b/observability/prometheus_grafana/prometheus_pull.yml
similarity index 100%
rename from metrics/prometheus_grafana/prometheus_pull.yml
rename to observability/prometheus_grafana/prometheus_pull.yml
diff --git a/metrics/prometheus_grafana/prometheus_push.yml 
b/observability/prometheus_grafana/prometheus_push.yml
similarity index 100%
rename from metrics/prometheus_grafana/prometheus_push.yml
rename to observability/prometheus_grafana/prometheus_push.yml
diff --git a/metrics/prometheus_grafana/proto/greet.pb.go 
b/observability/prometheus_grafana/proto/greet.pb.go
similarity index 98%
rename from metrics/prometheus_grafana/proto/greet.pb.go
rename to observability/prometheus_grafana/proto/greet.pb.go
index e1102c03..914dfef3 100644
--- a/metrics/prometheus_grafana/proto/greet.pb.go
+++ b/observability/prometheus_grafana/proto/greet.pb.go
@@ -139,7 +139,7 @@ const file_greet_proto_rawDesc = "" +
        "\rGreetResponse\x12\x1a\n" +
        "\bgreeting\x18\x01 \x01(\tR\bgreeting2D\n" +
        "\fGreetService\x124\n" +
-       
"\x05Greet\x12\x13.greet.GreetRequest\x1a\x14.greet.GreetResponse\"\x00B8Z6github.com/apache/dubbo-go-samples/metrics/prometheus_grafana/proto;greetb\x06proto3"
+       
"\x05Greet\x12\x13.greet.GreetRequest\x1a\x14.greet.GreetResponse\"\x00B8Z6github.com/apache/dubbo-go-samples/observability/prometheus_grafana/proto;greetb\x06proto3"
 
 var (
        file_greet_proto_rawDescOnce sync.Once
diff --git a/metrics/prometheus_grafana/proto/greet.proto 
b/observability/prometheus_grafana/proto/greet.proto
similarity index 90%
rename from metrics/prometheus_grafana/proto/greet.proto
rename to observability/prometheus_grafana/proto/greet.proto
index 9546a73e..07b364dc 100644
--- a/metrics/prometheus_grafana/proto/greet.proto
+++ b/observability/prometheus_grafana/proto/greet.proto
@@ -19,7 +19,7 @@ syntax = "proto3";
 
 package greet;
 
-option go_package = 
"github.com/apache/dubbo-go-samples/metrics/prometheus_grafana/proto;greet";
+option go_package = 
"github.com/apache/dubbo-go-samples/observability/prometheus_grafana/proto;greet";
 
 message GreetRequest {
   string name = 1;
@@ -31,4 +31,4 @@ message GreetResponse {
 
 service GreetService {
   rpc Greet(GreetRequest) returns (GreetResponse) {}
-}
\ No newline at end of file
+}
diff --git a/metrics/prometheus_grafana/proto/greet.triple.go 
b/observability/prometheus_grafana/proto/greet.triple.go
similarity index 100%
rename from metrics/prometheus_grafana/proto/greet.triple.go
rename to observability/prometheus_grafana/proto/greet.triple.go
diff --git a/start_integrate_test.sh b/start_integrate_test.sh
index 2f17f0b5..2bdd7db1 100755
--- a/start_integrate_test.sh
+++ b/start_integrate_test.sh
@@ -82,8 +82,11 @@ array+=("async")
 # error
 array+=("error")
 
-# metrics
-array+=("metrics/probe")
+# observability samples
+array+=("observability/probe")
+
+# observability integration validation
+array+=("observability/integration")
 
 # config_center
 array+=("config_center/nacos")

Reply via email to