Add real-time log monitoring and system stats dashboard

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>
This commit is contained in:
Trey t
2025-12-09 10:26:40 -06:00
parent 12eac24632
commit eb127fda20
31 changed files with 2880 additions and 213 deletions

View File

@@ -8,10 +8,12 @@ import (
"syscall"
"github.com/hibiken/asynq"
"github.com/redis/go-redis/v9"
"github.com/rs/zerolog/log"
"github.com/treytartt/casera-api/internal/config"
"github.com/treytartt/casera-api/internal/database"
"github.com/treytartt/casera-api/internal/monitoring"
"github.com/treytartt/casera-api/internal/push"
"github.com/treytartt/casera-api/internal/repositories"
"github.com/treytartt/casera-api/internal/services"
@@ -70,6 +72,43 @@ func main() {
log.Fatal().Err(err).Msg("Failed to parse Redis URL")
}
// Initialize monitoring service (if Redis is available)
var monitoringService *monitoring.Service
redisClientOpt, ok := redisOpt.(asynq.RedisClientOpt)
if ok {
redisClient := redis.NewClient(&redis.Options{
Addr: redisClientOpt.Addr,
Password: redisClientOpt.Password,
DB: redisClientOpt.DB,
})
// Verify Redis connection
if err := redisClient.Ping(context.Background()).Err(); err != nil {
log.Warn().Err(err).Msg("Failed to connect to Redis for monitoring - monitoring disabled")
} else {
monitoringService = monitoring.NewService(monitoring.Config{
Process: "worker",
RedisClient: redisClient,
DB: db, // Pass database for enable_monitoring setting sync
})
// Reinitialize logger with monitoring writer
utils.InitLoggerWithWriter(cfg.Server.Debug, monitoringService.LogWriter())
// Create Asynq inspector for queue statistics
inspector := asynq.NewInspector(redisOpt)
monitoringService.SetAsynqInspector(inspector)
// Start stats collection
monitoringService.Start()
defer monitoringService.Stop()
log.Info().
Bool("log_capture_enabled", monitoringService.IsEnabled()).
Msg("Monitoring service initialized")
}
}
// Create Asynq server
srv := asynq.NewServer(
redisOpt,