NewLife/Stardust

Add Go SDK implementation with APM monitoring and configuration center support

Co-authored-by: nnhy <506367+nnhy@users.noreply.github.com>
copilot-swe-agent[bot] authored at 2026-02-13 05:32:17
713d966
Tree
1 Parent(s) 777c1e5
Summary: 11 changed files with 1616 additions and 1 deletions.
Modified +354 -1
Added +44 -0
Added +57 -0
Added +44 -0
Added +92 -0
Added +92 -0
Added +194 -0
Added +3 -0
Added +97 -0
Added +183 -0
Added +456 -0
Modified +354 -1
diff --git a/Doc/SDK/stardust-sdk-go.md b/Doc/SDK/stardust-sdk-go.md
index 853c700..81ef011 100644
--- a/Doc/SDK/stardust-sdk-go.md
+++ b/Doc/SDK/stardust-sdk-go.md
@@ -1,6 +1,6 @@
 # 星尘监控 Go SDK
 
-适用于 Go 1.18+,提供星尘 APM 监控的接入能力。
+适用于 Go 1.18+,提供星尘 APM 监控和配置中心的接入能力。
 
 ## 安装
 
@@ -570,3 +570,356 @@ func main() {
 	http.ListenAndServe(":8080", StardustHandler(mux))
 }
 ```
+
+---
+
+# 配置中心
+
+## 配置中心快速开始
+
+```go
+package main
+
+import (
+	"fmt"
+	"stardust"
+)
+
+func main() {
+	// 创建配置客户端
+	config := stardust.NewConfigClient("http://star.example.com:6600", "MyGoApp", "MySecret")
+	config.Start()
+	defer config.Stop()
+
+	// 获取配置
+	value := config.Get("database.host")
+	fmt.Println("Database Host:", value)
+
+	// 监听配置变更
+	config.OnChange(func(configs map[string]string) {
+		fmt.Println("配置已更新:", configs)
+	})
+
+	// 等待程序运行
+	select {}
+}
+```
+
+## 配置中心完整代码
+
+```go
+package stardust
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"os"
+	"sync"
+	"time"
+)
+
+// ConfigInfo 配置信息
+type ConfigInfo struct {
+	Version     int               `json:"Version"`
+	Scope       string            `json:"Scope"`
+	SourceIP    string            `json:"SourceIP"`
+	NextVersion int               `json:"NextVersion"`
+	NextPublish string            `json:"NextPublish"`
+	UpdateTime  int64             `json:"UpdateTime"`
+	Configs     map[string]string `json:"Configs"`
+}
+
+// ConfigClient 配置中心客户端
+type ConfigClient struct {
+	Server   string
+	AppID    string
+	Secret   string
+	ClientID string
+
+	token       string
+	tokenExpire int64
+	version     int
+	configs     map[string]string
+	mu          sync.RWMutex
+	running     bool
+	stopCh      chan struct{}
+	client      *http.Client
+	onChange    func(map[string]string)
+}
+
+// NewConfigClient 创建配置客户端
+func NewConfigClient(server, appID, secret string) *ConfigClient {
+	return &ConfigClient{
+		Server:   server,
+		AppID:    appID,
+		Secret:   secret,
+		ClientID: fmt.Sprintf("%s@%d", getLocalIP(), os.Getpid()),
+		configs:  make(map[string]string),
+		stopCh:   make(chan struct{}),
+		client:   &http.Client{Timeout: 10 * time.Second},
+	}
+}
+
+// Start 启动配置客户端
+func (c *ConfigClient) Start() {
+	c.login()
+	c.running = true
+	go c.pollLoop()
+}
+
+// Stop 停止配置客户端
+func (c *ConfigClient) Stop() {
+	c.running = false
+	close(c.stopCh)
+}
+
+// Get 获取配置项
+func (c *ConfigClient) Get(key string) string {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	return c.configs[key]
+}
+
+// GetAll 获取所有配置
+func (c *ConfigClient) GetAll() map[string]string {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	result := make(map[string]string, len(c.configs))
+	for k, v := range c.configs {
+		result[k] = v
+	}
+	return result
+}
+
+// OnChange 注册配置变更回调
+func (c *ConfigClient) OnChange(callback func(map[string]string)) {
+	c.onChange = callback
+}
+
+func (c *ConfigClient) login() {
+	payload := map[string]interface{}{
+		"AppId":    c.AppID,
+		"Secret":   c.Secret,
+		"ClientId": c.ClientID,
+		"AppName":  c.AppID,
+	}
+
+	body, err := json.Marshal(payload)
+	if err != nil {
+		return
+	}
+
+	resp, err := c.client.Post(c.Server+"/App/Login", "application/json", bytes.NewReader(body))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "[Stardust] Config login failed: %v\n", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	var apiResp APIResponse
+	if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil || apiResp.Code != 0 {
+		return
+	}
+
+	var loginResp LoginResponse
+	if err := json.Unmarshal(apiResp.Data, &loginResp); err != nil {
+		return
+	}
+
+	c.token = loginResp.Token
+	if loginResp.Expire > 0 {
+		c.tokenExpire = time.Now().Unix() + int64(loginResp.Expire)
+	}
+}
+
+func (c *ConfigClient) getAllConfig() {
+	payload := map[string]interface{}{
+		"AppId":    c.AppID,
+		"Secret":   c.Secret,
+		"ClientId": c.ClientID,
+		"Version":  c.version,
+	}
+
+	body, err := json.Marshal(payload)
+	if err != nil {
+		return
+	}
+
+	reqURL := fmt.Sprintf("%s/Config/GetAll?Token=%s", c.Server, url.QueryEscape(c.token))
+	resp, err := c.client.Post(reqURL, "application/json", bytes.NewReader(body))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "[Stardust] Config GetAll failed: %v\n", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	respBody, _ := io.ReadAll(resp.Body)
+	var apiResp APIResponse
+	if err := json.Unmarshal(respBody, &apiResp); err != nil || apiResp.Code != 0 {
+		return
+	}
+
+	var configInfo ConfigInfo
+	if err := json.Unmarshal(apiResp.Data, &configInfo); err != nil {
+		return
+	}
+
+	// 版本没变化,不更新
+	if c.version > 0 && configInfo.Version == c.version && configInfo.Configs == nil {
+		return
+	}
+
+	// 更新配置
+	if configInfo.Configs != nil {
+		c.mu.Lock()
+		c.configs = configInfo.Configs
+		c.version = configInfo.Version
+		c.mu.Unlock()
+
+		// 触发回调
+		if c.onChange != nil {
+			go c.onChange(configInfo.Configs)
+		}
+	}
+}
+
+func (c *ConfigClient) pollLoop() {
+	// 首次立即获取配置
+	c.getAllConfig()
+
+	ticker := time.NewTicker(30 * time.Second)
+	defer ticker.Stop()
+	for {
+		select {
+		case <-ticker.C:
+			c.getAllConfig()
+		case <-c.stopCh:
+			return
+		}
+	}
+}
+```
+
+## 配置中心与APM监控集成示例
+
+```go
+package main
+
+import (
+	"fmt"
+	"stardust"
+	"time"
+)
+
+func main() {
+	// 启动APM监控
+	tracer := stardust.NewTracer("http://star.example.com:6600", "MyGoApp", "MySecret")
+	tracer.Start()
+	defer tracer.Stop()
+
+	// 启动配置中心
+	config := stardust.NewConfigClient("http://star.example.com:6600", "MyGoApp", "MySecret")
+	config.Start()
+	defer config.Stop()
+
+	// 监听配置变更
+	config.OnChange(func(configs map[string]string) {
+		span := tracer.NewSpan("配置更新", "")
+		span.Tag = fmt.Sprintf("配置项数量: %d", len(configs))
+		span.Finish()
+		
+		fmt.Println("配置已更新:")
+		for k, v := range configs {
+			fmt.Printf("  %s = %s\n", k, v)
+		}
+	})
+
+	// 业务逻辑
+	for {
+		span := tracer.NewSpan("业务处理", "")
+		
+		// 读取配置
+		dbHost := config.Get("database.host")
+		dbPort := config.Get("database.port")
+		
+		span.Tag = fmt.Sprintf("DB: %s:%s", dbHost, dbPort)
+		
+		// 模拟业务处理
+		time.Sleep(5 * time.Second)
+		
+		span.Finish()
+	}
+}
+```
+
+## 在Gin框架中使用配置中心
+
+```go
+package main
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"stardust"
+)
+
+var (
+	tracer = stardust.NewTracer("http://star.example.com:6600", "MyGinApp", "secret")
+	config = stardust.NewConfigClient("http://star.example.com:6600", "MyGinApp", "secret")
+)
+
+func main() {
+	tracer.Start()
+	defer tracer.Stop()
+	
+	config.Start()
+	defer config.Stop()
+
+	// 监听配置变更
+	config.OnChange(func(configs map[string]string) {
+		fmt.Println("配置已更新,请重新加载相关模块")
+	})
+
+	r := gin.Default()
+	
+	// APM中间件
+	r.Use(StardustMiddleware())
+
+	r.GET("/config/:key", func(c *gin.Context) {
+		key := c.Param("key")
+		value := config.Get(key)
+		c.JSON(200, gin.H{
+			"key":   key,
+			"value": value,
+		})
+	})
+
+	r.GET("/configs", func(c *gin.Context) {
+		c.JSON(200, config.GetAll())
+	})
+
+	r.Run(":8080")
+}
+
+func StardustMiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		name := c.Request.Method + " " + c.Request.URL.Path
+		span := tracer.NewSpan(name, "")
+		span.Tag = c.Request.Method + " " + c.Request.RequestURI
+
+		c.Next()
+
+		if c.Writer.Status() >= 400 {
+			span.Error = fmt.Sprintf("HTTP %d", c.Writer.Status())
+		}
+		if len(c.Errors) > 0 {
+			span.Error = c.Errors.String()
+		}
+		span.Finish()
+	}
+}
+```
Added +44 -0
diff --git a/SDK/Go/examples/apm_basic.go b/SDK/Go/examples/apm_basic.go
new file mode 100644
index 0000000..3cecebf
--- /dev/null
+++ b/SDK/Go/examples/apm_basic.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/NewLifeX/Stardust/SDK/Go/stardust"
+)
+
+func main() {
+	// 创建追踪器
+	tracer := stardust.NewTracer("http://localhost:6600", "MyGoApp", "MySecret")
+	tracer.Start()
+	defer tracer.Stop()
+
+	fmt.Println("APM 追踪器已启动,开始模拟业务操作...")
+
+	// 模拟业务操作
+	for i := 0; i < 5; i++ {
+		// 方式1:手动创建和结束
+		span := tracer.NewSpan("业务操作", "")
+		span.Tag = fmt.Sprintf("操作编号: %d", i+1)
+
+		// 模拟业务处理
+		time.Sleep(time.Millisecond * 100)
+
+		span.Finish()
+
+		// 方式2:使用 defer
+		func() {
+			span2 := tracer.NewSpan("数据库查询", "")
+			span2.Tag = "SELECT * FROM users"
+			defer span2.Finish()
+
+			// 模拟数据库查询
+			time.Sleep(time.Millisecond * 50)
+		}()
+
+		time.Sleep(time.Second)
+	}
+
+	fmt.Println("业务操作完成,等待数据上报...")
+	time.Sleep(time.Second * 5)
+}
Added +57 -0
diff --git a/SDK/Go/examples/combined.go b/SDK/Go/examples/combined.go
new file mode 100644
index 0000000..394c3c5
--- /dev/null
+++ b/SDK/Go/examples/combined.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/NewLifeX/Stardust/SDK/Go/stardust"
+)
+
+func main() {
+	// 同时启动 APM 和配置中心
+	tracer := stardust.NewTracer("http://localhost:6600", "MyGoApp", "MySecret")
+	tracer.Start()
+	defer tracer.Stop()
+
+	config := stardust.NewConfigClient("http://localhost:6600", "MyGoApp", "MySecret")
+	config.Start()
+	defer config.Stop()
+
+	fmt.Println("APM 和配置中心已启动...")
+
+	// 监听配置变更,并记录到 APM
+	config.OnChange(func(configs map[string]string) {
+		span := tracer.NewSpan("配置更新", "")
+		span.Tag = fmt.Sprintf("配置项数量: %d", len(configs))
+		span.Finish()
+
+		fmt.Println("\n配置已更新:")
+		for k, v := range configs {
+			fmt.Printf("  %s = %s\n", k, v)
+		}
+	})
+
+	// 等待配置加载
+	time.Sleep(time.Second * 2)
+
+	// 业务循环:根据配置执行业务逻辑
+	for i := 0; i < 10; i++ {
+		span := tracer.NewSpan("业务处理", "")
+
+		// 读取配置
+		dbHost := config.Get("database.host")
+		dbPort := config.Get("database.port")
+
+		span.Tag = fmt.Sprintf("DB: %s:%s, 批次: %d", dbHost, dbPort, i+1)
+
+		// 模拟业务处理
+		time.Sleep(time.Second * 2)
+
+		span.Finish()
+
+		fmt.Printf("业务批次 %d 完成\n", i+1)
+	}
+
+	fmt.Println("所有业务完成,等待数据上报...")
+	time.Sleep(time.Second * 5)
+}
Added +44 -0
diff --git a/SDK/Go/examples/config_basic.go b/SDK/Go/examples/config_basic.go
new file mode 100644
index 0000000..3cadd58
--- /dev/null
+++ b/SDK/Go/examples/config_basic.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/NewLifeX/Stardust/SDK/Go/stardust"
+)
+
+func main() {
+	// 创建配置客户端
+	config := stardust.NewConfigClient("http://localhost:6600", "MyGoApp", "MySecret")
+	config.Start()
+	defer config.Stop()
+
+	fmt.Println("配置中心客户端已启动...")
+
+	// 监听配置变更
+	config.OnChange(func(configs map[string]string) {
+		fmt.Println("\n=== 配置已更新 ===")
+		for k, v := range configs {
+			fmt.Printf("  %s = %s\n", k, v)
+		}
+		fmt.Println("==================\n")
+	})
+
+	// 等待配置加载
+	time.Sleep(time.Second * 2)
+
+	// 读取配置
+	fmt.Println("当前配置:")
+	for k, v := range config.GetAll() {
+		fmt.Printf("  %s = %s\n", k, v)
+	}
+
+	// 读取单个配置
+	dbHost := config.Get("database.host")
+	dbPort := config.Get("database.port")
+	fmt.Printf("\n数据库配置: %s:%s\n", dbHost, dbPort)
+
+	// 持续运行
+	fmt.Println("\n按 Ctrl+C 退出...")
+	select {}
+}
Added +92 -0
diff --git a/SDK/Go/examples/README.md b/SDK/Go/examples/README.md
new file mode 100644
index 0000000..1731747
--- /dev/null
+++ b/SDK/Go/examples/README.md
@@ -0,0 +1,92 @@
+# Stardust Go SDK 示例程序
+
+本目录包含了 Stardust Go SDK 的使用示例。
+
+## 前置要求
+
+1. 安装 Go 1.18 或更高版本
+2. 运行 Stardust 服务器(默认端口 6600)
+
+## 示例列表
+
+### 1. APM 基础示例 (apm_basic.go)
+
+演示如何使用 APM 追踪器进行链路追踪。
+
+```bash
+go run apm_basic.go
+```
+
+功能:
+- 创建和启动追踪器
+- 手动埋点
+- 使用 defer 自动结束 span
+- 自动上报数据到服务器
+
+### 2. 配置中心基础示例 (config_basic.go)
+
+演示如何使用配置中心客户端。
+
+```bash
+go run config_basic.go
+```
+
+功能:
+- 连接配置中心
+- 获取单个配置项
+- 获取所有配置
+- 监听配置变更
+
+### 3. 综合示例 (combined.go)
+
+演示如何同时使用 APM 和配置中心。
+
+```bash
+go run combined.go
+```
+
+功能:
+- 同时启动 APM 和配置中心
+- 配置变更时记录到 APM
+- 根据配置执行业务逻辑
+- 业务操作的链路追踪
+
+## 配置说明
+
+在运行示例前,需要在 Stardust 服务器中创建应用:
+
+1. 应用标识:`MyGoApp`
+2. 密钥:`MySecret`
+
+或者修改示例代码中的连接参数:
+
+```go
+tracer := stardust.NewTracer("http://your-server:6600", "YourAppId", "YourSecret")
+config := stardust.NewConfigClient("http://your-server:6600", "YourAppId", "YourSecret")
+```
+
+## 在自己的项目中使用
+
+1. 安装依赖:
+
+```bash
+go get github.com/NewLifeX/Stardust/SDK/Go/stardust
+```
+
+2. 导入并使用:
+
+```go
+import "github.com/NewLifeX/Stardust/SDK/Go/stardust"
+
+func main() {
+    tracer := stardust.NewTracer("http://localhost:6600", "MyApp", "secret")
+    tracer.Start()
+    defer tracer.Stop()
+    
+    // 你的业务代码
+}
+```
+
+## 更多文档
+
+详细文档请参考:[/Doc/SDK/stardust-sdk-go.md](../../../Doc/SDK/stardust-sdk-go.md)
Added +92 -0
diff --git a/SDK/Go/README.md b/SDK/Go/README.md
new file mode 100644
index 0000000..600b91b
--- /dev/null
+++ b/SDK/Go/README.md
@@ -0,0 +1,92 @@
+# Stardust Go SDK
+
+星尘(Stardust)Go 语言客户端 SDK,提供 APM 监控和配置中心功能。
+
+## 目录结构
+
+```
+SDK/Go/
+├── stardust/           # SDK 核心包
+│   ├── go.mod         # Go 模块定义
+│   ├── README.md      # SDK 使用文档
+│   ├── tracer.go      # APM 追踪器实现
+│   ├── config.go      # 配置中心客户端实现
+│   └── stardust_test.go  # 单元测试
+└── examples/          # 示例程序
+    ├── README.md      # 示例说明
+    ├── apm_basic.go   # APM 基础示例
+    ├── config_basic.go # 配置中心基础示例
+    └── combined.go    # 综合示例
+```
+
+## 快速开始
+
+### 安装
+
+```bash
+go get github.com/NewLifeX/Stardust/SDK/Go/stardust
+```
+
+### APM 监控
+
+```go
+import "github.com/NewLifeX/Stardust/SDK/Go/stardust"
+
+tracer := stardust.NewTracer("http://localhost:6600", "MyGoApp", "MySecret")
+tracer.Start()
+defer tracer.Stop()
+
+span := tracer.NewSpan("业务操作", "")
+span.Tag = "参数信息"
+defer span.Finish()
+// 你的业务代码
+```
+
+### 配置中心
+
+```go
+import "github.com/NewLifeX/Stardust/SDK/Go/stardust"
+
+config := stardust.NewConfigClient("http://localhost:6600", "MyGoApp", "MySecret")
+config.Start()
+defer config.Stop()
+
+value := config.Get("database.host")
+config.OnChange(func(configs map[string]string) {
+    // 配置变更处理
+})
+```
+
+## 特性
+
+- ✅ 完整的 APM 链路追踪支持
+- ✅ 配置中心客户端实现
+- ✅ 自动登录和 Token 刷新
+- ✅ 心跳保活
+- ✅ Gzip 压缩大数据包
+- ✅ 自动采样控制
+- ✅ 配置变更推送
+- ✅ 无第三方依赖
+- ✅ 支持 Go 1.18+
+- ✅ 完整的单元测试
+
+## 运行测试
+
+```bash
+cd stardust
+go test -v
+```
+
+## 示例程序
+
+查看 `examples/` 目录获取完整示例。
+
+## 文档
+
+- [SDK 详细文档](stardust/README.md)
+- [示例程序说明](examples/README.md)
+- [完整 API 文档](/Doc/SDK/stardust-sdk-go.md)
+
+## License
+
+MIT License
Added +194 -0
diff --git a/SDK/Go/stardust/config.go b/SDK/Go/stardust/config.go
new file mode 100644
index 0000000..e8dbfdc
--- /dev/null
+++ b/SDK/Go/stardust/config.go
@@ -0,0 +1,194 @@
+package stardust
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"os"
+	"sync"
+	"time"
+)
+
+// ConfigInfo 配置信息
+type ConfigInfo struct {
+	Version     int               `json:"Version"`
+	Scope       string            `json:"Scope"`
+	SourceIP    string            `json:"SourceIP"`
+	NextVersion int               `json:"NextVersion"`
+	NextPublish string            `json:"NextPublish"`
+	UpdateTime  int64             `json:"UpdateTime"`
+	Configs     map[string]string `json:"Configs"`
+}
+
+// ConfigClient 配置中心客户端
+type ConfigClient struct {
+	Server   string
+	AppID    string
+	Secret   string
+	ClientID string
+
+	token       string
+	tokenExpire int64
+	version     int
+	configs     map[string]string
+	mu          sync.RWMutex
+	running     bool
+	stopCh      chan struct{}
+	client      *http.Client
+	onChange    func(map[string]string)
+}
+
+// NewConfigClient 创建配置客户端
+func NewConfigClient(server, appID, secret string) *ConfigClient {
+	return &ConfigClient{
+		Server:   server,
+		AppID:    appID,
+		Secret:   secret,
+		ClientID: fmt.Sprintf("%s@%d", getLocalIP(), os.Getpid()),
+		configs:  make(map[string]string),
+		stopCh:   make(chan struct{}),
+		client:   &http.Client{Timeout: 10 * time.Second},
+	}
+}
+
+// Start 启动配置客户端
+func (c *ConfigClient) Start() {
+	c.login()
+	c.running = true
+	go c.pollLoop()
+}
+
+// Stop 停止配置客户端
+func (c *ConfigClient) Stop() {
+	c.running = false
+	close(c.stopCh)
+}
+
+// Get 获取配置项
+func (c *ConfigClient) Get(key string) string {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	return c.configs[key]
+}
+
+// GetAll 获取所有配置
+func (c *ConfigClient) GetAll() map[string]string {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	result := make(map[string]string, len(c.configs))
+	for k, v := range c.configs {
+		result[k] = v
+	}
+	return result
+}
+
+// OnChange 注册配置变更回调
+func (c *ConfigClient) OnChange(callback func(map[string]string)) {
+	c.onChange = callback
+}
+
+func (c *ConfigClient) login() {
+	payload := map[string]interface{}{
+		"AppId":    c.AppID,
+		"Secret":   c.Secret,
+		"ClientId": c.ClientID,
+		"AppName":  c.AppID,
+	}
+
+	body, err := json.Marshal(payload)
+	if err != nil {
+		return
+	}
+
+	resp, err := c.client.Post(c.Server+"/App/Login", "application/json", bytes.NewReader(body))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "[Stardust] Config login failed: %v\n", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	var apiResp APIResponse
+	if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil || apiResp.Code != 0 {
+		return
+	}
+
+	var loginResp LoginResponse
+	if err := json.Unmarshal(apiResp.Data, &loginResp); err != nil {
+		return
+	}
+
+	c.token = loginResp.Token
+	if loginResp.Expire > 0 {
+		c.tokenExpire = time.Now().Unix() + int64(loginResp.Expire)
+	}
+}
+
+func (c *ConfigClient) getAllConfig() {
+	payload := map[string]interface{}{
+		"AppId":    c.AppID,
+		"Secret":   c.Secret,
+		"ClientId": c.ClientID,
+		"Version":  c.version,
+	}
+
+	body, err := json.Marshal(payload)
+	if err != nil {
+		return
+	}
+
+	reqURL := fmt.Sprintf("%s/Config/GetAll?Token=%s", c.Server, url.QueryEscape(c.token))
+	resp, err := c.client.Post(reqURL, "application/json", bytes.NewReader(body))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "[Stardust] Config GetAll failed: %v\n", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	respBody, _ := io.ReadAll(resp.Body)
+	var apiResp APIResponse
+	if err := json.Unmarshal(respBody, &apiResp); err != nil || apiResp.Code != 0 {
+		return
+	}
+
+	var configInfo ConfigInfo
+	if err := json.Unmarshal(apiResp.Data, &configInfo); err != nil {
+		return
+	}
+
+	// 版本没变化,不更新
+	if c.version > 0 && configInfo.Version == c.version && configInfo.Configs == nil {
+		return
+	}
+
+	// 更新配置
+	if configInfo.Configs != nil {
+		c.mu.Lock()
+		c.configs = configInfo.Configs
+		c.version = configInfo.Version
+		c.mu.Unlock()
+
+		// 触发回调
+		if c.onChange != nil {
+			go c.onChange(configInfo.Configs)
+		}
+	}
+}
+
+func (c *ConfigClient) pollLoop() {
+	// 首次立即获取配置
+	c.getAllConfig()
+
+	ticker := time.NewTicker(30 * time.Second)
+	defer ticker.Stop()
+	for {
+		select {
+		case <-ticker.C:
+			c.getAllConfig()
+		case <-c.stopCh:
+			return
+		}
+	}
+}
Added +3 -0
diff --git a/SDK/Go/stardust/go.mod b/SDK/Go/stardust/go.mod
new file mode 100644
index 0000000..ef90ba0
--- /dev/null
+++ b/SDK/Go/stardust/go.mod
@@ -0,0 +1,3 @@
+module github.com/NewLifeX/Stardust/SDK/Go/stardust
+
+go 1.18
Added +97 -0
diff --git a/SDK/Go/stardust/README.md b/SDK/Go/stardust/README.md
new file mode 100644
index 0000000..8947b6c
--- /dev/null
+++ b/SDK/Go/stardust/README.md
@@ -0,0 +1,97 @@
+# Stardust Go SDK
+
+星尘监控(Stardust)Go SDK,提供 APM 监控和配置中心的接入能力。
+
+## 特性
+
+- ✅ APM 链路追踪
+- ✅ 配置中心
+- ✅ 无第三方依赖,仅使用 Go 标准库
+- ✅ 支持 Go 1.18+
+
+## 安装
+
+```bash
+go get github.com/NewLifeX/Stardust/SDK/Go/stardust
+```
+
+## APM 监控快速开始
+
+```go
+package main
+
+import (
+    "github.com/NewLifeX/Stardust/SDK/Go/stardust"
+)
+
+func main() {
+    tracer := stardust.NewTracer("http://star.example.com:6600", "MyGoApp", "MySecret")
+    tracer.Start()
+    defer tracer.Stop()
+
+    // 手动埋点
+    span := tracer.NewSpan("业务操作", "")
+    span.Tag = "参数信息"
+    doSomething()
+    span.Finish()
+}
+```
+
+## 配置中心快速开始
+
+```go
+package main
+
+import (
+    "fmt"
+    "github.com/NewLifeX/Stardust/SDK/Go/stardust"
+)
+
+func main() {
+    config := stardust.NewConfigClient("http://star.example.com:6600", "MyGoApp", "MySecret")
+    config.Start()
+    defer config.Stop()
+
+    // 获取配置
+    value := config.Get("database.host")
+    fmt.Println("Database Host:", value)
+
+    // 监听配置变更
+    config.OnChange(func(configs map[string]string) {
+        fmt.Println("配置已更新:", configs)
+    })
+
+    select {}
+}
+```
+
+## 完整文档
+
+详细文档请参考:[/Doc/SDK/stardust-sdk-go.md](../../../Doc/SDK/stardust-sdk-go.md)
+
+## 框架集成
+
+### Gin 框架
+
+```go
+import (
+    "github.com/gin-gonic/gin"
+    "github.com/NewLifeX/Stardust/SDK/Go/stardust"
+)
+
+var tracer = stardust.NewTracer("http://star.example.com:6600", "MyGinApp", "secret")
+
+func StardustMiddleware() gin.HandlerFunc {
+    return func(c *gin.Context) {
+        name := c.Request.Method + " " + c.Request.URL.Path
+        span := tracer.NewSpan(name, "")
+        span.Tag = c.Request.Method + " " + c.Request.RequestURI
+        defer span.Finish()
+        c.Next()
+    }
+}
+```
+
+## License
+
+MIT License
Added +183 -0
diff --git a/SDK/Go/stardust/stardust_test.go b/SDK/Go/stardust/stardust_test.go
new file mode 100644
index 0000000..614b936
--- /dev/null
+++ b/SDK/Go/stardust/stardust_test.go
@@ -0,0 +1,183 @@
+package stardust
+
+import (
+	"testing"
+	"time"
+)
+
+func TestNewTracer(t *testing.T) {
+	tracer := NewTracer("http://localhost:6600", "TestApp", "TestSecret")
+	if tracer == nil {
+		t.Fatal("NewTracer returned nil")
+	}
+	if tracer.AppID != "TestApp" {
+		t.Errorf("Expected AppID=TestApp, got %s", tracer.AppID)
+	}
+	if tracer.Secret != "TestSecret" {
+		t.Errorf("Expected Secret=TestSecret, got %s", tracer.Secret)
+	}
+	if tracer.Period != 60 {
+		t.Errorf("Expected Period=60, got %d", tracer.Period)
+	}
+}
+
+func TestSpan(t *testing.T) {
+	tracer := NewTracer("http://localhost:6600", "TestApp", "TestSecret")
+
+	span := tracer.NewSpan("TestOperation", "")
+	if span == nil {
+		t.Fatal("NewSpan returned nil")
+	}
+	if span.name != "TestOperation" {
+		t.Errorf("Expected name=TestOperation, got %s", span.name)
+	}
+	if span.ID == "" {
+		t.Error("Span ID should not be empty")
+	}
+	if span.TraceID == "" {
+		t.Error("Span TraceID should not be empty")
+	}
+
+	span.Tag = "test tag"
+	span.Finish()
+
+	if span.EndTime == 0 {
+		t.Error("Span EndTime should not be zero after Finish()")
+	}
+	if span.EndTime < span.StartTime {
+		t.Error("Span EndTime should be greater than StartTime")
+	}
+}
+
+func TestSpanBuilder(t *testing.T) {
+	builder := newSpanBuilder("TestOp", 10, 5)
+	if builder == nil {
+		t.Fatal("newSpanBuilder returned nil")
+	}
+	if builder.Name != "TestOp" {
+		t.Errorf("Expected Name=TestOp, got %s", builder.Name)
+	}
+
+	// 添加正常 span
+	span1 := &Span{
+		StartTime: time.Now().UnixMilli(),
+		EndTime:   time.Now().UnixMilli() + 100,
+		Error:     "",
+	}
+	builder.addSpan(span1)
+
+	if builder.Total != 1 {
+		t.Errorf("Expected Total=1, got %d", builder.Total)
+	}
+	if builder.Errors != 0 {
+		t.Errorf("Expected Errors=0, got %d", builder.Errors)
+	}
+
+	// 添加错误 span
+	span2 := &Span{
+		StartTime: time.Now().UnixMilli(),
+		EndTime:   time.Now().UnixMilli() + 200,
+		Error:     "test error",
+	}
+	builder.addSpan(span2)
+
+	if builder.Total != 2 {
+		t.Errorf("Expected Total=2, got %d", builder.Total)
+	}
+	if builder.Errors != 1 {
+		t.Errorf("Expected Errors=1, got %d", builder.Errors)
+	}
+	if len(builder.ErrorSamples) != 1 {
+		t.Errorf("Expected 1 error sample, got %d", len(builder.ErrorSamples))
+	}
+}
+
+func TestNewConfigClient(t *testing.T) {
+	config := NewConfigClient("http://localhost:6600", "TestApp", "TestSecret")
+	if config == nil {
+		t.Fatal("NewConfigClient returned nil")
+	}
+	if config.AppID != "TestApp" {
+		t.Errorf("Expected AppID=TestApp, got %s", config.AppID)
+	}
+	if config.Secret != "TestSecret" {
+		t.Errorf("Expected Secret=TestSecret, got %s", config.Secret)
+	}
+}
+
+func TestConfigClientGetSet(t *testing.T) {
+	config := NewConfigClient("http://localhost:6600", "TestApp", "TestSecret")
+
+	// 手动设置配置(模拟从服务器获取)
+	config.mu.Lock()
+	config.configs["test.key"] = "test.value"
+	config.configs["database.host"] = "localhost"
+	config.mu.Unlock()
+
+	// 测试 Get
+	value := config.Get("test.key")
+	if value != "test.value" {
+		t.Errorf("Expected test.value, got %s", value)
+	}
+
+	// 测试不存在的 key
+	empty := config.Get("nonexistent")
+	if empty != "" {
+		t.Errorf("Expected empty string, got %s", empty)
+	}
+
+	// 测试 GetAll
+	all := config.GetAll()
+	if len(all) != 2 {
+		t.Errorf("Expected 2 configs, got %d", len(all))
+	}
+	if all["database.host"] != "localhost" {
+		t.Errorf("Expected localhost, got %s", all["database.host"])
+	}
+}
+
+func TestRandomHex(t *testing.T) {
+	hex1 := randomHex(8)
+	hex2 := randomHex(8)
+
+	if len(hex1) != 16 { // 8 bytes = 16 hex chars
+		t.Errorf("Expected length 16, got %d", len(hex1))
+	}
+	if hex1 == hex2 {
+		t.Error("randomHex should generate different values")
+	}
+}
+
+func TestGetLocalIP(t *testing.T) {
+	ip := getLocalIP()
+	if ip == "" {
+		t.Error("getLocalIP should not return empty string")
+	}
+	// 应该返回 IP 地址格式,至少不是空
+	if len(ip) < 7 { // 最短的 IP: 0.0.0.0
+		t.Errorf("IP address too short: %s", ip)
+	}
+}
+
+func TestContainsIgnoreCase(t *testing.T) {
+	tests := []struct {
+		s      string
+		substr string
+		want   bool
+	}{
+		{"HelloWorld", "world", true},
+		{"HelloWorld", "WORLD", true},
+		{"HelloWorld", "hello", true},
+		{"HelloWorld", "xyz", false},
+		{"", "test", false},
+		{"test", "", true},
+	}
+
+	for _, tt := range tests {
+		got := containsIgnoreCase(tt.s, tt.substr)
+		if got != tt.want {
+			t.Errorf("containsIgnoreCase(%q, %q) = %v, want %v",
+				tt.s, tt.substr, got, tt.want)
+		}
+	}
+}
Added +456 -0
diff --git a/SDK/Go/stardust/tracer.go b/SDK/Go/stardust/tracer.go
new file mode 100644
index 0000000..b2896a4
--- /dev/null
+++ b/SDK/Go/stardust/tracer.go
@@ -0,0 +1,456 @@
+package stardust
+
+import (
+	"bytes"
+	"compress/gzip"
+	"crypto/rand"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net"
+	"net/http"
+	"net/url"
+	"os"
+	"sync"
+	"time"
+)
+
+// Span 追踪片段
+type Span struct {
+	ID        string `json:"Id"`
+	ParentID  string `json:"ParentId"`
+	TraceID   string `json:"TraceId"`
+	StartTime int64  `json:"StartTime"`
+	EndTime   int64  `json:"EndTime"`
+	Tag       string `json:"Tag"`
+	Error     string `json:"Error"`
+
+	name   string
+	tracer *Tracer
+}
+
+// SetError 设置错误
+func (s *Span) SetError(err error) {
+	if err != nil {
+		s.Error = err.Error()
+	}
+}
+
+// Finish 结束片段
+func (s *Span) Finish() {
+	s.EndTime = time.Now().UnixMilli()
+	if s.tracer != nil {
+		s.tracer.finishSpan(s)
+	}
+}
+
+// SpanBuilder 构建器,按操作名聚合
+type SpanBuilder struct {
+	Name         string  `json:"Name"`
+	StartTime    int64   `json:"StartTime"`
+	EndTime      int64   `json:"EndTime"`
+	Total        int     `json:"Total"`
+	Errors       int     `json:"Errors"`
+	Cost         int64   `json:"Cost"`
+	MaxCost      int     `json:"MaxCost"`
+	MinCost      int     `json:"MinCost"`
+	Samples      []*Span `json:"Samples"`
+	ErrorSamples []*Span `json:"ErrorSamples"`
+
+	maxSamples int
+	maxErrors  int
+	mu         sync.Mutex
+}
+
+func newSpanBuilder(name string, maxSamples, maxErrors int) *SpanBuilder {
+	return &SpanBuilder{
+		Name:         name,
+		StartTime:    time.Now().UnixMilli(),
+		Samples:      make([]*Span, 0),
+		ErrorSamples: make([]*Span, 0),
+		maxSamples:   maxSamples,
+		maxErrors:    maxErrors,
+	}
+}
+
+func (b *SpanBuilder) addSpan(span *Span) {
+	elapsed := int(span.EndTime - span.StartTime)
+
+	b.mu.Lock()
+	defer b.mu.Unlock()
+
+	b.Total++
+	b.Cost += int64(elapsed)
+	if b.MaxCost == 0 || elapsed > b.MaxCost {
+		b.MaxCost = elapsed
+	}
+	if b.MinCost == 0 || elapsed < b.MinCost {
+		b.MinCost = elapsed
+	}
+
+	if span.Error != "" {
+		b.Errors++
+		if len(b.ErrorSamples) < b.maxErrors {
+			b.ErrorSamples = append(b.ErrorSamples, span)
+		}
+	} else {
+		if len(b.Samples) < b.maxSamples {
+			b.Samples = append(b.Samples, span)
+		}
+	}
+	b.EndTime = time.Now().UnixMilli()
+}
+
+// TraceModel 上报请求体
+type TraceModel struct {
+	AppID    string         `json:"AppId"`
+	AppName  string         `json:"AppName"`
+	ClientID string         `json:"ClientId"`
+	Version  string         `json:"Version,omitempty"`
+	Builders []*SpanBuilder `json:"Builders"`
+}
+
+// TraceResponse 上报响应
+type TraceResponse struct {
+	Period           int      `json:"Period"`
+	MaxSamples       int      `json:"MaxSamples"`
+	MaxErrors        int      `json:"MaxErrors"`
+	Timeout          int      `json:"Timeout"`
+	MaxTagLength     int      `json:"MaxTagLength"`
+	RequestTagLength int      `json:"RequestTagLength"`
+	EnableMeter      *bool    `json:"EnableMeter"`
+	Excludes         []string `json:"Excludes"`
+}
+
+// APIResponse 通用响应
+type APIResponse struct {
+	Code int             `json:"code"`
+	Data json.RawMessage `json:"data"`
+}
+
+// LoginResponse 登录响应
+type LoginResponse struct {
+	Code       string `json:"Code"`
+	Secret     string `json:"Secret"`
+	Name       string `json:"Name"`
+	Token      string `json:"Token"`
+	Expire     int    `json:"Expire"`
+	ServerTime int64  `json:"ServerTime"`
+}
+
+// PingResponse 心跳响应
+type PingResponse struct {
+	Time       int64  `json:"Time"`
+	ServerTime int64  `json:"ServerTime"`
+	Period     int    `json:"Period"`
+	Token      string `json:"Token"`
+}
+
+// Tracer 星尘追踪器
+type Tracer struct {
+	Server   string
+	AppID    string
+	AppName  string
+	Secret   string
+	ClientID string
+
+	// 采样参数
+	Period      int
+	MaxSamples  int
+	MaxErrors   int
+	Timeout     int
+	MaxTagLen   int
+	Excludes    []string
+	EnableMeter bool
+
+	token       string
+	tokenExpire int64
+
+	builders map[string]*SpanBuilder
+	mu       sync.Mutex
+	running  bool
+	stopCh   chan struct{}
+	client   *http.Client
+}
+
+// NewTracer 创建追踪器
+func NewTracer(server, appID, secret string) *Tracer {
+	return &Tracer{
+		Server:      server,
+		AppID:       appID,
+		AppName:     appID,
+		Secret:      secret,
+		ClientID:    fmt.Sprintf("%s@%d", getLocalIP(), os.Getpid()),
+		Period:      60,
+		MaxSamples:  1,
+		MaxErrors:   10,
+		Timeout:     5000,
+		MaxTagLen:   1024,
+		EnableMeter: true,
+		builders:    make(map[string]*SpanBuilder),
+		stopCh:      make(chan struct{}),
+		client:      &http.Client{Timeout: 10 * time.Second},
+	}
+}
+
+// Start 启动追踪器
+func (t *Tracer) Start() {
+	t.login()
+	t.running = true
+	go t.reportLoop()
+	go t.pingLoop()
+}
+
+// Stop 停止追踪器
+func (t *Tracer) Stop() {
+	t.running = false
+	close(t.stopCh)
+	t.flush()
+}
+
+// NewSpan 创建追踪片段
+func (t *Tracer) NewSpan(name, parentID string) *Span {
+	return &Span{
+		ID:        randomHex(8),
+		ParentID:  parentID,
+		TraceID:   randomHex(16),
+		StartTime: time.Now().UnixMilli(),
+		name:      name,
+		tracer:    t,
+	}
+}
+
+func (t *Tracer) finishSpan(span *Span) {
+	// 排除自身
+	if span.name == "/Trace/Report" || span.name == "/Trace/ReportRaw" {
+		return
+	}
+	for _, exc := range t.Excludes {
+		if exc != "" && containsIgnoreCase(span.name, exc) {
+			return
+		}
+	}
+
+	// 截断 Tag
+	if len(span.Tag) > t.MaxTagLen {
+		span.Tag = span.Tag[:t.MaxTagLen]
+	}
+
+	t.mu.Lock()
+	defer t.mu.Unlock()
+
+	builder, ok := t.builders[span.name]
+	if !ok {
+		builder = newSpanBuilder(span.name, t.MaxSamples, t.MaxErrors)
+		t.builders[span.name] = builder
+	}
+	builder.addSpan(span)
+}
+
+func (t *Tracer) login() {
+	payload := map[string]interface{}{
+		"AppId":    t.AppID,
+		"Secret":   t.Secret,
+		"ClientId": t.ClientID,
+		"AppName":  t.AppName,
+	}
+
+	body, err := json.Marshal(payload)
+	if err != nil {
+		return
+	}
+
+	resp, err := t.client.Post(t.Server+"/App/Login", "application/json", bytes.NewReader(body))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "[Stardust] Login failed: %v\n", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	var apiResp APIResponse
+	if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil || apiResp.Code != 0 {
+		return
+	}
+
+	var loginResp LoginResponse
+	if err := json.Unmarshal(apiResp.Data, &loginResp); err != nil {
+		return
+	}
+
+	t.token = loginResp.Token
+	if loginResp.Expire > 0 {
+		t.tokenExpire = time.Now().Unix() + int64(loginResp.Expire)
+	}
+	if loginResp.Code != "" {
+		t.AppID = loginResp.Code
+	}
+	if loginResp.Secret != "" {
+		t.Secret = loginResp.Secret
+	}
+}
+
+func (t *Tracer) ping() {
+	payload := map[string]interface{}{
+		"Id":   os.Getpid(),
+		"Name": t.AppName,
+		"Time": time.Now().UnixMilli(),
+	}
+
+	body, _ := json.Marshal(payload)
+	reqURL := fmt.Sprintf("%s/App/Ping?Token=%s", t.Server, url.QueryEscape(t.token))
+
+	resp, err := t.client.Post(reqURL, "application/json", bytes.NewReader(body))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "[Stardust] Ping failed: %v\n", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	var apiResp APIResponse
+	if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil || apiResp.Code != 0 {
+		return
+	}
+
+	var pingResp PingResponse
+	if err := json.Unmarshal(apiResp.Data, &pingResp); err != nil {
+		return
+	}
+
+	if pingResp.Token != "" {
+		t.token = pingResp.Token
+	}
+}
+
+func (t *Tracer) report(buildersData []*SpanBuilder) {
+	model := TraceModel{
+		AppID:    t.AppID,
+		AppName:  t.AppName,
+		ClientID: t.ClientID,
+		Builders: buildersData,
+	}
+
+	body, err := json.Marshal(model)
+	if err != nil {
+		return
+	}
+
+	var resp *http.Response
+	if len(body) > 1024 {
+		// Gzip 压缩
+		var buf bytes.Buffer
+		gz := gzip.NewWriter(&buf)
+		gz.Write(body)
+		gz.Close()
+
+		reqURL := fmt.Sprintf("%s/Trace/ReportRaw?Token=%s", t.Server, url.QueryEscape(t.token))
+		resp, err = t.client.Post(reqURL, "application/x-gzip", &buf)
+	} else {
+		reqURL := fmt.Sprintf("%s/Trace/Report?Token=%s", t.Server, url.QueryEscape(t.token))
+		resp, err = t.client.Post(reqURL, "application/json", bytes.NewReader(body))
+	}
+
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "[Stardust] Report failed: %v\n", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	respBody, _ := io.ReadAll(resp.Body)
+	var apiResp APIResponse
+	if err := json.Unmarshal(respBody, &apiResp); err != nil || apiResp.Code != 0 {
+		return
+	}
+
+	var traceResp TraceResponse
+	if err := json.Unmarshal(apiResp.Data, &traceResp); err != nil {
+		return
+	}
+
+	if traceResp.Period > 0 {
+		t.Period = traceResp.Period
+	}
+	if traceResp.MaxSamples > 0 {
+		t.MaxSamples = traceResp.MaxSamples
+	}
+	if traceResp.MaxErrors > 0 {
+		t.MaxErrors = traceResp.MaxErrors
+	}
+	if traceResp.Timeout > 0 {
+		t.Timeout = traceResp.Timeout
+	}
+	if traceResp.MaxTagLength > 0 {
+		t.MaxTagLen = traceResp.MaxTagLength
+	}
+	if traceResp.Excludes != nil {
+		t.Excludes = traceResp.Excludes
+	}
+}
+
+func (t *Tracer) flush() {
+	t.mu.Lock()
+	if len(t.builders) == 0 {
+		t.mu.Unlock()
+		return
+	}
+	list := make([]*SpanBuilder, 0, len(t.builders))
+	for _, b := range t.builders {
+		if b.Total > 0 {
+			list = append(list, b)
+		}
+	}
+	t.builders = make(map[string]*SpanBuilder)
+	t.mu.Unlock()
+
+	if len(list) > 0 {
+		t.report(list)
+	}
+}
+
+func (t *Tracer) reportLoop() {
+	ticker := time.NewTicker(time.Duration(t.Period) * time.Second)
+	defer ticker.Stop()
+	for {
+		select {
+		case <-ticker.C:
+			t.flush()
+		case <-t.stopCh:
+			return
+		}
+	}
+}
+
+func (t *Tracer) pingLoop() {
+	ticker := time.NewTicker(30 * time.Second)
+	defer ticker.Stop()
+	for {
+		select {
+		case <-ticker.C:
+			t.ping()
+		case <-t.stopCh:
+			return
+		}
+	}
+}
+
+// ========== 工具函数 ==========
+
+func randomHex(n int) string {
+	b := make([]byte, n)
+	rand.Read(b)
+	return hex.EncodeToString(b)
+}
+
+func getLocalIP() string {
+	conn, err := net.Dial("udp", "8.8.8.8:80")
+	if err != nil {
+		return "127.0.0.1"
+	}
+	defer conn.Close()
+	return conn.LocalAddr().(*net.UDPAddr).IP.String()
+}
+
+func containsIgnoreCase(s, substr string) bool {
+	return len(s) >= len(substr) &&
+		bytes.Contains(bytes.ToLower([]byte(s)), bytes.ToLower([]byte(substr)))
+}