Files
diachron/logger/store.go
Michael Wolf dc5a70ba33 Add logging service
New Go program (logger/) that:
- Accepts POSTed JSON log messages via POST /log
- Stores last N messages in a ring buffer (default 1M)
- Retrieves logs via GET /logs with limit/before/after filters
- Shows status via GET /status

Also updates express/logging.ts to POST messages to the logger service.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 20:45:34 -06:00

127 lines
2.4 KiB
Go

package main
import (
"sync"
)
// Message represents a log entry from the express backend
type Message struct {
Timestamp int64 `json:"timestamp"`
Source string `json:"source"` // "logging" | "diagnostic" | "user"
Text []string `json:"text"`
}
// LogStore is a thread-safe ring buffer for log messages
type LogStore struct {
mu sync.RWMutex
messages []Message
head int // next write position
full bool // whether buffer has wrapped
capacity int
}
// NewLogStore creates a new log store with the given capacity
func NewLogStore(capacity int) *LogStore {
return &LogStore{
messages: make([]Message, capacity),
capacity: capacity,
}
}
// Add inserts a new message into the store
func (s *LogStore) Add(msg Message) {
s.mu.Lock()
defer s.mu.Unlock()
s.messages[s.head] = msg
s.head++
if s.head >= s.capacity {
s.head = 0
s.full = true
}
}
// Count returns the number of messages in the store
func (s *LogStore) Count() int {
s.mu.RLock()
defer s.mu.RUnlock()
if s.full {
return s.capacity
}
return s.head
}
// GetRecent returns the most recent n messages, newest first
func (s *LogStore) GetRecent(n int) []Message {
s.mu.RLock()
defer s.mu.RUnlock()
count := s.Count()
if n > count {
n = count
}
if n == 0 {
return nil
}
result := make([]Message, n)
pos := s.head - 1
for i := 0; i < n; i++ {
if pos < 0 {
pos = s.capacity - 1
}
result[i] = s.messages[pos]
pos--
}
return result
}
// Filter parameters for retrieving logs
type FilterParams struct {
Limit int // max messages to return (0 = default 100)
Before int64 // only messages before this timestamp
After int64 // only messages after this timestamp
}
// GetFiltered returns messages matching the filter criteria
func (s *LogStore) GetFiltered(params FilterParams) []Message {
s.mu.RLock()
defer s.mu.RUnlock()
limit := params.Limit
if limit <= 0 {
limit = 100
}
count := s.Count()
if count == 0 {
return nil
}
result := make([]Message, 0, limit)
pos := s.head - 1
for i := 0; i < count && len(result) < limit; i++ {
if pos < 0 {
pos = s.capacity - 1
}
msg := s.messages[pos]
// Apply filters
if params.Before > 0 && msg.Timestamp >= params.Before {
pos--
continue
}
if params.After > 0 && msg.Timestamp <= params.After {
pos--
continue
}
result = append(result, msg)
pos--
}
return result
}