mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 19:32:26 -06: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
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |