summaryrefslogtreecommitdiff
path: root/stack.go
diff options
context:
space:
mode:
Diffstat (limited to 'stack.go')
-rw-r--r--stack.go91
1 files changed, 91 insertions, 0 deletions
diff --git a/stack.go b/stack.go
new file mode 100644
index 0000000..d4fed60
--- /dev/null
+++ b/stack.go
@@ -0,0 +1,91 @@
+// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
+// Use of this source code is governed by the GPL 3.0
+
+package pinpb
+
+// this is similar to 'gin' but specifically only for
+// sending and working with protocol buffers
+//
+// also, it is as close to possible a golang 'primitive'
+// package (there is no go.sum file)
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "runtime"
+ "time"
+)
+
+var (
+ dunno = []byte("???")
+ centerDot = []byte("·")
+ dot = []byte(".")
+ slash = []byte("/")
+)
+
+// stack returns a nicely formatted stack frame, skipping skip frames.
+func Stack(skip int) []byte {
+ buf := new(bytes.Buffer) // the returned data
+ // As we loop, we open files and read them. These variables record the currently
+ // loaded file.
+ var lines [][]byte
+ var lastFile string
+ for i := skip; ; i++ { // Skip the expected number of frames
+ pc, file, line, ok := runtime.Caller(i)
+ if !ok {
+ break
+ }
+ // Print this much at least. If we can't find the source, it won't show.
+ fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
+ if file != lastFile {
+ data, err := os.ReadFile(file)
+ if err != nil {
+ continue
+ }
+ lines = bytes.Split(data, []byte{'\n'})
+ lastFile = file
+ }
+ fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
+ }
+ return buf.Bytes()
+}
+
+// source returns a space-trimmed slice of the n'th line.
+func source(lines [][]byte, n int) []byte {
+ n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
+ if n < 0 || n >= len(lines) {
+ return dunno
+ }
+ return bytes.TrimSpace(lines[n])
+}
+
+// function returns, if possible, the name of the function containing the PC.
+func function(pc uintptr) []byte {
+ fn := runtime.FuncForPC(pc)
+ if fn == nil {
+ return dunno
+ }
+ name := []byte(fn.Name())
+ // The name includes the path name to the package, which is unnecessary
+ // since the file name is already included. Plus, it has center dots.
+ // That is, we see
+ // runtime/debug.*T·ptrmethod
+ // and want
+ // *T.ptrmethod
+ // Also the package path might contain dot (e.g. code.google.com/...),
+ // so first eliminate the path prefix
+ if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
+ name = name[lastSlash+1:]
+ }
+ if period := bytes.Index(name, dot); period >= 0 {
+ name = name[period+1:]
+ }
+ name = bytes.ReplaceAll(name, centerDot, dot)
+ return name
+}
+
+// timeFormat returns a customized time string for logger.
+func timeFormat(t time.Time) string {
+ return t.Format("2006/01/02 - 15:04:05")
+}