Golang 并发下的问题定位

944 阅读2分钟

问题描述

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

gin-swagger-error.jpg

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

解决方案

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

说做就做

实现思路

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

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

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


log.Printf("Picking operation from %s\n", aurora.Blue(funType.FullName()))

  1. 如果想要给日志中添加 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的结构也可能不同, 所以此处我直接调用一个开源实现:

github.com/petermattis…

  1. 修改 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 ,然后作为日志的前缀。

修改后的日志效果

gin-swagger-update.jpg

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

panic-gid.jpg


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

gin-swagger-update-grep.jpg