exp 实验性标准库
简介
exp(experimental)是 Go 语言的实验性标准库,包含了一些新功能和实验性的实现。这些库可能会在未来成为标准库的一部分,也可能被移除或修改。常用的 exp 库包括 expvar(变量监控)、slog(结构化日志)等。
expvar 变量监控
expvar 提供了一种标准化的方式来发布和监控运行时的变量,特别适合用于监控和调试。
基本使用
package main
import (
"expvar"
"fmt"
"net/http"
"time"
)
var (
requests = expvar.NewInt("requests")
errors = expvar.NewInt("errors")
connections = expvar.NewInt("connections")
)
func main() {
// 注册 HTTP 处理器
http.Handle("/debug/vars", expvar.Handler())
// 模拟请求处理
go func() {
for {
requests.Add(1)
time.Sleep(time.Second)
}
}()
fmt.Println("Server started on :8080")
fmt.Println("Visit http://localhost:8080/debug/vars to see metrics")
http.ListenAndServe(":8080", nil)
}
自定义变量
package main
import (
"expvar"
"fmt"
"sync"
)
// 自定义计数器
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) String() string {
c.mu.Lock()
defer c.mu.Unlock()
return fmt.Sprintf("%d", c.count)
}
func (c *Counter) Add(n int) {
c.mu.Lock()
defer c.mu.Unlock()
c.count += n
}
var customCounter = &Counter{}
func init() {
expvar.Publish("custom_counter", customCounter)
}
func main() {
customCounter.Add(1)
customCounter.Add(2)
// 获取变量值
val := expvar.Get("custom_counter")
fmt.Println("Custom counter:", val.String())
}
Map 类型
package main
import (
"expvar"
"fmt"
)
var (
// 创建 Map
stats = expvar.NewMap("stats")
)
func main() {
// 添加键值对
stats.Add("requests", 100)
stats.Add("errors", 5)
// 设置值
stats.Set("version", expvar.String("1.0.0"))
// 获取值
requests := stats.Get("requests")
fmt.Println("Requests:", requests.String())
// 遍历所有键
stats.Do(func(kv expvar.KeyValue) bool {
fmt.Printf("%s: %s\n", kv.Key, kv.Value.String())
return true
})
}
Float 类型
var (
cpuUsage = expvar.NewFloat("cpu_usage")
memory = expvar.NewFloat("memory_usage")
)
func updateMetrics() {
cpuUsage.Set(45.5)
memory.Set(1024.5)
}
String 类型
var (
version = expvar.NewString("version")
buildTime = expvar.NewString("build_time")
)
func init() {
version.Set("1.0.0")
buildTime.Set("2024-01-01 00:00:00")
}
在 Web 服务器中使用
package main
import (
"expvar"
"fmt"
"net/http"
"time"
)
var (
requestCount = expvar.NewInt("request_count")
errors = expvar.NewInt("errors")
avgTime = expvar.NewFloat("avg_time_ms")
)
func handler(w http.ResponseWriter, r *http.Request) {
start := time.Now()
requestCount.Add(1)
// 模拟处理
time.Sleep(time.Millisecond * 10)
// 计算平均时间
elapsed := time.Since(start).Milliseconds()
avgTime.Set(float64(elapsed))
fmt.Fprintln(w, "Hello, World!")
}
func main() {
// 注册 expvar 处理器
http.Handle("/debug/vars", expvar.Handler())
// 注册业务处理器
http.HandleFunc("/", handler)
fmt.Println("Server started on :8080")
fmt.Println("Metrics: http://localhost:8080/debug/vars")
http.ListenAndServe(":8080", nil)
}
slog 结构化日志
slog 是 Go 1.21+ 引入的结构化日志库,提供了结构化的日志记录功能,比传统的 log 包更强大和灵活。
基本使用
package main
import (
"log/slog"
"os"
)
func main() {
// 创建默认 logger
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// 记录不同级别的日志
logger.Debug("Debug message")
logger.Info("Info message")
logger.Warn("Warning message")
logger.Error("Error message")
}
结构化字段
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// 添加结构化字段
logger.Info("User logged in",
slog.String("user_id", "123"),
slog.String("username", "alice"),
slog.String("ip", "192.168.1.1"),
)
logger.Error("Request failed",
slog.String("path", "/api/users"),
slog.Int("status", 500),
slog.Duration("duration_ms", 150),
)
}
自定义 Handler
package main
import (
"context"
"encoding/json"
"log/slog"
"os"
)
// 自定义 Handler
type CustomHandler struct {
slog.Handler
}
func (h *CustomHandler) Handle(ctx context.Context, r slog.Record) error {
// 添加自定义字段
r.AddAttrs(slog.String("service", "myapp"))
return h.Handler.Handle(ctx, r)
}
func main() {
handler := &CustomHandler{
Handler: slog.NewJSONHandler(os.Stdout, nil),
}
logger := slog.New(handler)
logger.Info("Test message")
}
日志级别控制
package main
import (
"log/slog"
"os"
)
func main() {
// 设置日志级别
opts := &slog.HandlerOptions{
Level: slog.LevelInfo,
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, opts))
// Debug 级别的日志不会输出
logger.Debug("Debug message")
logger.Info("Info message")
}
上下文支持
package main
import (
"context"
"log/slog"
"os"
)
type contextKey struct{}
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// 在上下文中添加值
ctx := context.WithValue(context.Background(), contextKey{}, "request-123")
// 使用上下文记录日志
logger.InfoContext(ctx, "Processing request")
}
分组日志
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// 使用分组
logger.Info("User action",
slog.Group("user",
slog.String("id", "123"),
slog.String("name", "alice"),
),
slog.Group("request",
slog.String("path", "/api/users"),
slog.String("method", "GET"),
),
)
}
与 Gin 集成
package main
import (
"log/slog"
"net/http"
"github.com/gin-gonic/gin"
)
var logger = slog.New(slog.NewJSONHandler(os.Stdout, nil))
func main() {
r := gin.Default()
// 自定义中间件
r.Use(func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next()
logger.Info("Request",
slog.String("method", c.Request.Method),
slog.String("path", path),
slog.Int("status", c.Writer().Status()),
slog.Duration("duration", time.Since(start)),
)
})
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello, World!")
})
r.Run(":8080")
}
exp 最佳实践
1. expvar 使用建议
- 监控关键指标:监控请求计数、错误计数、连接数等关键指标
- 原子操作:使用 expvar.Int 和 expvar.Float 确保线程安全
- 合理命名:使用有意义的变量名,便于理解
- 定期清理:避免积累过多的监控数据
- 安全考虑:在生产环境中限制 /debug/vars 的访问权限
2. slog 使用建议
- 结构化字段:使用结构化字段而不是字符串拼接
- 日志级别:合理使用不同级别的日志
- 上下文传递:使用上下文传递请求相关信息
- 性能考虑:避免在高频路径中记录过多日志
- 日志轮转:配合日志轮转工具管理日志文件
3. 集成建议
// 在应用启动时初始化
func InitMonitoring() {
// 注册 expvar
expvar.Publish("version", expvar.String(Version))
expvar.Publish("build_time", expvar.String(BuildTime))
// 初始化 logger
opts := &slog.HandlerOptions{
Level: slog.LevelInfo,
}
logger = slog.New(slog.NewJSONHandler(os.Stdout, opts))
}