Get Name of Current Module in Go

huangapple go评论85阅读模式
英文:

Get Name of Current Module in Go

问题

我正在尝试为我编写的HTTP处理程序自动创建命名的日志记录器,其中我传递了一个函数(指针)。

我正在使用这个问题中提到的代码来获取函数的名称:

package utils

import (
	"reflect"
	"runtime"
)

func GetFunctionName(fn interface{}) string {
	value := reflect.ValueOf(fn)
	ptr := value.Pointer()
	ffp := runtime.FuncForPC(ptr)

	return ffp.Name()
}

我在我的main函数中使用它进行测试,代码如下:

package main

import (
	"github.com/naftulikay/golang-webapp/experiments/functionname/long"
	"github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path"
	"github.com/naftulikay/golang-webapp/experiments/functionname/utils"
	"log"
)

type Empty struct{}

func main() {
	a := long.HandlerA
	b := path.HandlerB
	c := path.HandlerC

	log.Printf("long.HandlerA: %s", utils.GetFunctionName(a))
	log.Printf("long.nested.path.HandlerB: %s", utils.GetFunctionName(b))
	log.Printf("long.nested.path.HandlerC: %s", utils.GetFunctionName(c))
}

我看到的输出如下:

github.com/naftulikay/golang-webapp/experiments/functionname/long.HandlerA

这样做没问题,但我希望输出结果为long.HandlerAlong.nested.path.HandlerB等。

如果我能获取到Go模块的名称(github.com/naftulikay/golang-webapp/experiments/functionname),我可以使用strings.Replace来删除模块名称,得到long/nested/path.HandlerB,然后再使用strings.Replace/替换为.,最终得到我想要的值long.nested.path.HandlerB

第一个问题是:有没有比runtime.FuncForPC(reflect.ValueOf(fn).Pointer())更好的方法来获取函数的限定路径?

如果答案是否定的,有没有办法使用runtimereflect来获取当前的Go模块名称,以便我可以将runtime.FuncForPC的输出转换为我需要的格式?

再次强调,我得到的值如下:

  • github.com/naftulikay/golang-webapp/experiments/functionname/long.HandlerA
  • github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path.HandlerB
  • github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path.HandlerC

我希望得到的值如下:

  • long.HandlerA
  • long.nested.path.HandlerB
  • long.nested.path.HandlerC

编辑: 看起来Go没有模块的运行时表示,这没关系,如果我可以在编译时做到这一点也可以。我看过代码生成文档,但很难弄清楚如何编写自己的自定义代码生成器,以便可以从go generate中使用。

英文:

I am attempting to create named loggers automatically for HTTP handlers that I'm writing, where I am passed a function (pointer).

I'm using the code mentioned in this question to get the name of a function:

package utils

import (
	"reflect"
	"runtime"
)

func GetFunctionName(fn interface{}) string {
	value := reflect.ValueOf(fn)
	ptr := value.Pointer()
	ffp := runtime.FuncForPC(ptr)

	return ffp.Name()
}

I'm using this in my main function to try it out like so:

package main

import (
	"github.com/naftulikay/golang-webapp/experiments/functionname/long"
	"github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path"
	"github.com/naftulikay/golang-webapp/experiments/functionname/utils"
	"log"
)

type Empty struct{}

func main() {
	a := long.HandlerA
	b := path.HandlerB
	c := path.HandlerC

	log.Printf("long.HandlerA: %s", utils.GetFunctionName(a))
	log.Printf("long.nested.path.HandlerB: %s", utils.GetFunctionName(b))
	log.Printf("long.nested.path.HandlerC: %s", utils.GetFunctionName(c))
}

I see output like this:

github.com/naftulikay/golang-webapp/experiments/functionname/long.HandlerA

This is okay but I'd like an output such as long.HandlerA, long.nested.path.HandlerB, etc.

If I could get the Go module name (github.com/naftulikay/golang-webapp/experiments/functionname), I can then use strings.Replace to remove the module name to arrive at long/nested/path.HandlerB, then strings.Replace to replace / with . to finally get to my desired value, which is long.nested.path.HandlerB.

The first question is: can I do better than runtime.FuncForPC(reflect.ValueOf(fn).Pointer()) for getting the qualified path to a function?

If the answer is no, is there a way to get the current Go module name using runtime or reflect so that I can transform the output of runtime.FuncForPC into what I need?

Once again, I'm getting values like:

  • github.com/naftulikay/golang-webapp/experiments/functionname/long.HandlerA
  • github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path.HandlerB
  • github.com/naftulikay/golang-webapp/experiments/functionname/long/nested/path.HandlerC

