
go日志
获取程序执行时的调用栈信息
其实就是获取调用栈的信息。
go
func main() {
f2()
}
func f2() {
f1()
}
func f1() {
for i := 0; i <= 2; i++ {
pc, file, line, ok := runtime.Caller(i)
if ok {
FuncName := runtime.FuncForPC(pc).Name()
fmt.Printf("%s:%d:%s\n", FuncName, line, file)
}
}
}
结果:
text
main.f1:48:E:/go_ws/demo/main.go
main.f2:44:E:/go_ws/demo/main.go
main.main:41:E:/go_ws/demo/main.go
异常日志
基本语法
go
logger := log.New(输出目标, 前缀, 标志)
控制日志的格式选项,使用位或操作符 |
组合
go
const (
Ldate = 1 << iota // 日期: 2009/01/23
Ltime // 时间: 01:23:23
Lmicroseconds // 微秒: 01:23:23.123123
Llongfile // 完整文件路径/行号: /a/b/c/d.go:23
Lshortfile // 文件名/行号: d.go:23
LUTC // 使用 UTC 时间
Lmsgprefix // 将前缀放在消息前面
LstdFlags = Ldate | Ltime // 标准标志
)
原生打印异常日志
go
package main
import (
"log"
"os"
"runtime"
)
// 自定义日志文件
var logger *log.Logger
func init() {
// 打开日志文件,追加模式
file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal("无法打开日志文件:", err)
}
logger = log.New(file, "", log.LstdFlags|log.Lshortfile)
}
func main() {
defer func() {
if r := recover(); r != nil {
// 获取 panic 值
logger.Printf("PANIC: %v", r)
// 获取完整堆栈信息
buf := make([]byte, 4096)
n := runtime.Stack(buf, true) // true 表示打印所有协程的栈
logger.Printf("STACK TRACE:\n%s", buf[:n])
}
}()
f2()
}
func f2() {
f1()
}
func f1() {
panic("发生严重错误!")
}
结果: 真正发生panic的地方是E:/go_ws/demo/main.go:40
这一行,前面的是在 defer
的匿名函数里调用,可以忽略,func1()
是defer
的匿名函数
text
2025/10/07 20:49:36 main.go:25: PANIC: 发生严重错误!
2025/10/07 20:49:36 main.go:30: STACK TRACE:
goroutine 1 [running]:
main.main.func1()
E:/go_ws/demo/main.go:29 +0xd0
panic({0x7ff6124e5840?, 0x7ff61252bb48?})
D:/devtool/go/src/runtime/panic.go:783 +0x132
main.f1(...)
E:/go_ws/demo/main.go:40
main.f2(...)
E:/go_ws/demo/main.go:37
main.main()
E:/go_ws/demo/main.go:34 +0x45
slog实现
代码实现,注意默认打印的堆栈是一行, 这是一个 Go slog 包默认行为 导致的现象:当结构化日志字段中包含多行字符串(如堆栈信息)时, slog 会 自动对换行符进行转义或内联处理,以保证每条日志是一行(便于日志系统采集、解析)。
go
package main
import (
"log/slog"
"os"
"runtime"
"strings"
)
// 初始化 slog 日志器
func initSlog() *slog.Logger {
// 创建或打开日志文件
file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
slog.Error("无法打开日志文件", "error", err)
os.Exit(1)
}
// 设置输出到文件和标准输出(可选)
handler := slog.NewTextHandler(file, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
return slog.New(handler)
}
func main() {
logger := initSlog()
defer func() {
if r := recover(); r != nil {
// 记录 panic 的值
logger.Error("PANIC 捕获",
"panic_value", r,
)
// 获取堆栈信息
buf := make([]byte, 4096)
n := runtime.Stack(buf, true) // true 表示打印所有 goroutine 的栈
stackTrace := buf[:n]
// 第一种:堆栈打印在一行
//logger.Error("STACK TRACE",
// "stack_trace", stackTrace,
//)
// 第二种:分行展示,更加易读
lines := strings.Split(string(stackTrace), "\n")
for _, line := range lines {
logger.Error("stack", "line", line)
}
}
}()
f2()
}
func f2() {
f1()
}
func f1() {
panic("发生严重错误!")
}
如果想打印成json的格式,也很简单。
go
handler := slog.NewJSONHandler(file, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
结果:
text
第一种:
time=2025-10-07T21:22:06.320+08:00 level=ERROR msg="PANIC 捕获" panic_value=发生严重错误!
time=2025-10-07T21:22:06.331+08:00 level=ERROR msg="STACK TRACE" stack_trace="goroutine 1 [running]:\nmain.main.func1()\n\tE:/go_ws/demo/main.go:36 +0xc8\npanic({0x7ff7b3316540?, 0x7ff7b3368748?})\n\tD:/devtool/go/src/runtime/panic.go:783 +0x132\nmain.f1(...)\n\tE:/go_ws/demo/main.go:54\nmain.f2(...)\n\tE:/go_ws/demo/main.go:50\nmain.main()\n\tE:/go_ws/demo/main.go:46 +0x54\n"
第二种:
time=2025-10-07T21:28:36.698+08:00 level=ERROR msg="PANIC 捕获" panic_value=发生严重错误!
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line="goroutine 1 [running]:"
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line=main.main.func1()
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line="\tE:/go_ws/demo/main.go:37 +0xd0"
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line="panic({0x7ff6a9757540?, 0x7ff6a97a97d8?})"
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line="\tD:/devtool/go/src/runtime/panic.go:783 +0x132"
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line=main.f1(...)
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line="\tE:/go_ws/demo/main.go:59"
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line=main.f2(...)
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line="\tE:/go_ws/demo/main.go:55"
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line=main.main()
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line="\tE:/go_ws/demo/main.go:51 +0x54"
time=2025-10-07T21:28:36.710+08:00 level=DEBUG msg=stack line=""
json格式:
{"time":"2025-10-07T21:46:51.5157142+08:00","level":"ERROR","msg":"PANIC 捕获","panic_value":"发生严重错误!"}
{"time":"2025-10-07T21:46:51.5306584+08:00","level":"ERROR","msg":"STACK TRACE","stack_trace":"goroutine 1 [running]:\nmain.main.func1()\n\tE:/go_ws/demo/main.go:36 +0xc8\npanic({0x7ff70ee26540?, 0x7ff70ee78748?})\n\tD:/devtool/go/src/runtime/panic.go:783 +0x132\nmain.f1(...)\n\tE:/go_ws/demo/main.go:55\nmain.f2(...)\n\tE:/go_ws/demo/main.go:51\nmain.main()\n\tE:/go_ws/demo/main.go:47 +0x54\n"}