5bf8d5445e
Make router logger more friendly, show the related function name/file/line. [BREAKING] This PR substantially changes the logging format of the router logger. If you use this logging for monitoring e.g. fail2ban you will need to update this to match the new format.
172 lines
4.4 KiB
Go
172 lines
4.4 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package routing
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
var funcInfoMap = map[uintptr]*FuncInfo{}
|
|
var funcInfoNameMap = map[string]*FuncInfo{}
|
|
var 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 interface{}, 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
|
|
}
|