And I'd like to get values like:

  • long.HandlerA
  • long.nested.path.HandlerB
  • long.nested.path.HandlerC

EDIT: It appears that Go does not have a runtime representation of modules, and that's okay, if I can do it at compile time that would be fine too. I've seen the codegen documentation and I'm having a hard time figuring out how to write my own custom codegen that can be used from go generate.

答案1

得分: 5

模块信息包含在可执行二进制文件中,并可以使用debug.ReadBuildInfo()函数获取(唯一的要求是可执行文件必须使用模块支持构建,但这是当前版本的默认设置,也可能是未来版本的唯一设置)。

BuildInfo.Path是当前模块的路径。

假设你有以下的go.mod文件:

module example.com/foo

示例读取构建信息:

bi, ok := debug.ReadBuildInfo()
if !ok {
	log.Printf("Failed to read build info")
	return
}

fmt.Println(bi.Main.Path)
// 或者
fmt.Println(bi.Path)

这将输出(在Go Playground上尝试一下):

example.com/foo
example.com/foo

参考链接:https://stackoverflow.com/questions/62009264/golang-how-to-display-modules-version-from-inside-of-code/62009359#62009359

英文:

The module info is included in the executable binary, and can be acquired using the debug.ReadBuildInfo() function (the only requirement is that the executable must be built using module support, but this is the default in the current version, and likely the only in future versions).

BuildInfo.Path is the current module's path.

Let's say you have the following go.mod file:

module example.com/foo

Example reading the build info:

bi, ok := debug.ReadBuildInfo()
if !ok {
	log.Printf("Failed to read build info")
	return
}

fmt.Println(bi.Main.Path)
// or
fmt.Println(bi.Path)

This will output (try it on the Go Playground):

example.com/foo
example.com/foo

See related: https://stackoverflow.com/questions/62009264/golang-how-to-display-modules-version-from-inside-of-code/62009359#62009359

答案2

得分: 3

如果你的目标只是在程序中使用模块的名称,并且如果你可以接受在链接时设置该值,那么你可以使用-ldflags构建选项。

你可以使用go list -m命令在模块目录中获取模块的名称。

你可以将所有内容放在一个Makefile或者一个shell脚本中:

MOD_NAME=$(go list -m)
go build -ldflags="-X 'main.MODNAME=$MOD_NAME'" -o main ./...

其中main.go的内容如下:

package main

import "fmt"

var MODNAME string

func main() {
	fmt.Println(MODNAME) // example.com
}

使用提到的golang.org/x/mod/modfile包,一个示例可能如下:

package main

import (
    "fmt"
	"golang.org/x/mod/modfile"
	_ "embed"
)

//go:embed go.mod
var gomod []byte

func main() {
	f, err := modfile.Parse("go.mod", gomod, nil)
	if err != nil {
		panic(err)
	}
	fmt.Println(f.Module.Mod.Path) // example.com
}

然而,在你的使用情况下,将整个go.mod文件嵌入可能有些过度。当然,你也可以在运行时打开文件,但这意味着你必须将go.mod与可执行文件一起部署。使用-ldflags设置模块名称更加直接明了。

英文:

If your goal is to just have the name of the module available in your program, and if you are okay with setting this value at link time, then you may use the -ldflags build option.

You can get the name of the module with go list -m from within the module directory.

You can place everything in a Makefile or in a shell script:

MOD_NAME=$(go list -m)
go build -ldflags="-X 'main.MODNAME=$MOD_NAME'" -o main ./...

With main.go looking like:

package main

import "fmt"

var MODNAME string

func main() {
	fmt.Println(MODNAME) // example.com
}

<hr>

With the mentioned &quot;golang.org/x/mod/modfile&quot; package, an example might look like:

package main

import (
    &quot;fmt&quot;
	&quot;golang.org/x/mod/modfile&quot;
	_ &quot;embed&quot;
)

//go:embed go.mod
var gomod []byte

func main() {
	f, err := modfile.Parse(&quot;go.mod&quot;, gomod, nil)
	if err != nil {
		panic(err)
	}
	fmt.Println(f.Module.Mod.Path) // example.com
}

However embedding the entire go.mod file in your use case seems overkill. Of course you could also open the file at runtime, but that means you have to deploy go.mod along with your executable. Setting the module name with -ldflags is more straightforward IMO.

huangapple
  • 本文由 发表于 2021年10月6日 03:07:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/69456014.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定