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