| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- // 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
- }
|