// Package log implements append-only access and share logging for the proxy. // // al, result := log.NewAccessLog("/var/log/proxy-access.log") // bus.Subscribe(proxy.EventLogin, al.OnLogin) // bus.Subscribe(proxy.EventClose, al.OnClose) package log import ( "fmt" "os" "sync" "dappco.re/go/core/proxy" ) // AccessLog writes connection lifecycle lines to an append-only text file. // // Line format (connect): 2026-04-04T12:00:00Z CONNECT // Line format (close): 2026-04-04T12:00:00Z CLOSE rx= tx= // // al, result := log.NewAccessLog("/var/log/proxy-access.log") // bus.Subscribe(proxy.EventLogin, al.OnLogin) // bus.Subscribe(proxy.EventClose, al.OnClose) type AccessLog struct { path string mu sync.Mutex // f is opened append-only on first write; nil until first event. // Uses core.File for I/O abstraction. f *os.File closed bool } // NewAccessLog stores the destination path and lazily opens it on first write. // // al := log.NewAccessLog("/var/log/proxy-access.log") func NewAccessLog(path string) *AccessLog { return &AccessLog{path: path} } // OnLogin writes a CONNECT line. Called synchronously from the event bus. // // al.OnLogin(proxy.Event{Miner: miner}) func (l *AccessLog) OnLogin(event proxy.Event) { if event.Miner == nil { return } line := fmt.Sprintf("%s CONNECT %s %s %s\n", timestamp(), event.Miner.IP(), event.Miner.User(), event.Miner.Agent(), ) l.writeLine(line) } // OnClose writes a CLOSE line with byte counts. // // al.OnClose(proxy.Event{Miner: miner}) func (l *AccessLog) OnClose(event proxy.Event) { if event.Miner == nil { return } line := fmt.Sprintf("%s CLOSE %s %s rx=%d tx=%d\n", timestamp(), event.Miner.IP(), event.Miner.User(), event.Miner.RX(), event.Miner.TX(), ) l.writeLine(line) } // Close releases the append-only file handle if it has been opened. // // al.Close() func (l *AccessLog) Close() { if l == nil { return } l.mu.Lock() defer l.mu.Unlock() if l.closed { return } l.closed = true if l.f != nil { _ = l.f.Close() l.f = nil } } func (l *AccessLog) writeLine(line string) { if l == nil || l.path == "" { return } l.mu.Lock() defer l.mu.Unlock() if l.closed { return } if l.f == nil { file, errorValue := os.OpenFile(l.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if errorValue != nil { return } l.f = file } _, _ = l.f.WriteString(line) }