Skip to content
鼓励作者:欢迎打赏犒劳

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"}

如有转载或 CV 的请标注本站原文地址