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