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