mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 14:22: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
 | |
| 		}
 | |
| 	}
 | |
| }
 |