简介
在 Gin 框架中,你可以使用 runtime/debug 包来打印调试信息,特别是在错误处理和日志记录方面。
runtime/debug
是 Go 标准库中一个用于调试和诊断的包,提供了多种功能来帮助开发者分析程序运行状态、排查问题以及优化性能。
以下是其主要功能的详细说明:
1. 堆栈跟踪(Stack Trace)
-
功能:获取程序当前执行点的调用堆栈信息。
-
常用方法 :
debug.PrintStack()
:直接将堆栈信息打印到标准错误输出(os.Stderr
)。debug.Stack()
:返回堆栈信息的字节切片([]byte
),可自定义输出方式(如记录到日志文件)。
-
典型场景 :
- 在错误恢复(
recover
)时打印堆栈,定位panic
的来源。 - 记录复杂业务逻辑的执行路径。
- 在错误恢复(
-
示例 :
gofunc foo() { defer func() { if r := recover(); r != nil { debug.PrintStack() // 打印堆栈到标准错误 log.Printf("Recovered from panic: %v\nStack: %s", r, debug.Stack()) } }() panic("error occurred") }
2. 内存统计(Memory Stats)
-
功能:获取 Go 运行时内存分配和垃圾回收(GC)的统计信息。
-
常用方法 :
debug.ReadMemStats(&m)
:将内存统计信息填充到runtime.MemStats
结构体中。
-
关键字段 :
Alloc
:当前分配的堆内存(字节)。TotalAlloc
:累计分配的堆内存(字节)。Sys
:从操作系统获取的总内存(字节)。NumGC
:GC 执行次数。PauseTotalNs
:GC 暂停总时间(纳秒)。
-
典型场景 :
- 监控内存泄漏或频繁 GC。
- 性能调优时分析内存使用模式。
-
示例 :
gofunc printMemStats() { var m runtime.MemStats runtime.ReadMemStats(&m) log.Printf("Alloc = %v MiB", bToMb(m.Alloc)) log.Printf("TotalAlloc = %v MiB", bToMb(m.TotalAlloc)) log.Printf("Sys = %v MiB", bToMb(m.Sys)) log.Printf("NumGC = %v", m.NumGC) } func bToMb(b uint64) uint64 { return b / 1024 / 1024 }
3. 设置 GOMAXPROCS
-
功能:调整程序可使用的 CPU 核心数。
-
方法 :
debug.SetMaxProcs(n int)
:设置 Go 调度器可使用的最大逻辑 CPU 核心数。
-
典型场景 :
- 限制程序使用的 CPU 资源(如容器化部署时)。
- 测试多核性能。
-
注意 :
- Go 1.5 后默认使用所有可用的 CPU 核心,通常无需手动设置。
-
示例 :
gofunc main() { n := runtime.NumCPU() // 获取 CPU 核心数 debug.SetMaxProcs(n / 2) // 限制使用一半的 CPU 核心 // ... }
4. 自由列表(Free List)信息
-
功能:查看内存分配器的内部状态(如空闲内存块分布)。
-
方法 :
debug.FreeOSMemory()
:强制将未使用的内存归还给操作系统(谨慎使用)。debug.SetGCPercent(percent int)
:调整 GC 触发阈值(默认 100,表示堆内存增长 100% 时触发 GC)。
-
典型场景 :
- 长期运行的服务中释放未使用的内存。
- 调整 GC 行为以优化延迟敏感型应用。
-
示例 :
gofunc optimizeGC() { // 降低 GC 触发频率(例如 200 表示堆内存增长 200% 时触发) debug.SetGCPercent(200) }
5. 构建信息(Build Info)
-
功能:获取程序编译时的构建信息(如 Go 版本、编译时间、模块路径)。
-
方法 :
debug.ReadBuildInfo()
:返回*debug.BuildInfo
结构体,包含:Main.Path
:主模块路径。Main.Version
:主模块版本。Deps
:依赖模块列表。
-
典型场景 :
- 在日志中记录程序版本信息。
- 动态加载插件时验证依赖。
-
示例 :
gofunc printBuildInfo() { info, ok := debug.ReadBuildInfo() if !ok { log.Println("Build info not available") return } log.Printf("Path: %s", info.Main.Path) log.Printf("Version: %s", info.Main.Version) log.Printf("Go version: %s", info.GoVersion) }
6. 设置内存限制(实验性)
-
功能:限制程序的内存使用(Go 1.19+ 引入)。
-
方法 :
debug.SetMemoryLimit(limit uint64)
:设置程序的内存使用上限(字节)。
-
典型场景 :
- 防止内存溢出攻击。
- 在资源受限的环境中运行。
-
示例 :
gofunc main() { // 限制内存使用为 1GB debug.SetMemoryLimit(1 << 30) // 1GB // ... }
总结:适用场景
功能 | 典型场景 |
---|---|
堆栈跟踪 | 错误恢复、调试复杂逻辑 |
内存统计 | 监控内存泄漏、性能调优 |
设置 GOMAXPROCS | 容器化部署、多核性能测试 |
自由列表管理 | 长期运行服务的内存释放、GC 行为优化 |
构建信息 | 程序版本记录、依赖验证 |
内存限制 | 资源受限环境、安全防护 |
注意事项
- 性能开销 :频繁调用
debug.PrintStack()
或runtime.ReadMemStats()
可能影响性能,建议在关键路径外使用。 - 生产环境:堆栈信息和内存统计通常仅用于开发或调试阶段,生产环境建议通过日志聚合工具(如 ELK)分析。
- 兼容性 :部分功能(如
SetMemoryLimit
)是较新版本引入的,需确认 Go 版本支持。
通过合理使用 runtime/debug
,可以显著提升 Go 程序的调试效率和运行稳定性。
实践代码样例
package logger
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"io"
"net/http"
"os"
"path"
"runtime/debug"
"time"
)
func init() {
// 设置日志格式为json格式
logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
logrus.SetReportCaller(false)
}
func Write(msg string, filename string) {
setOutPutFile(logrus.InfoLevel, filename)
logrus.Info(msg)
}
func Debug(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.DebugLevel, "debug")
logrus.WithFields(fields).Debug(args)
}
func Info(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.InfoLevel, "info")
logrus.WithFields(fields).Info(args)
}
func Warn(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.WarnLevel, "warn")
logrus.WithFields(fields).Warn(args)
}
func Fatal(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.FatalLevel, "fatal")
logrus.WithFields(fields).Fatal(args)
}
func Error(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.ErrorLevel, "error")
logrus.WithFields(fields).Error(args)
}
func Panic(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.PanicLevel, "panic")
logrus.WithFields(fields).Panic(args)
}
func Trace(fields logrus.Fields, args ...interface{}) {
setOutPutFile(logrus.TraceLevel, "trace")
logrus.WithFields(fields).Trace(args)
}
func setOutPutFile(level logrus.Level, logName string) {
if _, err := os.Stat("./runtime/log"); os.IsNotExist(err) {
err = os.MkdirAll("./runtime/log", 0777)
if err != nil {
panic(fmt.Errorf("create log dir '%s' error: %s", "./runtime/log", err))
}
}
timeStr := time.Now().Format("2006-01-02")
fileName := path.Join("./runtime/log", logName+"_"+timeStr+".log")
var err error
os.Stderr, err = os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
fmt.Println("open log file err", err)
}
logrus.SetOutput(os.Stderr)
logrus.SetLevel(level)
return
}
func LoggerToFile() gin.LoggerConfig {
if _, err := os.Stat("./runtime/log"); os.IsNotExist(err) {
err = os.MkdirAll("./runtime/log", 0777)
if err != nil {
panic(fmt.Errorf("create log dir '%s' error: %s", "./runtime/log", err))
}
}
timeStr := time.Now().Format("2006-01-02")
fileName := path.Join("./runtime/log", "success_"+timeStr+".log")
os.Stderr, _ = os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
var conf = gin.LoggerConfig{
Formatter: func(param gin.LogFormatterParams) string {
return fmt.Sprintf("%s - %s \"%s %s %s %d %s \"%s\" %s\"\n",
param.TimeStamp.Format("2006-01-02 15:04:05"),
param.ClientIP,
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
},
Output: io.MultiWriter(os.Stdout, os.Stderr),
}
return conf
}
func Recover(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
if _, errDir := os.Stat("./runtime/log"); os.IsNotExist(errDir) {
errDir = os.MkdirAll("./runtime/log", 0777)
if errDir != nil {
panic(fmt.Errorf("create log dir '%s' error: %s", "./runtime/log", errDir))
}
}
timeStr := time.Now().Format("2006-01-02")
fileName := path.Join("./runtime/log", "error_"+timeStr+".log")
f, errFile := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if errFile != nil {
fmt.Println(errFile)
}
timeFileStr := time.Now().Format("2006-01-02 15:04:05")
f.WriteString("panic error time:" + timeFileStr + "\n")
f.WriteString(fmt.Sprintf("%v", err) + "\n")
f.WriteString("stacktrace from panic:" + string(debug.Stack()) + "\n")
f.Close()
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": fmt.Sprintf("%v", err),
})
//终止后续接口调用,不加的话recover到异常后,还会继续执行接口里后续代码
c.Abort()
}
}()
c.Next()
}
集成在router 里面
func Routers() *gin.Engine {
r := gin.Default()
// 调用日志组件
r.Use(gin.LoggerWithConfig(logger.LoggerToFile()))
r.Use(logger.Recover)
// 其他业务
}