docs: add visitor pattern to G-1 for zero-copy log filtering
Introduce visit(fn func(*LogEntry) bool) on ring buffer. Passes pointer under RLock directly to callback; no intermediate snapshot slice. RecentLogs() copies only entries that pass the filter (not all 100). Sanitization (Caller strip, SanitizeFields) still requires one copy per returned entry - unavoidable. Keep recent() for test compatibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a30bbf7567
commit
10df96e6c2
1 changed files with 59 additions and 0 deletions
59
CLAUDE.md
59
CLAUDE.md
|
|
@ -691,6 +691,65 @@ Go には言語レベルの readonly 参照がないため「書き込み禁止
|
|||
|
||||
Phase 1 の変更内容を「`entries` の型を `[]*LogEntry` に変更 + `recent()` の戻り値を `[]*LogEntry` に変更」に更新すること。
|
||||
|
||||
#### さらに踏み込む: visitor パターンで中間スライスも排除
|
||||
|
||||
`*LogEntry` 保持案はポインタコピーは残る。`RecentLogs()` の実装を見ると、さらに削減できる余地がある。
|
||||
|
||||
```
|
||||
現状:
|
||||
ring(100件) → recent(0) → snapshot[]LogEntry(100件全コピー)
|
||||
→ filter/sanitize → result[M件]
|
||||
↑フィルタで弾く分まで全コピーするのが無駄
|
||||
|
||||
visitor パターン:
|
||||
ring(100件) → visit(callback) ─ フィルタを通ったM件だけコピー → result[M件]
|
||||
RLock 中に *LogEntry を直接渡す、中間スライス不要
|
||||
```
|
||||
|
||||
```go
|
||||
// ring buffer に visit() を追加
|
||||
func (rb *logRingBuffer) visit(fn func(*LogEntry) bool) {
|
||||
rb.mu.RLock()
|
||||
defer rb.mu.RUnlock()
|
||||
n := rb.count
|
||||
start := (rb.head - n + len(rb.entries)) % len(rb.entries)
|
||||
for i := n - 1; i >= 0; i-- { // 新しい順にイテレート
|
||||
if !fn(&rb.entries[(start+i)%len(rb.entries)]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RecentLogs: 中間スライスなし、フィルタを通った分だけコピー
|
||||
func RecentLogs(minLevel LogLevel, component string, limit int) []LogEntry {
|
||||
result := make([]LogEntry, 0, limit)
|
||||
ringBuf.visit(func(e *LogEntry) bool {
|
||||
if len(result) >= limit {
|
||||
return false
|
||||
}
|
||||
if lvl, ok := levelFromName[e.Level]; ok && lvl < minLevel {
|
||||
return true // コピーせずスキップ
|
||||
}
|
||||
if component != "" && e.Component != component {
|
||||
return true // コピーせずスキップ
|
||||
}
|
||||
sanitized := *e // フィルタを通った分だけコピー
|
||||
sanitized.Caller = ""
|
||||
sanitized.Fields = SanitizeFields(e.Fields)
|
||||
result = append(result, sanitized)
|
||||
return true
|
||||
})
|
||||
// visit が新しい順にイテレートするので reverse 不要
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
**避けられないコピー**: `e.Caller = ""` と `SanitizeFields()` でエントリを変更するため、返すエントリは必ず新しい値として生成が必要。コピー自体はゼロにならないが、**フィルタで弾くエントリのコピーは完全になくなる**。
|
||||
|
||||
**制約**: `visit()` は RLock を保持したまま callback を呼ぶ。callback 内でポインタを保存・持ち出してはいけない(`*e` として即コピーするのは安全)。
|
||||
|
||||
**最終的な Phase 1 の変更内容**: `recent()` を残しつつ `visit()` を追加 → `RecentLogs()` を `visit()` ベースに書き換え。`recent()` は既存テストとの互換のため残す。
|
||||
|
||||
### G-2. Phase 3-1: `FormatPlanDisplay()` が修正対象に含まれていない
|
||||
|
||||
`GetMemoryContext()` 以外にも `FormatPlanDisplay()` が同じ多重 `ReadLongTerm()` 問題を持つ。呼び出しチェーンは以下の通り:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue