Golang怎么在编译时注入版别信息

问题

一般来说,稍微做得好一点的开源软件,其二进制文件都会带上版别信息,比方Docker,你能够通过以下指令来检查:

$ docker --version
Docker version 20.10.17, build 100c701
$ docker version
...
//输出太长,这儿就不展现了

有些Web使用还提供了/version接口,你能够通过接口来获取:

$ curl http://127.0.0.1:8080/version | jq
{
  "Build Time": "Fri, 16 Jun 2023 16:19:23 +0800",
  "Git Commit": "b554659",
  "Go Version": "go1.19",
  "OS/Arch": "darwin/amd64",
  "version": "0.9.2"
}

但是许多公司并没有这方面的强制性标准,大都开发人员也没有这样的意识或习惯,所以大部分项目并没有完结这个功用。如果二进制文件不带上版别信息,你可能要凭借其它系统来查询或追溯,这无疑是一件难过的事,关于代码调试和问题定位很不友爱。

那么在Golang中,怎么完结这个功用呢?

思路

版别信息有许多,比方代码版别号、Git提交号、编译时刻、Go版别号等,怎么让二进制文件带上这些信息呢?

硬编码在代码里肯定不可。因为Git提交号是将代码提交到代码仓库时才产生,而Go版别号和编译时刻只有在编译时才干拿到。

比较合理的做法是在编译时注入。Go编译东西提供了-ldflags选项,通过-X参数能够注入包变量的值,咱们只需要在代码中将版别信息定义成包变量,然后在编译时完结注入即可。这个过程能够完结为自动化完结,下面以make东西为例阐明。

完结步骤

1. 在代码中定义版别信息的变量

将代码版别号、Git提交号、编译时刻定义为包变量:

package config
var (
	Version   string  //代码版别号
	GitCommit string  //Git提交号
	BuildTime string  //编译时刻
)

Go版别号等信息能够凭借runtime包获取:

runtime.Version()  //Go版别
runtime.GOOS       //操作系统
runtime.GOARCH     //平台架构
2. 在代码中使用这些变量

1)完结--version参数或version子指令

Cobra指令行框架为例,为rootCmd完结--version参数:

rootCmd.Version = config.Version
rootCmd.SetVersionTemplate(fmt.Sprintf(`{{with .Name}}{{printf "%%s version information: " .}}{{end}}
    {{printf "Version:    %%s" .Version}}
    Git Commit: %s
    Build Time: %s
    Go version: %s
    OS/Arch:    %s/%s
`, config.GitCommit, config.BuildTime, runtime.Version(), runtime.GOOS, runtime.GOARCH))

2)完结/version接口

Gin框架为例,将/version路由到如下的Version函数:

func Version(ctx *gin.Context) {
	ctx.Writer.Header().Set("Content-Type", "application/json")
	ctx.Writer.WriteHeader(http.StatusOK)
	json.NewEncoder(ctx.Writer).Encode(map[string]string{
		"version":    config.Version,
		"Git Commit": config.GitCommit,
		"Build Time": config.BuildTime,
		"Go Version": runtime.Version(),
		"OS/Arch":    runtime.GOOS + "/" + runtime.GOARCH,
	})
}
3. 在编译前获取版别信息

Makefile中,用git指令获取代码版别号、Git提交号,用date指令获取当前时刻

VERSION    = $(shell git describe --tags --always)
GIT_COMMIT = $(shell git rev-parse --short HEAD)
BUILD_TIME = $(shell date -R)
4. 在编译时注入版别信息

Makefile中,通过-ldflags选项为go build指令传入编译参数,完结版别信息的注入

define LDFLAGS
"-X 'github.com/myname/myapp/config.Version=${VERSION}' \
-X 'github.com/myname/myapp/config.GitCommit=${GIT_COMMIT}' \
-X 'github.com/myname/myapp/config.BuildTime=${BUILD_TIME}'"
endef
build:
    go build -ldflags ${LDFLAGS} -o myapp_${VERSION} .
5. 验证效果

编译出二进制,即可验证--version参数;用二进制发动服务进程,即可验证/version接口。

$ make build
...
//编译过程,省掉输出

$ myapp --version
myapp version information: 
    Version:    0.9.2
    Git Commit: b554659
    Build Time: Sat, 17 Jun 2023 11:30:44 +0800
    Go version: go1.20.5
    OS/Arch:    darwin/arm64

$ myapp
...
//发动进程,省掉输出

$ curl http://127.0.0.1:8080/version | jq
{
  "Build Time": "Sat, 17 Jun 2023 11:30:44 +0800",
  "Git Commit": "b554659",
  "Go Version": "go1.20.5",
  "OS/Arch": "darwin/arm64",
  "version": "0.9.2"
}

总结

二进制文件直接带上版别信息,能为代码调试和问题定位带来很大的方便。本文介绍了通过-ldflags选项完结在编译时注入版别信息的具体做法。

这是一个比较通用的方法,许多开源软件比方 Docker 也是这样做的。