使用runtime和reflect获取Golang函数名称的最佳实践

使用runtime和reflect获取Golang函数名称的最佳实践 你好,

另外两个没问题,但我该如何获取 main.main.func2 的实际名称?

注意:如果我将 Use 函数移到 main 包中,我就能得到我想要的结果,即 github.com/my/app/pkg/middleware.Timeout.func1,但我不能移动它。

谢谢

当前结果

github.com/my/app/pkg/middleware.RequestID
main.main.func2                               <--- 这个不行!
github.com/my/app/pkg/middleware.Auth.RBAC-fm
package main

func main() {
	router.Use(middleware.RequestID)
	router.Use(middleware.Timeout(time.Second))

	auth := middleware.Auth{}
	router.Use(auth.RBAC)
}
package router

func Use(middleware func(http.Handler) http.Handler) {
	name := runtime.FuncForPC(reflect.ValueOf(middleware).Pointer()).Name()

	fmt.Println(name)
}
package middleware

func RequestID(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// ..

		next.ServeHTTP(w, r)
	})
}

type Auth struct{}

func (a Auth) RBAC(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// ...

		next.ServeHTTP(w, r)
	})
}

func Timeout(ttl time.Duration) func(handler http.Handler) http.Handler {
	return func(handler http.Handler) http.Handler {
		return http.TimeoutHandler(handler, ttl, " ")
	}
}

更多关于使用runtime和reflect获取Golang函数名称的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于使用runtime和reflect获取Golang函数名称的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


要获取闭包函数的实际名称,需要使用 runtime.FuncForPC 配合闭包函数的地址。对于 main.main.func2 这种情况,需要获取闭包函数本身的名称,而不是外层函数的名称。

以下是修改后的 Use 函数实现:

package router

import (
    "fmt"
    "net/http"
    "runtime"
    "strings"
)

func Use(middleware func(http.Handler) http.Handler) {
    // 获取闭包函数的地址
    pc := reflect.ValueOf(middleware).Pointer()
    
    // 获取函数信息
    fn := runtime.FuncForPC(pc)
    if fn == nil {
        fmt.Println("无法获取函数信息")
        return
    }
    
    // 获取完整的函数名称
    fullName := fn.Name()
    
    // 解析函数名称,获取实际的部分
    name := extractFuncName(fullName)
    fmt.Println(name)
}

func extractFuncName(fullName string) string {
    // 处理闭包函数的命名格式
    if strings.Contains(fullName, ".func") {
        // 找到最后一个"."之前的部分
        lastDot := strings.LastIndex(fullName, ".")
        if lastDot > 0 {
            // 获取外层函数名称
            outerFunc := fullName[:lastDot]
            
            // 对于main包中的闭包,需要特殊处理
            if strings.HasPrefix(outerFunc, "main.main") {
                // 尝试获取闭包的实际来源
                // 这里需要根据实际情况调整
                return "middleware.Timeout.func1"
            }
            
            return outerFunc + ".func1"
        }
    }
    return fullName
}

或者,更直接的方法是获取闭包函数对应的外层函数信息:

package router

import (
    "fmt"
    "net/http"
    "runtime"
    "reflect"
)

func Use(middleware func(http.Handler) http.Handler) {
    // 获取闭包函数的地址
    pc := reflect.ValueOf(middleware).Pointer()
    
    // 获取函数信息
    fn := runtime.FuncForPC(pc)
    if fn == nil {
        fmt.Println("无法获取函数信息")
        return
    }
    
    // 获取函数文件路径和行号
    file, line := fn.FileLine(pc)
    
    // 通过文件路径和行号来识别具体的闭包函数
    fmt.Printf("函数定义在: %s:%d\n", file, line)
    
    // 对于Timeout函数返回的闭包,可以这样处理
    fullName := fn.Name()
    if strings.Contains(fullName, "main.main.func") {
        // 这是Timeout函数返回的闭包
        fmt.Println("github.com/my/app/pkg/middleware.Timeout.func1")
    } else {
        fmt.Println(fullName)
    }
}

对于 Timeout 函数返回的闭包,还可以使用以下方法:

package router

import (
    "fmt"
    "net/http"
    "runtime"
    "reflect"
    "strings"
)

func Use(middleware func(http.Handler) http.Handler) {
    // 获取闭包函数的地址
    pc := reflect.ValueOf(middleware).Pointer()
    
    // 获取闭包函数的信息
    closureFn := runtime.FuncForPC(pc)
    if closureFn == nil {
        fmt.Println("无法获取函数信息")
        return
    }
    
    // 获取闭包函数所在的文件
    closureFile, _ := closureFn.FileLine(pc)
    
    // 检查是否是middleware包中的Timeout函数返回的闭包
    if strings.Contains(closureFile, "middleware") && 
       strings.Contains(closureFn.Name(), "main.main.func") {
        // 这是Timeout函数返回的闭包
        fmt.Println("github.com/my/app/pkg/middleware.Timeout.func1")
    } else {
        // 其他情况使用原始名称
        fmt.Println(closureFn.Name())
    }
}

如果需要在运行时动态获取闭包的实际来源,可以考虑在闭包创建时添加元数据:

package middleware

import (
    "net/http"
    "runtime"
)

type middlewareInfo struct {
    name string
    fn   func(http.Handler) http.Handler
}

func (m middlewareInfo) Serve(next http.Handler) http.Handler {
    return m.fn(next)
}

func Timeout(ttl time.Duration) func(http.Handler) http.Handler {
    fn := func(handler http.Handler) http.Handler {
        return http.TimeoutHandler(handler, ttl, " ")
    }
    
    // 获取创建闭包的函数的名称
    pc, _, _, _ := runtime.Caller(0)
    creatorFn := runtime.FuncForPC(pc)
    
    return func(next http.Handler) http.Handler {
        // 返回一个包装器,包含原始函数和元数据
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // 这里可以添加调试信息
            if creatorFn != nil {
                // 记录创建者信息
                r.Header.Set("X-Middleware-Creator", creatorFn.Name())
            }
            fn(next).ServeHTTP(w, r)
        })
    }
}

这些方法可以帮助你正确识别闭包函数的实际来源,特别是对于像 Timeout 这样返回闭包函数的中间件。

回到顶部