← 返回 stdlib

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