Something went wrong. Try again.
search and/or read your saved and liked bluesky posts
wails go svelte sqlite desktop bluesky
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272package main
import ( "bufio" "context" "fmt" "io" "os" "path/filepath" "strings" "sync" "time"
"github.com/wailsapp/wails/v2/pkg/runtime")
// LogLevel represents the severity of a log messagetype LogLevel string
const ( DebugLevel LogLevel = "DEBUG" InfoLevel LogLevel = "INFO" WarnLevel LogLevel = "WARN" ErrorLevel LogLevel = "ERROR")
// LogEntry represents a single log entrytype LogEntry struct { Level string `json:"level"` Message string `json:"message"` Timestamp time.Time `json:"timestamp"`}
// LogService provides logging functionality via Wails bindingstype LogService struct { ctx context.Context mu sync.RWMutex entries []LogEntry maxEntries int file *os.File writer *LogWriter level LogLevel}
// LogWriter implements io.Writer and emits Wails eventstype LogWriter struct { service *LogService mu sync.Mutex}
func (w *LogWriter) Write(p []byte) (n int, err error) { w.mu.Lock() defer w.mu.Unlock()
lines := strings.Split(string(p), "\n") for _, line := range lines { line = strings.TrimSpace(line) if line == "" { continue }
level := w.parseLevel(line)
entry := LogEntry{ Level: string(level), Message: line, Timestamp: time.Now(), }
w.service.addEntry(entry) w.service.emitLogLine(entry) }
return len(p), nil}
func (w *LogWriter) parseLevel(line string) LogLevel { upper := strings.ToUpper(line) if strings.Contains(upper, "ERROR") || strings.Contains(upper, "ERR") { return ErrorLevel } if strings.Contains(upper, "WARN") || strings.Contains(upper, "WARNING") { return WarnLevel } if strings.Contains(upper, "DEBUG") || strings.Contains(upper, "DBG") { return DebugLevel } return InfoLevel}
// NewLogService creates a new LogService instancefunc NewLogService() *LogService { return &LogService{ entries: make([]LogEntry, 0), maxEntries: 1000, level: InfoLevel, }}
func (s *LogService) setContext(ctx context.Context) { s.ctx = ctx}
// Initialize sets up the log service with a file writerfunc (s *LogService) Initialize() error { logPath := s.getLogPath() logDir := filepath.Dir(logPath) if err := os.MkdirAll(logDir, 0755); err != nil { return fmt.Errorf("failed to create log directory: %w", err) }
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("failed to open log file: %w", err) } s.file = file
s.writer = &LogWriter{service: s} return nil}
// Close closes the log filefunc (s *LogService) Close() error { if s.file != nil { return s.file.Close() } return nil}
// GetWriter returns the io.Writer for loggingfunc (s *LogService) GetWriter() io.Writer { if s.writer == nil { return io.Discard } return s.writer}
// GetMultiWriter returns a MultiWriter that writes to both the log file and the event emitterfunc (s *LogService) GetMultiWriter() io.Writer { if s.file == nil || s.writer == nil { return io.Discard } return io.MultiWriter(s.file, s.writer)}
// GetEntries returns all log entriesfunc (s *LogService) GetEntries() []LogEntry { s.mu.RLock() defer s.mu.RUnlock()
result := make([]LogEntry, len(s.entries)) copy(result, s.entries) return result}
// GetEntriesByLevel returns log entries filtered by levelfunc (s *LogService) GetEntriesByLevel(level string) []LogEntry { s.mu.RLock() defer s.mu.RUnlock()
var result []LogEntry for _, entry := range s.entries { if entry.Level == level { result = append(result, entry) } } return result}
// Clear clears all log entriesfunc (s *LogService) Clear() { s.mu.Lock() defer s.mu.Unlock()
s.entries = make([]LogEntry, 0) s.emitLogCleared()}
// SetLevel sets the minimum log levelfunc (s *LogService) SetLevel(level string) { s.mu.Lock() defer s.mu.Unlock()
s.level = LogLevel(level)}
// GetLevel returns the current log levelfunc (s *LogService) GetLevel() string { s.mu.RLock() defer s.mu.RUnlock()
return string(s.level)}
func (s *LogService) addEntry(entry LogEntry) { s.mu.Lock() defer s.mu.Unlock()
if !s.shouldLog(entry.Level) { return }
s.entries = append(s.entries, entry)
if len(s.entries) > s.maxEntries { s.entries = s.entries[len(s.entries)-s.maxEntries:] }}
func (s *LogService) shouldLog(entryLevel string) bool { levels := map[LogLevel]int{ DebugLevel: 0, InfoLevel: 1, WarnLevel: 2, ErrorLevel: 3, }
entryIdx := levels[LogLevel(entryLevel)] currentIdx := levels[s.level]
return entryIdx >= currentIdx}
func (s *LogService) emitLogLine(entry LogEntry) { if s.ctx != nil { runtime.EventsEmit(s.ctx, "log:line", entry) }}
func (s *LogService) emitLogCleared() { if s.ctx != nil { runtime.EventsEmit(s.ctx, "log:cleared", map[string]any{}) }}
func (s *LogService) getLogPath() string { if path := os.Getenv("BSKY_BROWSER_LOG"); path != "" { return path }
configDir := os.Getenv("XDG_CONFIG_HOME") if configDir == "" { home, _ := os.UserHomeDir() configDir = filepath.Join(home, ".config") }
appDir := filepath.Join(configDir, "bsky-browser", "logs") timestamp := time.Now().Format("2006-01-02_15-04-05") return filepath.Join(appDir, fmt.Sprintf("bsky-browser_%s.log", timestamp))}
// BufferedLogWriter wraps a writer with buffering for better performancetype BufferedLogWriter struct { writer *bufio.Writer service *LogService}
func NewBufferedLogWriter(service *LogService) *BufferedLogWriter { return &BufferedLogWriter{ writer: bufio.NewWriter(service.GetMultiWriter()), service: service, }}
func (w *BufferedLogWriter) Write(p []byte) (n int, err error) { return w.writer.Write(p)}
func (w *BufferedLogWriter) Flush() error { return w.writer.Flush()}