v0.116.0: observability pass — always-on debug ring + leveled sweep + agent tab + self-log pull — MinAgent: 0.81.0
Capture layer: LogBuffer always exists; logger = MultiWriter(LevelFilterWriter (stdout, logging.level), ring) so DEBUG detail exists remotely without a config flip while docker logs keep respecting the level. New internal/logx leveled helpers. Report ACK gains controller_log_requested (additive); next report ships controller_log_tail (128KB, consume-once, app-tail wire byte-compatible). Debug page: Vezérlő|Ügynök tabs; agent tab proxies agent /debug/logs with the pre-0.83 notice on typed 404. Sweep: netstorage_job phases, netprobe, handler validation refusals + orphan WARN, SupportsWithSource gate line, agentapi per-call DEBUG, migrate phase lines, tier2/offbox unswallowed persists. Red-proofs: filter-disabled, drain-removed, dropped-phase-line all FAIL. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -103,6 +103,47 @@ func (lb *LogBuffer) Entries(minLevel string, limit int, after time.Time) ([]Log
|
||||
return result, total
|
||||
}
|
||||
|
||||
// Lines renders the held entries as plain text lines (chronological), dropping
|
||||
// from the HEAD (oldest) to honor maxBytes so the newest lines survive — the
|
||||
// report-channel controller_log_tail budget (maxBytes ≤ 0 → no byte cap).
|
||||
// Format mirrors the agent ring's: "<RFC3339> [<LEVEL>] <message>".
|
||||
func (lb *LogBuffer) Lines(maxBytes int) []string {
|
||||
lb.mu.RLock()
|
||||
total := lb.size
|
||||
start := 0
|
||||
if !lb.full {
|
||||
total = lb.pos
|
||||
} else {
|
||||
start = lb.pos
|
||||
}
|
||||
entries := make([]LogEntry, 0, total)
|
||||
for i := 0; i < total; i++ {
|
||||
entries = append(entries, lb.entries[(start+i)%lb.size])
|
||||
}
|
||||
lb.mu.RUnlock()
|
||||
lines := make([]string, len(entries))
|
||||
for i, e := range entries {
|
||||
src := ""
|
||||
if e.Source != "" {
|
||||
src = e.Source + ": "
|
||||
}
|
||||
lines[i] = e.Timestamp.Format(time.RFC3339) + " [" + e.Level + "] " + src + e.Message
|
||||
}
|
||||
if maxBytes <= 0 {
|
||||
return lines
|
||||
}
|
||||
budget := 0
|
||||
keepFrom := len(lines)
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
budget += len(lines[i]) + 1 // +1 for the newline it represents
|
||||
if budget > maxBytes {
|
||||
break
|
||||
}
|
||||
keepFrom = i
|
||||
}
|
||||
return lines[keepFrom:]
|
||||
}
|
||||
|
||||
// parseLine parses a single log line into a LogEntry.
|
||||
func parseLine(line string) LogEntry {
|
||||
entry := LogEntry{
|
||||
|
||||
Reference in New Issue
Block a user