Newer
Older
import (
"bufio"
"runtime/debug"
"strconv"
"strings"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
)
type Stack struct {
Routine int64 // go routine nr that crashed...
Callers []errors.CallerInfo
}
func CallStack() Stack {
stack := Stack{
Callers: []errors.CallerInfo{},
}
s := bufio.NewScanner(strings.NewReader(string(debug.Stack())))
// "goroutine 14 [running]:\nruntime/debug.Stack()\n\t/usr/local/go/src/runtime/debug/stack.go:24 +0x88\ngitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/api.Api.Handler.func3.1(0x1400009ff60)\n\t/Users/jansemmelink/uafrica/go-utils/api/lambda.go:210 +0x48\npanic({0x100780d20, 0x100b98a50})\n\t/usr/local/go/src/runtime/panic.go:1038 +0x21c\ngitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/examples/core/api/users.Crash({{{0x1008237e0, 0x1400040af30}, {0x4, {0x1008186e0, 0x14000010020}, 0x1400040af90, 0x25, 0x2f}, {0xc04f2cb513fbdc28, 0x103ccb5b4c, ...}}, ...}, ...)\n\t/Users/jansemmelink/uafrica/go-utils/examples/core/api/users/users.go:115 +0x20\nreflect.Value.call({0x10076bba0, 0x10080cdc8, 0x13}, {0x10059bb1c, 0x4}, {0x140000a0730, 0x2, 0x2})\n\t/usr/local/go/src/reflect/value.go:543 +0x584\nreflect.Value.Call({0x10076bba0, 0x10080cdc8, 0x13}, {0x140000a0730, 0x2, 0x2})\n\t/usr/local/go/src/reflect/value.go:339 +0x8c\ngitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/api.Api.Handler.func3({{0x10082f730, 0x100780960}, {0x0, 0x0}, {0x0, 0x0}, {0x10076bba0, 0x10080cdc8, 0x13}}, {0x140000a0730, ...})\n\t/Users/jansemmelink/uafrica/go-utils/api/lambda.go:214 +0x84\ngitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/api.Api.Handler({{0x10082b3f0, 0x140003b6480}, {0x10059b80a, 0x3}, {0x140003b6360}, {0x1005a3606, 0x12}, 0x10080cf98, {0x1008190e0, 0x100bdd308}, ...}, ...)\n\t/Users/jansemmelink/uafrica/go-utils/api/lambda.go:216 +0x1238\ngitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/api.Api.ServeHTTP({{0x10082b3f0, 0x140003b6480}, {0x10059b80a, 0x3}, {0x140003b6360}, {0x1005a3606, 0x12}, 0x10080cf98, {0x1008190e0, 0x100bdd308}, ...}, ...)\n\t/Users/jansemmelink/uafrica/go-utils/api/local.go:81 +0x6ac\nnet/http.serverHandler.ServeHTTP({0x1400015a620}, {0x1008200e8, 0x1400015aa80}, 0x140003c6900)\n\t/usr/local/go/src/net/http/server.go:2878 +0x444\nnet/http.(*conn).serve(0x14000279220, {0x1008237e0, 0x140003b6780})\n\t/usr/local/go/src/net/http/server.go:1929 +0xb6c\ncreated by net/http.(*Server).Serve\n\t/usr/local/go/src/net/http/server.go:3033 +0x4b8\n"
// i.e. multiple lines ending with "\n" each, e.g.:
// ------------------------------------------------------------------------------------------------------------
// goroutine 37 [running]:
// runtime/debug.Stack()
// /usr/local/go/src/runtime/debug/stack.go:24 +0x88
// gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/api.Api.Handler.func3.1(0x1400042cb00, 0x14000095f68)
// /Users/jansemmelink/uafrica/go-utils/api/lambda.go:216 +0x50
// panic({0x100c08d20, 0x101020a70})
// /usr/local/go/src/runtime/panic.go:1038 +0x21c
// gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/examples/core/api/users.Crash({{{0x100cab7c0, 0x140004843c0}, {0x4, {0x100ca06c0, 0x14000138010}, 0x14000484420, 0x20, 0x28}, {0xc04f2d6c3023b330, 0x227861e6a, ...}}, ...}, ...)
// /Users/jansemmelink/uafrica/go-utils/examples/core/api/users/users.go:115 +0x20
// ...
// ------------------------------------------------------------------------------------------------------------
// get go routine nr from first line: "gorouting <nr> [running]:"
if s.Scan() {
p := strings.SplitN(s.Text(), " ", 3)
if len(p) >= 2 && p[0] == "goroutine" {
routineNr, err := strconv.ParseInt(p[1], 10, 64)
if err == nil {
stack.Routine = routineNr
}
}
}
// next expect line pairs for each level of the stack
// read first line in this pair, expecting <package>.<funcName>(<args>)
if !s.Scan() {
break
}
line1 := s.Text()
if !s.Scan() {
break
}
line2 := s.Text()
// fmt.Printf(" STACK LINE: %s %s\n", line1, line2)
// split line 1 on any bracket or comma to get "<package>.<funcName>"["<arg>" ...]
// func may have multiple '.', so do not split on that yet!
line1Fields := strings.FieldsFunc(line1, func(c rune) bool { return strings.Contains("(), ", string(c)) })
// split line 2 <file>:<line> +0x##...
line2Fields := strings.FieldsFunc(line2, func(c rune) bool { return strings.Contains(": ", string(c)) })
lineNr, _ := strconv.ParseInt(line2Fields[1], 10, 64)
caller := errors.NewCaller(line1Fields[0], line2Fields[0], int(lineNr))
// skip first levels that refer to capturing the stack
ci := caller.Info()
if len(stack.Callers) == 0 {
if ci.Package == "runtime/debug" ||
ci.Package == "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs" ||
ci.Package == "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors" ||
ci.Package == "" {
continue
}
if _, err := strconv.ParseInt(ci.Function, 10, 64); err == nil {
continue // typical defer function without a name used to catch the crash
}
}
stack.Callers = append(stack.Callers, caller.Info())
}
return stack
}