gotosocial/vendor/codeberg.org/gruf/go-caller/caller.go
kim 7af9117e0d [feature + performance] add JSON logging format (#4355)
# 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>
2025-08-09 16:23:00 +02:00

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