问题描述

在使用 gin-swagger 的过程中, 经常会发生因为缺少 jsontag 而导致的异常。 由于 gin-swagger 是并发执行的, 输出的日志本身是错位的。 这就导致无法定义是哪一个结构体缺少 tag 导致的。

一般而言, 这种时候只能一个个点开去检查。

解决方案

思路 : 要是每行日志带当前 goroutine_id 的话, 是不是就可以准确定位到报错的 goroutine 他打印的日志是哪些了呢!

说做就做

实现思路

  • 查看当前日志是怎么打印的

发现 gin-swagger 日志直接调用的 golang 的标准库 log

由于没有对log初始化, 所以默认使用的是 stdout

log.printf("picking operation from %s\n", aurora.blue(funtype.fullname()))
  • 如果想要给日志中添加 goroutine_id 的话, 就需要在调用 log.printf 的时候获取当前 goroutine 的 id , 所以首先要解决的是怎么获取 goroutine_id 的问题。

调研后发现了集中常见的获取 goroutine_id 的方法:

2.1 通过栈信息解析后获取

func getgid() uint64 {
    b := make([]byte, 64)
    b = b[:runtime.stack(b, false)]
    b = bytes.trimprefix(b, []byte("goroutine "))
    b = b[:bytes.indexbyte(b, ' ')]
    n, _ := strconv.parseuint(string(b), 10, 64)
    return n
}

2.2 修改 go 源码获取

# src/runtime/runtime2.go
func goid() int64 {
    _g_ := getg()
    return _g_.goid
}

2.3 通过 cgo 获取

文件 id.c

#include "runtime.h"
int64 ·id(void) {
    return g->goid;
}

文件 id.go

package id
func id() int64

另外还可以通过汇编获取 goroutine_id

由于go的版本不同,goroutine的结构也可能不同, 所以此处我直接调用一个开源实现:

https://github.com/petermattis/goid

  • 修改 gin-swagger main.go 源码, 修改 log write 的实现

修改前

func main() {
    cmd.execute()
}

修改后

type out os.file
func (o out) write(b []byte) (int, error) {
    prefix := fmt.sprintf("gid-%d: ", goid.get())
    all := make([]byte, len(b)+len(prefix))
    all = []byte(prefix)
    all = append(all, b...)
    f := os.file(o)
    return f.write(all)
}
func main() {
    var out out
    out = out(os.stdout)
    log.setoutput(out)
    cmd.execute()

这样修改后,每次 gin-swagger 调用 log 打印日志都时候, 都会使用我定义的 write 方法, write 方法中每次打印前都会先查询出当前的 goroutine_id ,然后作为日志的前缀。

修改后的日志效果

从中可以看到每行日志开头,都已经加上了 goroutine_id。 只要使用 panic goroutineid 做一次 grep , 即可筛选出需要的日志了,极大的方便了定位问题。

cat /tmp/gin-swagger.log | grep 7647

到此这篇关于golang 并发下的问题定位的文章就介绍到这了,更多相关golang并发问题内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!