Implements a comprehensive monitoring system for the admin interface: Backend: - New monitoring package with Redis ring buffer for log storage - Zerolog MultiWriter to capture logs to Redis - System stats collection (CPU, memory, disk, goroutines, GC) - HTTP metrics middleware (request counts, latency, error rates) - Asynq queue stats for worker process - WebSocket endpoint for real-time log streaming - Admin auth middleware now accepts token in query params (for WebSocket) Frontend: - New monitoring page with tabs (Overview, Logs, API Stats, Worker Stats) - Real-time log viewer with level filtering and search - System stats cards showing CPU, memory, goroutines, uptime - HTTP endpoint statistics table - Asynq queue depth visualization - Enable/disable monitoring toggle in settings Memory safeguards: - Max 200 unique endpoints tracked - Hourly stats reset to prevent unbounded growth - Max 1000 log entries in ring buffer - Max 1000 latency samples for P95 calculation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package monitoring
|
|
|
|
import "time"
|
|
|
|
// LogEntry represents a single log entry captured from zerolog
|
|
type LogEntry struct {
|
|
ID string `json:"id"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Level string `json:"level"` // debug, info, warn, error, fatal
|
|
Message string `json:"message"`
|
|
Caller string `json:"caller"` // file:line
|
|
Process string `json:"process"` // "api" or "worker"
|
|
Fields map[string]any `json:"fields"` // Additional structured fields
|
|
}
|
|
|
|
// SystemStats contains all system and runtime statistics
|
|
type SystemStats struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Process string `json:"process"`
|
|
CPU CPUStats `json:"cpu"`
|
|
Memory MemoryStats `json:"memory"`
|
|
Disk DiskStats `json:"disk"`
|
|
Runtime RuntimeStats `json:"runtime"`
|
|
HTTP *HTTPStats `json:"http,omitempty"` // API only
|
|
Asynq *AsynqStats `json:"asynq,omitempty"` // Worker only
|
|
}
|
|
|
|
// CPUStats contains CPU usage information
|
|
type CPUStats struct {
|
|
UsagePercent float64 `json:"usage_percent"`
|
|
NumCPU int `json:"num_cpu"`
|
|
LoadAvg1 float64 `json:"load_avg_1"`
|
|
LoadAvg5 float64 `json:"load_avg_5"`
|
|
LoadAvg15 float64 `json:"load_avg_15"`
|
|
}
|
|
|
|
// MemoryStats contains both system and Go runtime memory info
|
|
type MemoryStats struct {
|
|
// System memory
|
|
UsedBytes uint64 `json:"used_bytes"`
|
|
TotalBytes uint64 `json:"total_bytes"`
|
|
UsagePercent float64 `json:"usage_percent"`
|
|
// Go heap
|
|
HeapAlloc uint64 `json:"heap_alloc"`
|
|
HeapSys uint64 `json:"heap_sys"`
|
|
HeapInuse uint64 `json:"heap_inuse"`
|
|
}
|
|
|
|
// DiskStats contains disk usage information
|
|
type DiskStats struct {
|
|
UsedBytes uint64 `json:"used_bytes"`
|
|
TotalBytes uint64 `json:"total_bytes"`
|
|
FreeBytes uint64 `json:"free_bytes"`
|
|
UsagePercent float64 `json:"usage_percent"`
|
|
}
|
|
|
|
// RuntimeStats contains Go runtime information
|
|
type RuntimeStats struct {
|
|
Goroutines int `json:"goroutines"`
|
|
NumGC uint32 `json:"num_gc"`
|
|
LastGCPause uint64 `json:"last_gc_pause_ns"`
|
|
Uptime int64 `json:"uptime_seconds"`
|
|
}
|
|
|
|
// HTTPStats contains HTTP request metrics (API server only)
|
|
type HTTPStats struct {
|
|
RequestsTotal int64 `json:"requests_total"`
|
|
RequestsPerMinute float64 `json:"requests_per_minute"`
|
|
AvgLatencyMs float64 `json:"avg_latency_ms"`
|
|
ErrorRate float64 `json:"error_rate"`
|
|
ByEndpoint map[string]EndpointStats `json:"by_endpoint"`
|
|
ByStatusCode map[int]int64 `json:"by_status_code"`
|
|
}
|
|
|
|
// EndpointStats contains per-endpoint HTTP metrics
|
|
type EndpointStats struct {
|
|
Count int64 `json:"count"`
|
|
AvgLatencyMs float64 `json:"avg_latency_ms"`
|
|
P95LatencyMs float64 `json:"p95_latency_ms"`
|
|
ErrorRate float64 `json:"error_rate"`
|
|
}
|
|
|
|
// AsynqStats contains Asynq job queue metrics (Worker only)
|
|
type AsynqStats struct {
|
|
Queues map[string]QueueStats `json:"queues"`
|
|
}
|
|
|
|
// QueueStats contains stats for a single Asynq queue
|
|
type QueueStats struct {
|
|
Pending int `json:"pending"`
|
|
Active int `json:"active"`
|
|
Scheduled int `json:"scheduled"`
|
|
Retry int `json:"retry"`
|
|
Archived int `json:"archived"`
|
|
Completed int `json:"completed"`
|
|
Failed int `json:"failed"`
|
|
}
|
|
|
|
// LogFilters for querying logs
|
|
type LogFilters struct {
|
|
Level string `form:"level"`
|
|
Process string `form:"process"`
|
|
Search string `form:"search"`
|
|
Limit int `form:"limit,default=100"`
|
|
}
|
|
|
|
// GetLimit returns the limit with bounds checking
|
|
func (f *LogFilters) GetLimit() int {
|
|
if f.Limit <= 0 {
|
|
return 100
|
|
}
|
|
if f.Limit > 1000 {
|
|
return 1000
|
|
}
|
|
return f.Limit
|
|
}
|
|
|
|
// WebSocket message types
|
|
const (
|
|
WSMessageTypeLog = "log"
|
|
WSMessageTypeStats = "stats"
|
|
)
|
|
|
|
// WSMessage wraps messages sent over WebSocket
|
|
type WSMessage struct {
|
|
Type string `json:"type"` // "log" or "stats"
|
|
Data any `json:"data"`
|
|
}
|