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