Files
honeyDueAPI/internal/monitoring/service.go
Trey t 4976eafc6c Rebrand from Casera/MyCrib to honeyDue
Total rebrand across all Go API source files:
- Go module path: casera-api -> honeydue-api
- All imports updated (130+ files)
- Docker: containers, images, networks renamed
- Email templates: support email, noreply, icon URL
- Domains: casera.app/mycrib.treytartt.com -> honeyDue.treytartt.com
- Bundle IDs: com.tt.casera -> com.tt.honeyDue
- IAP product IDs updated
- Landing page, admin panel, config defaults
- Seeds, CI workflows, Makefile, docs
- Database table names preserved (no migration needed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 06:33:38 -06:00

200 lines
5.1 KiB
Go

package monitoring
import (
"io"
"time"
"github.com/hibiken/asynq"
"github.com/labstack/echo/v4"
"github.com/redis/go-redis/v9"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
"github.com/treytartt/honeydue-api/internal/models"
)
const (
// DefaultStatsInterval is the default interval for collecting/publishing stats
DefaultStatsInterval = 5 * time.Second
// SettingsSyncInterval is how often to check the database for enable_monitoring setting
SettingsSyncInterval = 30 * time.Second
)
// Service orchestrates all monitoring components
type Service struct {
process string
logBuffer *LogBuffer
statsStore *StatsStore
collector *Collector
httpCollector *HTTPStatsCollector
handler *Handler
logWriter *RedisLogWriter
db *gorm.DB
settingsStopCh chan struct{}
}
// Config holds configuration for the monitoring service
type Config struct {
Process string // "api" or "worker"
RedisClient *redis.Client // Redis client for log buffer
StatsInterval time.Duration // Interval for stats collection (default 5s)
DB *gorm.DB // Database for checking enable_monitoring setting (optional)
}
// NewService creates a new monitoring service
func NewService(cfg Config) *Service {
if cfg.StatsInterval == 0 {
cfg.StatsInterval = DefaultStatsInterval
}
// Create components
logBuffer := NewLogBuffer(cfg.RedisClient)
statsStore := NewStatsStore(cfg.RedisClient)
collector := NewCollector(cfg.Process, statsStore)
handler := NewHandler(logBuffer, statsStore)
logWriter := NewRedisLogWriter(logBuffer, cfg.Process)
// For API server, create HTTP stats collector
var httpCollector *HTTPStatsCollector
if cfg.Process == "api" {
httpCollector = NewHTTPStatsCollector()
collector.SetHTTPCollector(httpCollector)
}
svc := &Service{
process: cfg.Process,
logBuffer: logBuffer,
statsStore: statsStore,
collector: collector,
httpCollector: httpCollector,
handler: handler,
logWriter: logWriter,
db: cfg.DB,
settingsStopCh: make(chan struct{}),
}
// Check initial setting from database
if cfg.DB != nil {
svc.syncSettingsFromDB()
}
return svc
}
// SetAsynqInspector sets the Asynq inspector for worker stats
func (s *Service) SetAsynqInspector(inspector *asynq.Inspector) {
s.collector.SetAsynqInspector(inspector)
}
// Start begins collecting and publishing stats
func (s *Service) Start() {
log.Info().
Str("process", s.process).
Dur("interval", DefaultStatsInterval).
Bool("enabled", s.logWriter.IsEnabled()).
Msg("Starting monitoring service")
s.collector.StartPublishing(DefaultStatsInterval)
// Start settings sync if database is available
if s.db != nil {
go s.startSettingsSync()
}
}
// Stop stops the monitoring service
func (s *Service) Stop() {
// Stop settings sync
close(s.settingsStopCh)
s.collector.Stop()
// Flush and close the log writer's background goroutine
s.logWriter.Close()
log.Info().Str("process", s.process).Msg("Monitoring service stopped")
}
// syncSettingsFromDB checks the database for the enable_monitoring setting
func (s *Service) syncSettingsFromDB() {
if s.db == nil {
return
}
var settings models.SubscriptionSettings
err := s.db.First(&settings, 1).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
// No settings record, default to enabled
s.logWriter.SetEnabled(true)
}
// On other errors, keep current state
return
}
wasEnabled := s.logWriter.IsEnabled()
s.logWriter.SetEnabled(settings.EnableMonitoring)
if wasEnabled != settings.EnableMonitoring {
log.Info().
Str("process", s.process).
Bool("enabled", settings.EnableMonitoring).
Msg("Monitoring log capture setting changed")
}
}
// startSettingsSync periodically checks the database for settings changes
func (s *Service) startSettingsSync() {
ticker := time.NewTicker(SettingsSyncInterval)
defer ticker.Stop()
for {
select {
case <-s.settingsStopCh:
return
case <-ticker.C:
s.syncSettingsFromDB()
}
}
}
// SetEnabled manually enables or disables log capture
func (s *Service) SetEnabled(enabled bool) {
s.logWriter.SetEnabled(enabled)
}
// IsEnabled returns whether log capture is enabled
func (s *Service) IsEnabled() bool {
return s.logWriter.IsEnabled()
}
// SetDB sets the database connection for settings sync
// This can be called after NewService if DB wasn't available during initialization
func (s *Service) SetDB(db *gorm.DB) {
s.db = db
s.syncSettingsFromDB()
}
// LogWriter returns an io.Writer for zerolog that captures logs to Redis
func (s *Service) LogWriter() io.Writer {
return s.logWriter
}
// Handler returns the HTTP handler for monitoring endpoints
func (s *Service) Handler() *Handler {
return s.handler
}
// HTTPCollector returns the HTTP stats collector (nil for worker)
func (s *Service) HTTPCollector() *HTTPStatsCollector {
return s.httpCollector
}
// MetricsMiddleware returns the Echo middleware for HTTP metrics (API server only)
func (s *Service) MetricsMiddleware() echo.MiddlewareFunc {
if s.httpCollector == nil {
return nil
}
return MetricsMiddleware(s.httpCollector)
}