Skip to content
Snippets Groups Projects
stack.go 5.09 KiB
Newer Older
Francé Wilke's avatar
Francé Wilke committed
package logs

import (
	"bufio"
	"runtime/debug"
	"strconv"
	"strings"

	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
	Routine int64 // go routine nr that crashed...
	Callers []errors.CallerInfo
}

func CallStack() Stack {
	stack := Stack{
		Callers: []errors.CallerInfo{},
	}

	// get the call stack
	s := bufio.NewScanner(strings.NewReader(string(debug.Stack())))
	// expect stack to look like this:
	//   "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
}