-
发生时间:2022-08-25
-
问题描述:
- 在开发产线平台的日志管理模块时,我定义了结构体
FtLog来记录日志信息(如下所示)。 - 当上传日志失败时,我希望在错误日志中打印
FtLog的详细信息。 默认的打印格式是只会打印结构体每个字段的取值,而我希望同时打印字段的名字和取值。 - 因此,我为
FtLog实现了String方法,采用了Go 标准库 fmt 的 “%+v” 格式。 - 对产线平台进行测试,构造上传日志失败的场景,发现程序会崩溃,报错
fatal error: stack overflow。
type FtLog struct { TestSuccess bool `form:"success" binding:"required"` // 测试是否成功 FactoryName string `form:"factory" binding:"required"` // 工厂名字 StationName string `form:"station" binding:"required"` // 工位名字 ProductModel string `form:"productModel" binding:"required"` // 产品型号 DeviceType string `form:"deviceType" binding:"required"` // 设备类型 DeviceId string `form:"deviceId" binding:"required"` // 设备ID FileName string `form:"fileName"` // 日志文件名 SavePath string `form:"savePath"` // 日志保存路径 } func (l *FtLog) String() string { return fmt.Sprintf("%+v", l) } - 在开发产线平台的日志管理模块时,我定义了结构体
-
问题类别:软件开发
-
原因分析:
- Go 的源码保存在 Go 安装目录的
src子目录中, 我们打开src/fmt/print.go文件即可查看fmt.Sprintf的实现。 - 查看源码得知,当
Sprintf函数的第一个参数含有v字符时,Sprintf函数会调用输入变量的String方法进行打印。 - 也就是说,我们在
String中调用Sprintf,而Sprintf又会调用String,这构成了递归。函数不停被递归调用,导致栈溢出。
- Go 的源码保存在 Go 安装目录的
-
解决方案:改用
json.marshal以代替fmt.Sprintf,既避免了递归,也达到了同时打印字段的名字和取值的目的。func (l *FtLog) String() string { data, _ := json.Marshal(l) return string(data) } -
实施结果:修改后程序可以正常运行,打印结果符合预期。
-
经验总结:
- Go 语言中为结构体自定义
String方法时,要慎用 fmt 库,并注意避免递归。 - Go 语言中实现一个接口时,要了解该接口的使用场景(会被谁调用、怎么调用等),避免踩坑。
- Go 语言标准库的源码保存在 Go 安装目录的
src子目录中,查看起来特别方便。
- Go 语言中为结构体自定义
fmt 通用格式化字符串
%v the value in a default format
when printing structs, the plus flag (%+v) adds field names
%#v a Go-syntax representation of the value
%T a Go-syntax representation of the type of the value
%% a literal percent sign; consumes no value