26a43708b7
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
75 lines
2.7 KiB
Go
75 lines
2.7 KiB
Go
package report
|
|
|
|
import (
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Controller self-log tail (v0.116.0 observability) — the CONTROLLER's own debug
|
|
// ring riding the report channel, the exact consume-once ACK-flag shape of
|
|
// logtail.go (which stays byte-compatible; these are ADDITIVE fields): the hub
|
|
// stores a pending per-customer request; the report ACK advertises it as
|
|
// controller_log_requested; the NEXT report carries controller_log_tail; the hub
|
|
// clears the pending request on arrival. A failed push leaves the hub request
|
|
// pending — the next ACK re-arms it (fail-safe retry, no duplicate shipping).
|
|
|
|
// controllerLogMaxBytes caps the shipped tail (newest lines kept — the ring's
|
|
// Lines budget, mirrored agent-side at the same 128 KB).
|
|
const controllerLogMaxBytes = 128 * 1024
|
|
|
|
// ControllerLogTail is the report's on-demand controller ring tail.
|
|
type ControllerLogTail struct {
|
|
CollectedAt time.Time `json:"collected_at"`
|
|
Lines []string `json:"lines"`
|
|
}
|
|
|
|
var (
|
|
selfTailMu sync.Mutex
|
|
selfTailPending bool
|
|
selfTailSource func(maxBytes int) []string
|
|
)
|
|
|
|
// SetPendingControllerLog records the ACK's controller_log_requested flag. The
|
|
// hub is the source of truth: an ACK without the flag clears any stale pending.
|
|
func SetPendingControllerLog(requested bool) {
|
|
selfTailMu.Lock()
|
|
defer selfTailMu.Unlock()
|
|
selfTailPending = requested
|
|
}
|
|
|
|
// SetControllerLogSource wires the debug ring (web.LogBuffer.Lines) — set once by
|
|
// main.go at startup. Unset means a pending request is silently unfulfillable
|
|
// (drained but nothing shipped; the hub re-arms on the next ACK).
|
|
func SetControllerLogSource(src func(maxBytes int) []string) {
|
|
selfTailMu.Lock()
|
|
defer selfTailMu.Unlock()
|
|
selfTailSource = src
|
|
}
|
|
|
|
// drainPendingControllerLog takes + CLEARS the pending flag and returns the
|
|
// source (consume-once controller-side, the drainPendingLogTails shape).
|
|
func drainPendingControllerLog() (func(maxBytes int) []string, bool) {
|
|
selfTailMu.Lock()
|
|
defer selfTailMu.Unlock()
|
|
pending := selfTailPending
|
|
selfTailPending = false
|
|
return selfTailSource, pending
|
|
}
|
|
|
|
// buildControllerLogTail attaches the ring tail when a pull is pending. The INFO
|
|
// line is the CUSTOMER-VISIBLE transparency record (it lands in the ring/viewer):
|
|
// an operator pull of this box's controller log is never silent.
|
|
func buildControllerLogTail(logger *log.Logger) *ControllerLogTail {
|
|
src, pending := drainPendingControllerLog()
|
|
if !pending || src == nil {
|
|
return nil
|
|
}
|
|
// Logged BEFORE collecting, so the transparency line rides in the tail it announces.
|
|
if logger != nil {
|
|
logger.Printf("[INFO] [report] operator log pull served (controller ring)")
|
|
}
|
|
lines := src(controllerLogMaxBytes)
|
|
return &ControllerLogTail{CollectedAt: time.Now().UTC(), Lines: lines}
|
|
}
|