概述

为了增强 Golang 程序的可观测性,方便定位问题,我们往往会在代码中集成链路追踪 (tracing) 的能力,Jaeger 是当今比较主流的选择,而 tracing 相关的 API 如今都抽象到了 OpenTelemetry 项目中,涵盖各种实现,也包括 Jaeger 在内。
req 是一款功能非常强大且易用的 Golang HTTP 请求库,利用 req 强大的中间件能力,可以轻松为我们涉及 HTTP 调用的代码统一集成链路追踪的能力,且能以最少的代码量进行扩展。
本文将给出一个可运行的程序示例:输入一个 GitHub 用户名,展示用户的简短介绍,包含名字、网站地址以及该用户下的最火开源项目与 star 数量,期间涉及的函数与 API 调用链路追踪信息均上报至 Jaeger,进行可视化展示。
主要包含以下特点:

RequestMiddlewareResponseMiddleware
go mod initgithubgithub.go
Client*req.ClientSetCommonHeaderSetBaseURLAcceptSetCommonErrorAPIErrorAPIErrorOnAfterResponseResponseMiddlewareresp.ErrOnBeforeRequestRequestMiddlewareResponseMiddlewareresp.Err
Client
Client.SetTracerClient*req.ClientWrapRoundTripFuncrt.RoundTrip(req)defer span.End()
GetUserProfile
GetGETClient.SetCommonBaseURLusernameSetPathParamUserProfileSetResultwithAPINameDoDo*req.ResponseErrerr
ListUserRepo
SetQueryParamsAnyType

可以看到,后续我们每次对接新的 API 都变得非常轻松,因为利用了 req 的中间件能力,对异常与链路追踪都进行了统一处理,对接 API 时,只需传入 API 必要的参数与响应体结构类型即可,没有一点多余的代码,非常直观和简洁。
好了,作为示例我们就只对接这两个 API 就够了,我们还可以再为 Client 增加一些实用的小功能:
// LoginWithToken login with GitHub personal access token.
// GitHub API doc: https://docs.github.com/en/rest/overview/other-authentication-methods#authenticating-for-saml-sso
func (c *Client) LoginWithToken(token string) *Client {
c.SetCommonHeader("Authorization", "token "+token)
return c
}

// SetDebug enable debug if set to true, disable debug if set to false.
func (c *Client) SetDebug(enable bool) *Client {
if enable {
c.EnableDebugLog()
c.EnableDumpAll()
} else {
c.DisableDebugLog()
c.DisableDumpAll()
}
return c
}

LoginWithTokenSetDebug
main.go
serviceNamegithub-querygithubClient
TracerProvidertraceProviderTracerProvider
JAEGER_ENDPOINTserviceName
QueryUser
QueryUserQueryUserfindMostPopularRepo

主要的实现函数准备就绪,现在我们来写 main 函数:
func main() {
tp, err := traceProvider()
if err != nil {
panic(err)
}
otel.SetTracerProvider(tp)

githubClient = github.NewClient()
if os.Getenv("DEBUG") == "on" {
githubClient.SetDebug(true)
}
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
githubClient.LoginWithToken(token)
}
githubClient.SetTracer(otel.Tracer("github"))

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)
go func() {
sig := <-sigs
fmt.Printf("Caught %s, shutting down\n", sig)
if err := tp.Shutdown(context.Background()); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()

for {
var name string
fmt.Printf("Please give a github username: ")
_, err := fmt.Fscanf(os.Stdin, "%s\n", &name)
if err != nil {
panic(err)
}
err = QueryUser(name)
if err != nil {
fmt.Println(err.Error())
}
}
}

traceProvider()TraceProviderotel.SetTracerProvider(tp)otel.Tracer(xx)github.NewClient()githubClientDEBUG=onGITHUB_TOKENgithubClient.SetTracer(otel.Tracer("github"))gihtubSIGTERMSIGTNTTraceProviderTraceProviderQueryUser
go run .spf13
ListUserRepoQueryUser
GetUserProfile



不断输入其它 username 测试,经过多次后可能会因 GitHub 的 API 限频导致异常:
$ go run .
Please give a github username: spf13
API error: API rate limit exceeded for 43.132.98.44. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.) (see doc https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting)

检查下 Jaeger UI,可以看到很详细很显眼的错误信息:


此时,你可以将你的 GitHub 账号 personal access token 写到环境变量来避免被限频:
export GITHUB_TOKEN=*******

尝试输入一个不存在的用户:
$ go run .
Please give a github username: kjtlejkdglfjsadhfajfsa
API error: Not Found (see doc https://docs.github.com/rest/reference/users#get-a-user)

检查下 Jaeger UI,同样的也可以看到详细的错误信息:


如果断开公网测试,可能会报 dns 解析失败的错:
$ go run .
Please give a github username: imroc
Get "https://api.github.com/users/imroc": dial tcp: lookup api.github.com: no such host


或者连接超时的错:
$ go run .
Please give a github username: spf13
Get "https://api.github.com/users/spf13": dial tcp 20.205.243.168:443: connect: operation timed out


完整代码
本文涉及的完整代码已放入 req 官方 examples 下的 opentelemetry-jaeger-tracing 目录。
总结
如果业务程序中需要调用其它服务的 API,我们可以利用 req 强大的中间件能力,统一处理所有请求的异常,统一记录所有请求详细信息到 Tracing 系统,写出健壮、可观测性强且极易扩展的 SDK 与业务代码。
相关链接