package logging import ( "io" "log" "os" "path/filepath" "sync" "luxtools-client/internal/config" ) const ( logBufferMaxBytes = 256 * 1024 logFileMaxBytes = 5 * 1024 * 1024 ) // Buffer stores recent log output for control pages. type Buffer struct { mu sync.Mutex buf []byte max int } func newBuffer(max int) *Buffer { return &Buffer{max: max} } func (b *Buffer) Write(p []byte) (int, error) { b.mu.Lock() defer b.mu.Unlock() if len(p) > b.max { p = p[len(p)-b.max:] } b.buf = append(b.buf, p...) if len(b.buf) > b.max { b.buf = b.buf[len(b.buf)-b.max:] } return len(p), nil } // Bytes returns a copy of the buffered log content. func (b *Buffer) Bytes() []byte { b.mu.Lock() defer b.mu.Unlock() if len(b.buf) == 0 { return nil } copyBuf := make([]byte, len(b.buf)) copy(copyBuf, b.buf) return copyBuf } func logFilePath() (string, error) { configPath, err := config.ConfigPath() if err != nil { return "", err } return filepath.Join(filepath.Dir(configPath), "luxtools-client.log"), nil } func prepareLogFile(path string, maxBytes int64) (*os.File, error) { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return nil, err } if info, err := os.Stat(path); err == nil && info.Size() > maxBytes { _ = os.Remove(path + ".old") _ = os.Rename(path, path+".old") } return os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) } // Setup builds loggers, a buffer, and optional file logging. func Setup() (*log.Logger, *log.Logger, *Buffer, string, func()) { logStore := newBuffer(logBufferMaxBytes) logPath, logErr := logFilePath() var logFile *os.File if logErr == nil { if f, err := prepareLogFile(logPath, logFileMaxBytes); err == nil { logFile = f } else { logPath = "" } } else { logPath = "" } infoWriters := []io.Writer{logStore, os.Stdout} errWriters := []io.Writer{logStore, os.Stderr} if logFile != nil { infoWriters = append(infoWriters, logFile) errWriters = append(errWriters, logFile) } infoLog := log.New(io.MultiWriter(infoWriters...), "", log.LstdFlags) errLog := log.New(io.MultiWriter(errWriters...), "ERROR: ", log.LstdFlags) cleanup := func() {} if logFile != nil { cleanup = func() { _ = logFile.Close() } } return infoLog, errLog, logStore, logPath, cleanup }