不能直接拿,runtime.funcforpc需配合runtime.caller(1)获取pc值后调用,再检查非nil才可安全调用.name()获取全名(如"main.main"),最后用strings.lastindex分离包名与函数名。

runtime.FuncForPC 能否直接拿到当前函数名?
不能直接拿,runtime.FuncForPC 需要一个程序计数器地址(PC),而 Go 没有像 Python 的 inspect.currentframe() 那样开箱即用的“当前函数”获取方式。你得先用 runtime.Caller 获取调用栈帧,再把返回的 PC 传给 runtime.FuncForPC。
常见错误是直接对 nil 或无效 PC 调用 runtime.FuncForPC,结果返回 nil,后续调用 .Name() 会 panic。
-
runtime.Caller(0)返回的是当前这行代码所在的函数(即调用Caller的位置),不是外层函数 - 想获取“当前执行函数”,通常要用
runtime.Caller(1)—— 向上跳一层调用栈 - 必须检查
runtime.FuncForPC(pc)是否非 nil,否则.Name()会 panic
如何安全提取包名和函数名字符串?
Go 的函数全名格式是 package.name(如 main.main、http.HandlerFunc.ServeHTTP),需手动按点号拆分。标准库不提供现成的“只取函数名”或“只取包名”的 API。
典型做法是先用 runtime.FuncForPC 得到 *runtime.Func,再调用 .Name(),最后用 strings.LastIndex 或 strings.Split 分离。
- 推荐用
strings.LastIndex(name, ".")找最后一个点,避免嵌套包名(如vendor/github.com/user/lib)被误切 - 若函数在匿名函数或闭包中,
Name()可能返回类似main.main.func1,此时func1是合法函数名片段 - 不要依赖
strings.Split(name, ".")[0]提取包名 ——github.com/user/pkg这种路径会被截成github,错
实际可用的一行式封装(带容错)
下面这个 helper 函数能在多数场景下稳定工作,且不 panic:
func FuncName() (pkg, name string) {
pc, _, _, _ := runtime.Caller(1)
f := runtime.FuncForPC(pc)
if f == nil {
return "unknown", "unknown"
}
full := f.Name()
lastDot := strings.LastIndex(full, ".")
if lastDot
<p>注意:<code>runtime.Caller(1)</code> 是关键;<code>lastDot 处理无点号的特殊情况(极少见,但可能出现在某些编译器优化或内建函数中);返回值顺序是 <code>(pkg, name)</code>,符合 Go 常见命名习惯。</code></p>
<h3>为什么在 defer 或 goroutine 中容易出错?</h3>
<p>在 <code>defer</code> 或新开的 <code>goroutine</code> 中调用时,<code>runtime.Caller</code> 拿到的 PC 对应的是 defer 注册点或 goroutine 启动点,不是实际执行上下文。比如:</p>
<pre class="brush:php;toolbar:false;">func foo() {
defer func() {
fmt.Println(FuncName()) // 打印的是 foo,不是 defer 匿名函数本身
}()
}
这种情况下,如果真要获取 defer 内部函数名,得在 defer 里显式传入 runtime.Caller(0),或者改用 debug.PrintStack() 解析栈(更重、更慢)。生产环境一般不这么做 —— 真需要精确追踪,应该用结构化日志 + trace ID,而不是靠运行时反射。
真正容易被忽略的是:CGO 调用、内联函数、以及 go build -ldflags="-s -w" 去符号后,runtime.FuncForPC 会失效,返回空名。这类构建产物里,上述所有方案都不可靠。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











