perf: add visitReverse to logger ring buffer, avoid full copy in RecentLogs
RecentLogs previously copied ALL ring buffer entries via recent(0), then iterated in reverse to filter. Now uses visitReverse() which iterates newest-to-oldest under read lock, copying only matching entries — eliminates the full-buffer allocation for filtered queries. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a9ff9b1bf7
commit
5582ff9551
1 changed files with 26 additions and 9 deletions
|
|
@ -93,6 +93,20 @@ func (rb *logRingBuffer) recent(limit int) []LogEntry {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// visitReverse iterates entries from newest to oldest under the read lock.
|
||||||
|
// The callback receives a pointer to the internal entry (valid only during
|
||||||
|
// the call). Return false to stop iteration.
|
||||||
|
func (rb *logRingBuffer) visitReverse(fn func(*LogEntry) bool) {
|
||||||
|
rb.mu.RLock()
|
||||||
|
defer rb.mu.RUnlock()
|
||||||
|
for i := 0; i < rb.count; i++ {
|
||||||
|
idx := (rb.head - 1 - i + len(rb.entries)) % len(rb.entries)
|
||||||
|
if !fn(&rb.entries[idx]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LogSubscriber receives log entries matching its filter.
|
// LogSubscriber receives log entries matching its filter.
|
||||||
type LogSubscriber struct {
|
type LogSubscriber struct {
|
||||||
Ch chan LogEntry
|
Ch chan LogEntry
|
||||||
|
|
@ -355,20 +369,23 @@ func broadcastToSubscribers(entry LogEntry) {
|
||||||
// RecentLogs returns recent log entries from the ring buffer, optionally filtered
|
// RecentLogs returns recent log entries from the ring buffer, optionally filtered
|
||||||
// by minimum level and component. The Caller field is stripped for security.
|
// by minimum level and component. The Caller field is stripped for security.
|
||||||
func RecentLogs(minLevel LogLevel, component string, limit int) []LogEntry {
|
func RecentLogs(minLevel LogLevel, component string, limit int) []LogEntry {
|
||||||
all := ringBuf.recent(0) // get all
|
|
||||||
result := make([]LogEntry, 0, limit)
|
result := make([]LogEntry, 0, limit)
|
||||||
for i := len(all) - 1; i >= 0 && len(result) < limit; i-- {
|
ringBuf.visitReverse(func(e *LogEntry) bool {
|
||||||
e := all[i]
|
if len(result) >= limit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lvl, ok := levelFromName[e.Level]; ok && lvl < minLevel {
|
if lvl, ok := levelFromName[e.Level]; ok && lvl < minLevel {
|
||||||
continue
|
return true
|
||||||
}
|
}
|
||||||
if component != "" && e.Component != component {
|
if component != "" && e.Component != component {
|
||||||
continue
|
return true
|
||||||
}
|
}
|
||||||
e.Caller = "" // strip for security
|
entry := *e
|
||||||
e.Fields = SanitizeFields(e.Fields) // mask sensitive values
|
entry.Caller = ""
|
||||||
result = append(result, e)
|
entry.Fields = SanitizeFields(e.Fields)
|
||||||
}
|
result = append(result, entry)
|
||||||
|
return true
|
||||||
|
})
|
||||||
// Reverse so oldest first
|
// Reverse so oldest first
|
||||||
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
|
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
|
||||||
result[i], result[j] = result[j], result[i]
|
result[i], result[j] = result[j], result[i]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue