jim800121chen c54f16fca0 Initial commit: visionA monorepo with local-tool subproject
local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
            Wails IPC raise endpoint, stale process cleanup

.autoflow/: full PRD / Design Spec / Architecture / Testing docs
            (4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:10:38 +08:00

74 lines
1.7 KiB
Go

package logger
import (
"sync"
"time"
)
// LogEntry represents a single structured log entry for WebSocket streaming.
type LogEntry struct {
Timestamp string `json:"timestamp"`
Level string `json:"level"`
Message string `json:"message"`
}
// BroadcastFunc is called whenever a new log entry is produced.
type BroadcastFunc func(entry LogEntry)
// Broadcaster captures log output, maintains a ring buffer of recent entries,
// and notifies subscribers (via BroadcastFunc) in real time.
type Broadcaster struct {
mu sync.RWMutex
buffer []LogEntry
bufSize int
pos int
full bool
broadcast BroadcastFunc
}
// NewBroadcaster creates a broadcaster with the given ring buffer capacity.
func NewBroadcaster(bufferSize int, fn BroadcastFunc) *Broadcaster {
return &Broadcaster{
buffer: make([]LogEntry, bufferSize),
bufSize: bufferSize,
broadcast: fn,
}
}
// Push adds a log entry to the ring buffer and broadcasts it.
func (b *Broadcaster) Push(level, message string) {
entry := LogEntry{
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
Level: level,
Message: message,
}
b.mu.Lock()
b.buffer[b.pos] = entry
b.pos = (b.pos + 1) % b.bufSize
if b.pos == 0 {
b.full = true
}
b.mu.Unlock()
if b.broadcast != nil {
b.broadcast(entry)
}
}
// Recent returns a copy of all buffered log entries in chronological order.
func (b *Broadcaster) Recent() []LogEntry {
b.mu.RLock()
defer b.mu.RUnlock()
if !b.full {
result := make([]LogEntry, b.pos)
copy(result, b.buffer[:b.pos])
return result
}
result := make([]LogEntry, b.bufSize)
copy(result, b.buffer[b.pos:])
copy(result[b.bufSize-b.pos:], b.buffer[:b.pos])
return result
}