github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/log/stack.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package log 7 8 import ( 9 "bytes" 10 "fmt" 11 "os" 12 "runtime" 13 ) 14 15 var unknown = []byte("???") 16 17 // Stack will skip back the provided number of frames and return a stack trace with source code. 18 // Although we could just use debug.Stack(), this routine will return the source code and 19 // skip back the provided number of frames - i.e. allowing us to ignore preceding function calls. 20 // A skip of 0 returns the stack trace for the calling function, not including this call. 21 // If the problem is a lack of memory of course all this is not going to work... 22 func Stack(skip int) string { 23 buf := new(bytes.Buffer) 24 25 // Store the last file we opened as its probable that the preceding stack frame 26 // will be in the same file 27 var lines [][]byte 28 var lastFilename string 29 for i := skip + 1; ; i++ { // Skip over frames 30 programCounter, filename, lineNumber, ok := runtime.Caller(i) 31 // If we can't retrieve the information break - basically we're into go internals at this point. 32 if !ok { 33 break 34 } 35 36 // Print equivalent of debug.Stack() 37 fmt.Fprintf(buf, "%s:%d (0x%x)\n", filename, lineNumber, programCounter) 38 // Now try to print the offending line 39 if filename != lastFilename { 40 data, err := os.ReadFile(filename) 41 if err != nil { 42 // can't read this sourcefile 43 // likely we don't have the sourcecode available 44 continue 45 } 46 lines = bytes.Split(data, []byte{'\n'}) 47 lastFilename = filename 48 } 49 fmt.Fprintf(buf, "\t%s: %s\n", functionName(programCounter), source(lines, lineNumber)) 50 } 51 return buf.String() 52 } 53 54 // functionName converts the provided programCounter into a function name 55 func functionName(programCounter uintptr) []byte { 56 function := runtime.FuncForPC(programCounter) 57 if function == nil { 58 return unknown 59 } 60 name := []byte(function.Name()) 61 62 // Because we provide the filename we can drop the preceding package name. 63 if lastslash := bytes.LastIndex(name, []byte("/")); lastslash >= 0 { 64 name = name[lastslash+1:] 65 } 66 // And the current package name. 67 if period := bytes.Index(name, []byte(".")); period >= 0 { 68 name = name[period+1:] 69 } 70 // And we should just replace the interpunct with a dot 71 name = bytes.ReplaceAll(name, []byte("ยท"), []byte(".")) 72 return name 73 } 74 75 // source returns a space-trimmed slice of the n'th line. 76 func source(lines [][]byte, n int) []byte { 77 n-- // in stack trace, lines are 1-indexed but our array is 0-indexed 78 if n < 0 || n >= len(lines) { 79 return unknown 80 } 81 return bytes.TrimSpace(lines[n]) 82 }