gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package routing
  4. import (
  5. "fmt"
  6. "reflect"
  7. "runtime"
  8. "strings"
  9. "sync"
  10. )
  11. var (
  12. funcInfoMap = map[uintptr]*FuncInfo{}
  13. funcInfoNameMap = map[string]*FuncInfo{}
  14. funcInfoMapMu sync.RWMutex
  15. )
  16. // FuncInfo contains information about the function to be logged by the router log
  17. type FuncInfo struct {
  18. file string
  19. shortFile string
  20. line int
  21. name string
  22. shortName string
  23. }
  24. // String returns a string form of the FuncInfo for logging
  25. func (info *FuncInfo) String() string {
  26. if info == nil {
  27. return "unknown-handler"
  28. }
  29. return fmt.Sprintf("%s:%d(%s)", info.shortFile, info.line, info.shortName)
  30. }
  31. // GetFuncInfo returns the FuncInfo for a provided function and friendlyname
  32. func GetFuncInfo(fn any, friendlyName ...string) *FuncInfo {
  33. // ptr represents the memory position of the function passed in as v.
  34. // This will be used as program counter in FuncForPC below
  35. ptr := reflect.ValueOf(fn).Pointer()
  36. // if we have been provided with a friendlyName look for the named funcs
  37. if len(friendlyName) == 1 {
  38. name := friendlyName[0]
  39. funcInfoMapMu.RLock()
  40. info, ok := funcInfoNameMap[name]
  41. funcInfoMapMu.RUnlock()
  42. if ok {
  43. return info
  44. }
  45. }
  46. // Otherwise attempt to get pre-cached information for this function pointer
  47. funcInfoMapMu.RLock()
  48. info, ok := funcInfoMap[ptr]
  49. funcInfoMapMu.RUnlock()
  50. if ok {
  51. if len(friendlyName) == 1 {
  52. name := friendlyName[0]
  53. info = copyFuncInfo(info)
  54. info.shortName = name
  55. funcInfoNameMap[name] = info
  56. funcInfoMapMu.Lock()
  57. funcInfoNameMap[name] = info
  58. funcInfoMapMu.Unlock()
  59. }
  60. return info
  61. }
  62. // This is likely the first time we have seen this function
  63. //
  64. // Get the runtime.func for this function (if we can)
  65. f := runtime.FuncForPC(ptr)
  66. if f != nil {
  67. info = convertToFuncInfo(f)
  68. // cache this info globally
  69. funcInfoMapMu.Lock()
  70. funcInfoMap[ptr] = info
  71. // if we have been provided with a friendlyName override the short name we've generated
  72. if len(friendlyName) == 1 {
  73. name := friendlyName[0]
  74. info = copyFuncInfo(info)
  75. info.shortName = name
  76. funcInfoNameMap[name] = info
  77. }
  78. funcInfoMapMu.Unlock()
  79. }
  80. return info
  81. }
  82. // convertToFuncInfo take a runtime.Func and convert it to a logFuncInfo, fill in shorten filename, etc
  83. func convertToFuncInfo(f *runtime.Func) *FuncInfo {
  84. file, line := f.FileLine(f.Entry())
  85. info := &FuncInfo{
  86. file: strings.ReplaceAll(file, "\\", "/"),
  87. line: line,
  88. name: f.Name(),
  89. }
  90. // only keep last 2 names in path, fall back to funcName if not
  91. info.shortFile = shortenFilename(info.file, info.name)
  92. // remove package prefix. eg: "xxx.com/pkg1/pkg2.foo" => "pkg2.foo"
  93. pos := strings.LastIndexByte(info.name, '/')
  94. if pos >= 0 {
  95. info.shortName = info.name[pos+1:]
  96. } else {
  97. info.shortName = info.name
  98. }
  99. // remove ".func[0-9]*" suffix for anonymous func
  100. info.shortName = trimAnonymousFunctionSuffix(info.shortName)
  101. return info
  102. }
  103. func copyFuncInfo(l *FuncInfo) *FuncInfo {
  104. return &FuncInfo{
  105. file: l.file,
  106. shortFile: l.shortFile,
  107. line: l.line,
  108. name: l.name,
  109. shortName: l.shortName,
  110. }
  111. }
  112. // shortenFilename generates a short source code filename from a full package path, eg: "code.gitea.io/routers/common/logger_context.go" => "common/logger_context.go"
  113. func shortenFilename(filename, fallback string) string {
  114. if filename == "" {
  115. return fallback
  116. }
  117. if lastIndex := strings.LastIndexByte(filename, '/'); lastIndex >= 0 {
  118. if secondLastIndex := strings.LastIndexByte(filename[:lastIndex], '/'); secondLastIndex >= 0 {
  119. return filename[secondLastIndex+1:]
  120. }
  121. }
  122. return filename
  123. }
  124. // trimAnonymousFunctionSuffix trims ".func[0-9]*" from the end of anonymous function names, we only want to see the main function names in logs
  125. func trimAnonymousFunctionSuffix(name string) string {
  126. // if the name is an anonymous name, it should be like "{main-function}.func1", so the length can not be less than 7
  127. if len(name) < 7 {
  128. return name
  129. }
  130. funcSuffixIndex := strings.LastIndex(name, ".func")
  131. if funcSuffixIndex < 0 {
  132. return name
  133. }
  134. hasFuncSuffix := true
  135. // len(".func") = 5
  136. for i := funcSuffixIndex + 5; i < len(name); i++ {
  137. if name[i] < '0' || name[i] > '9' {
  138. hasFuncSuffix = false
  139. break
  140. }
  141. }
  142. if hasFuncSuffix {
  143. return name[:funcSuffixIndex]
  144. }
  145. return name
  146. }