mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-28 13:52:25 -05:00
# Description Adds JSON logging as an optional alternative log output format. In the process this moves our log formatting itself into a separate subpkg to make it more easily modular, and improves caller name getting with some calling function name caching. ## Checklist - [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md). - [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat. - [x] I/we have not leveraged AI to create the proposed changes. - [x] I/we have performed a self-review of added code. - [x] I/we have written code that is legible and maintainable by others. - [x] I/we have commented the added code, particularly in hard-to-understand areas. - [x] I/we have made any necessary changes to documentation. - [ ] I/we have added tests that cover new code. - [x] I/we have run tests and they pass locally with the changes. - [x] I/we have run `go fmt ./...` and `golangci-lint run`. Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4355 Co-authored-by: kim <grufwub@gmail.com> Co-committed-by: kim <grufwub@gmail.com>
136 lines
3.2 KiB
Go
136 lines
3.2 KiB
Go
package caller
|
|
|
|
import (
|
|
"runtime"
|
|
"strings"
|
|
"sync/atomic"
|
|
)
|
|
|
|
var (
|
|
// callerCache caches PC values to string names.
|
|
// note this may be a little slower than Caller()
|
|
// calls on startup, but after all PCs are cached
|
|
// this should be ~3x faster + less GC overhead.
|
|
//
|
|
// see the following benchmark:
|
|
// goos: linux
|
|
// goarch: amd64
|
|
// pkg: codeberg.org/gruf/go-caller
|
|
// cpu: AMD Ryzen 7 7840U w/ Radeon 780M Graphics
|
|
// BenchmarkCallerCache
|
|
// BenchmarkCallerCache-16 16796982 66.19 ns/op 24 B/op 3 allocs/op
|
|
// BenchmarkNoCallerCache
|
|
// BenchmarkNoCallerCache-16 5486168 219.9 ns/op 744 B/op 6 allocs/op
|
|
callerCache atomic.Pointer[map[uintptr]string]
|
|
|
|
// stringCache caches strings to minimise string memory use
|
|
// by ensuring only 1 instance of the same func name string.
|
|
stringCache atomic.Pointer[map[string]string]
|
|
)
|
|
|
|
// Clear will empty the global caller PC -> func names cache.
|
|
func Clear() { callerCache.Store(nil); stringCache.Store(nil) }
|
|
|
|
// Name returns the calling function name for given
|
|
// program counter, formatted to be useful for logging.
|
|
func Name(pc uintptr) string {
|
|
|
|
// Get frame iterator for program counter.
|
|
frames := runtime.CallersFrames([]uintptr{pc})
|
|
if frames == nil {
|
|
return "???"
|
|
}
|
|
|
|
// Get func name from frame.
|
|
frame, _ := frames.Next()
|
|
name := frame.Function
|
|
if name == "" {
|
|
return "???"
|
|
}
|
|
|
|
// Drop all but package and function name, no path.
|
|
if idx := strings.LastIndex(name, "/"); idx >= 0 {
|
|
name = name[idx+1:]
|
|
}
|
|
|
|
const params = `[...]`
|
|
|
|
// Drop any function generic type parameter markers.
|
|
if idx := strings.Index(name, params); idx >= 0 {
|
|
name = name[:idx] + name[idx+len(params):]
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
// Get will return calling func information for given PC value,
|
|
// caching func names by their PC values to reduce calls to Caller().
|
|
func Get(pc uintptr) string {
|
|
var cache map[uintptr]string
|
|
for {
|
|
// Load caller cache map.
|
|
ptr := callerCache.Load()
|
|
|
|
if ptr != nil {
|
|
// Look for stored name.
|
|
name, ok := (*ptr)[pc]
|
|
if ok {
|
|
return name
|
|
}
|
|
|
|
// Make a clone of existing caller cache map.
|
|
cache = make(map[uintptr]string, len(*ptr)+1)
|
|
for key, value := range *ptr {
|
|
cache[key] = value
|
|
}
|
|
} else {
|
|
// Allocate new caller cache map.
|
|
cache = make(map[uintptr]string, 1)
|
|
}
|
|
|
|
// Calculate caller
|
|
// name for PC value.
|
|
name := Name(pc)
|
|
name = getString(name)
|
|
|
|
// Store in map.
|
|
cache[pc] = name
|
|
|
|
// Attempt to update caller cache map pointer.
|
|
if callerCache.CompareAndSwap(ptr, &cache) {
|
|
return name
|
|
}
|
|
}
|
|
}
|
|
|
|
func getString(key string) string {
|
|
var cache map[string]string
|
|
for {
|
|
// Load string cache map.
|
|
ptr := stringCache.Load()
|
|
|
|
if ptr != nil {
|
|
// Check for existing string.
|
|
if str, ok := (*ptr)[key]; ok {
|
|
return str
|
|
}
|
|
|
|
// Make a clone of existing string cache map.
|
|
cache = make(map[string]string, len(*ptr)+1)
|
|
for key, value := range *ptr {
|
|
cache[key] = value
|
|
}
|
|
} else {
|
|
// Allocate new string cache map.
|
|
cache = make(map[string]string, 1)
|
|
}
|
|
|
|
// Store this str.
|
|
cache[key] = key
|
|
|
|
// Attempt to update string cache map pointer.
|
|
if stringCache.CompareAndSwap(ptr, &cache) {
|
|
return key
|
|
}
|
|
}
|
|
}
|