You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

stack.go 2.5KB

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