mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
173 lines
4.3 KiB
Go
173 lines
4.3 KiB
Go
// Copyright 2026 Hanzo AI, Inc. 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: "github.com/hanzoai/git/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
|
|
}
|