Go 小服务可观测性入门:日志、指标和健康检查先做到位

本文讲解 Go 小型 HTTP 服务如何先做好基础可观测性,包括请求日志、健康检查、简单指标和错误边界。

小服务也需要知道自己发生了什么

可观测性听起来像大系统话题,容易让初学者联想到复杂的指标平台、分布式追踪和日志集群。其实对一个 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 服务时,就可以把这些基础点放进去,让服务从一开始就更容易理解和维护。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页