diff --git a/internal/merger/merger.go b/internal/merger/merger.go index 0215493..2b245ba 100644 --- a/internal/merger/merger.go +++ b/internal/merger/merger.go @@ -271,6 +271,7 @@ type Merger struct { historyLimit int // Maximum history points to retain staleTimeout time.Duration // Time before aircraft considered stale (15 seconds) updateMetrics map[uint32]*updateMetric // ICAO24 -> update rate calculation data + validationLog map[uint32]time.Time // ICAO24 -> last validation log time (rate limiting) } // updateMetric tracks recent update times for calculating update rates. @@ -301,6 +302,7 @@ func NewMerger() (*Merger, error) { historyLimit: 500, staleTimeout: 15 * time.Second, // Aircraft timeout - reasonable for ADS-B tracking updateMetrics: make(map[uint32]*updateMetric), + validationLog: make(map[uint32]time.Time), }, nil } @@ -453,10 +455,14 @@ func (m *Merger) mergeAircraftData(state *AircraftState, new *modes.Aircraft, so validation := m.validatePosition(new, state, timestamp) if !validation.Valid { - // Log validation errors and skip position update - icaoHex := fmt.Sprintf("%06X", new.ICAO24) - for _, err := range validation.Errors { - log.Printf("[POSITION_VALIDATION] ICAO %s: REJECTED position update - %s", icaoHex, err) + // Rate-limited logging: only log once every 10 seconds per aircraft + if lastLog, exists := m.validationLog[new.ICAO24]; !exists || timestamp.Sub(lastLog) > 10*time.Second { + icaoHex := fmt.Sprintf("%06X", new.ICAO24) + // Only log first error to reduce spam + if len(validation.Errors) > 0 { + log.Printf("[POSITION_VALIDATION] ICAO %s: REJECTED - %s", icaoHex, validation.Errors[0]) + m.validationLog[new.ICAO24] = timestamp + } } } else { // Position is valid, proceed with normal logic @@ -483,10 +489,15 @@ func (m *Merger) mergeAircraftData(state *AircraftState, new *modes.Aircraft, so } } - // Log warnings even if position is valid - for _, warning := range validation.Warnings { - icaoHex := fmt.Sprintf("%06X", new.ICAO24) - log.Printf("[POSITION_VALIDATION] ICAO %s: WARNING - %s", icaoHex, warning) + // Rate-limited warning logging + if len(validation.Warnings) > 0 { + // Only log warnings once every 30 seconds per aircraft + warningKey := new.ICAO24 + 0x10000000 // Offset to differentiate from error logging + if lastLog, exists := m.validationLog[warningKey]; !exists || timestamp.Sub(lastLog) > 30*time.Second { + icaoHex := fmt.Sprintf("%06X", new.ICAO24) + log.Printf("[POSITION_VALIDATION] ICAO %s: WARNING - %s", icaoHex, validation.Warnings[0]) + m.validationLog[warningKey] = timestamp + } } } @@ -611,19 +622,8 @@ func (m *Merger) updateHistories(state *AircraftState, aircraft *modes.Aircraft, Longitude: aircraft.Longitude, Source: sourceID, }) - } else { - // Log validation errors for debugging - icaoHex := fmt.Sprintf("%06X", aircraft.ICAO24) - for _, err := range validation.Errors { - log.Printf("[POSITION_VALIDATION] ICAO %s: REJECTED - %s", icaoHex, err) - } - } - - // Log warnings even for valid positions - for _, warning := range validation.Warnings { - icaoHex := fmt.Sprintf("%06X", aircraft.ICAO24) - log.Printf("[POSITION_VALIDATION] ICAO %s: WARNING - %s", icaoHex, warning) } + // Note: Validation errors/warnings are already logged in mergeAircraftData } // Signal history @@ -847,6 +847,9 @@ func (m *Merger) CleanupStale() { if now.Sub(state.LastUpdate) > m.staleTimeout { delete(m.aircraft, icao) delete(m.updateMetrics, icao) + // Clean up validation log entries + delete(m.validationLog, icao) + delete(m.validationLog, icao+0x10000000) // Warning key } } }