Gin 框架中集成 runtime/debug 打印日志堆栈信息

简介

在 Gin 框架中,你可以使用 runtime/debug 包来打印调试信息,特别是在错误处理和日志记录方面。
runtime/debug 是 Go 标准库中一个用于调试和诊断的包,提供了多种功能来帮助开发者分析程序运行状态、排查问题以及优化性能。

以下是其主要功能的详细说明:


1. 堆栈跟踪(Stack Trace)

  • 功能:获取程序当前执行点的调用堆栈信息。

  • 常用方法

    • debug.PrintStack():直接将堆栈信息打印到标准错误输出(os.Stderr)。
    • debug.Stack():返回堆栈信息的字节切片([]byte),可自定义输出方式(如记录到日志文件)。
  • 典型场景

    • 在错误恢复(recover)时打印堆栈,定位 panic 的来源。
    • 记录复杂业务逻辑的执行路径。
  • 示例

    go 复制代码
    func 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。
    • 性能调优时分析内存使用模式。
  • 示例

    go 复制代码
    func 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 核心,通常无需手动设置。
  • 示例

    go 复制代码
    func main() {
        n := runtime.NumCPU() // 获取 CPU 核心数
        debug.SetMaxProcs(n / 2) // 限制使用一半的 CPU 核心
        // ...
    }

4. 自由列表(Free List)信息

  • 功能:查看内存分配器的内部状态(如空闲内存块分布)。

  • 方法

    • debug.FreeOSMemory():强制将未使用的内存归还给操作系统(谨慎使用)。
    • debug.SetGCPercent(percent int):调整 GC 触发阈值(默认 100,表示堆内存增长 100% 时触发 GC)。
  • 典型场景

    • 长期运行的服务中释放未使用的内存。
    • 调整 GC 行为以优化延迟敏感型应用。
  • 示例

    go 复制代码
    func optimizeGC() {
        // 降低 GC 触发频率(例如 200 表示堆内存增长 200% 时触发)
        debug.SetGCPercent(200)
    }

5. 构建信息(Build Info)

  • 功能:获取程序编译时的构建信息(如 Go 版本、编译时间、模块路径)。

  • 方法

    • debug.ReadBuildInfo():返回 *debug.BuildInfo 结构体,包含:
      • Main.Path:主模块路径。
      • Main.Version:主模块版本。
      • Deps:依赖模块列表。
  • 典型场景

    • 在日志中记录程序版本信息。
    • 动态加载插件时验证依赖。
  • 示例

    go 复制代码
    func 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):设置程序的内存使用上限(字节)。
  • 典型场景

    • 防止内存溢出攻击。
    • 在资源受限的环境中运行。
  • 示例

    go 复制代码
    func main() {
        // 限制内存使用为 1GB
        debug.SetMemoryLimit(1 << 30) // 1GB
        // ...
    }

总结:适用场景

功能 典型场景
堆栈跟踪 错误恢复、调试复杂逻辑
内存统计 监控内存泄漏、性能调优
设置 GOMAXPROCS 容器化部署、多核性能测试
自由列表管理 长期运行服务的内存释放、GC 行为优化
构建信息 程序版本记录、依赖验证
内存限制 资源受限环境、安全防护

注意事项

  1. 性能开销 :频繁调用 debug.PrintStack()runtime.ReadMemStats() 可能影响性能,建议在关键路径外使用。
  2. 生产环境:堆栈信息和内存统计通常仅用于开发或调试阶段,生产环境建议通过日志聚合工具(如 ELK)分析。
  3. 兼容性 :部分功能(如 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)
// 其他业务

}
相关推荐
{⌐■_■}7 小时前
【计网】认识跨域,及其在go中通过注册CORS中间件解决跨域方案,go-zero、gin
java·linux·开发语言·c++·中间件·golang·gin
yuanlaile1 天前
Go全栈_Golang、Gin实战、Gorm实战、Go_Socket、Redis、Elasticsearch、微服务、K8s、RabbitMQ全家桶
linux·redis·golang·k8s·rabbitmq·gin
pedestrian_h2 天前
gin框架学习笔记
笔记·学习·go·web·gin
19岁开始学习2 天前
Gin框架
gin
唐僧洗头爱飘柔95273 天前
(Go Gin)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
后端·golang·go·restful·gin·goweb开发
一个热爱生活的普通人5 天前
GIN 服务如何实现优雅停机
go·gin
chxii6 天前
3.1goweb框架gin下
gin
Delphi菜鸟8 天前
go+mysql+cocos实现游戏搭建
mysql·游戏·golang·gin·cocos2d
老朋友此林11 天前
go语言学习笔记:gin + gorm + mysql 用户增删改查案例入门
mysql·golang·gin