小服务也需要知道自己发生了什么
可观测性听起来像大系统话题,容易让初学者联想到复杂的指标平台、分布式追踪和日志集群。其实对一个 Go 小服务来说,最基础的可观测性很朴素:服务是否还活着?请求进来了多少?哪些请求失败?外部调用耗时多久?启动时用了什么配置?
如果这些信息都没有,服务出问题时只能靠猜。Go 标准库已经足够做出基础版本:健康检查接口、请求日志、简单计数器、错误日志和启动摘要。等项目变大,再接 Prometheus、OpenTelemetry 或日志平台也不迟。
这篇文章用标准库写一个小服务的基础可观测性。
健康检查
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]string{
"status": "ok",
})
}
注册:
mux.HandleFunc("/healthz", healthHandler)
健康检查应该轻量,不要每次都做昂贵操作。基础存活检查返回 ok 即可。如果要检查数据库,可以另做 readiness 接口,避免一个慢依赖让存活检查本身变重。
请求日志
func RequestLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("method=%s path=%s duration=%s",
r.Method, r.URL.Path, time.Since(start))
})
}
更完整地记录状态码:
type statusRecorder struct {
http.ResponseWriter
status int
}
func (r *statusRecorder) WriteHeader(status int) {
r.status = status
r.ResponseWriter.WriteHeader(status)
}
中间件:
func RequestLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(rec, r)
log.Printf("method=%s path=%s status=%d duration_ms=%d",
r.Method, r.URL.Path, rec.status, time.Since(start).Milliseconds())
})
}
日志字段要稳定。即使用普通 log.Printf,也可以写成 key=value 格式,方便搜索。
简单指标
用 atomic 计数:
type Metrics struct {
requests atomic.Int64
errors atomic.Int64
}
func (m *Metrics) Count(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m.requests.Add(1)
next.ServeHTTP(w, r)
})
}
func (m *Metrics) Handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "requests %d\n", m.requests.Load())
fmt.Fprintf(w, "errors %d\n", m.errors.Load())
}
注册:
metrics := &Metrics{}
mux.Handle("/api/", metrics.Count(apiHandler))
mux.HandleFunc("/metrics", metrics.Handler)
这不是完整 Prometheus 格式,但对小项目已经能回答“请求量是多少”。后续接监控系统时,可以替换实现。
启动摘要
服务启动时打印关键配置:
log.Printf("starting service addr=%s env=%s version=%s",
cfg.Addr, cfg.Env, version)
不要打印密钥。可以打印是否设置:
log.Printf("third_party_api_key_set=%v", cfg.APIKey != "")
启动日志能帮助你确认服务是否读取了正确配置。很多线上问题其实是端口、环境变量或数据路径配置错。
小结
Go 小服务的可观测性可以从四件事开始:健康检查、请求日志、简单指标、启动摘要。它们不复杂,但能显著降低排查成本。等项目复杂后,再引入结构化日志、Prometheus 指标和分布式追踪。
可观测性不是上线后补救才做的事情。你在写第一个 HTTP 服务时,就可以把这些基础点放进去,让服务从一开始就更容易理解和维护。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。