Commit 7b880ea410
Unsigned
Layout: unified · split
DomainDig/DomainMonitoringService.swift +41 −2
| @@ -291,6 +291,12 @@ final class DomainMonitoringService { | |||
| 291 | previousSnapshot: previousSnapshot | 291 | previousSnapshot: previousSnapshot |
| 292 | ) | 292 | ) |
| 293 | let snapshot = Self.resolvedSnapshotAfterFallback(inspectedSnapshot, previousSnapshot: previousSnapshot) | 293 | let snapshot = Self.resolvedSnapshotAfterFallback(inspectedSnapshot, previousSnapshot: previousSnapshot) |
| 294 | // When the fallback fires, `snapshot` *is* `previousSnapshot`, so | ||
| 295 | // every comparison below is old-against-old and would otherwise | ||
| 296 | // report "No meaningful changes" for a domain we never reached. | ||
| 297 | let unreachableReason = (previousSnapshot != nil && Self.shouldFallbackToSnapshot(inspectedSnapshot)) | ||
| 298 | ? Self.firstErrorMessage(in: inspectedSnapshot) | ||
| 299 | : nil | ||
| 294 | let savedEntry: HistoryEntry? | 300 | let savedEntry: HistoryEntry? |
| 295 | if snapshot.statusMessage == nil { | 301 | if snapshot.statusMessage == nil { |
| 296 | savedEntry = persistSnapshot( | 302 | savedEntry = persistSnapshot( |
| @@ -330,14 +336,16 @@ final class DomainMonitoringService { | |||
| 330 | historyEntryID: savedEntry?.id ?? snapshot.historyEntryID, | 336 | historyEntryID: savedEntry?.id ?? snapshot.historyEntryID, |
| 331 | checkedAt: now, | 337 | checkedAt: now, |
| 332 | didChange: alertDescriptor != nil, | 338 | didChange: alertDescriptor != nil, |
| 333 | summaryMessage: snapshot.statusMessage | 339 | summaryMessage: unreachableReason.map { "Could not check — kept the previous result. \($0)" } |
| 340 | ?? snapshot.statusMessage | ||
| 334 | ?? alertDescriptor?.message | 341 | ?? alertDescriptor?.message |
| 335 | ?? savedEntry?.changeSummary?.message | 342 | ?? savedEntry?.changeSummary?.message |
| 336 | ?? "No meaningful changes", | 343 | ?? "No meaningful changes", |
| 337 | alertSeverity: alertDescriptor?.severity, | 344 | alertSeverity: alertDescriptor?.severity, |
| 338 | certificateWarningLevel: DomainDiffService.certificateWarningLevel(for: snapshot), | 345 | certificateWarningLevel: DomainDiffService.certificateWarningLevel(for: snapshot), |
| 339 | resultSource: snapshot.resultSource, | 346 | resultSource: snapshot.resultSource, |
| 340 | errorMessage: snapshot.statusMessage | 347 | errorMessage: snapshot.statusMessage, |
| 348 | unreachableReason: unreachableReason | ||
| 341 | ) | 349 | ) |
| 342 | results.append(result) | 350 | results.append(result) |
| 343 | 351 | ||
| @@ -397,6 +405,21 @@ final class DomainMonitoringService { | |||
| 397 | ) | 405 | ) |
| 398 | } | 406 | } |
| 399 | 407 | ||
| 408 | if let unreachableReason = result.unreachableReason { | ||
| 409 | return MonitoringEvent( | ||
| 410 | type: .monitoringFailure, | ||
| 411 | severity: .warning, | ||
| 412 | domain: result.domain, | ||
| 413 | timestamp: result.checkedAt, | ||
| 414 | summary: result.summaryMessage, | ||
| 415 | details: [ | ||
| 416 | "reason": unreachableReason, | ||
| 417 | "trigger": log.trigger.rawValue, | ||
| 418 | "resultSource": result.resultSource.rawValue | ||
| 419 | ] | ||
| 420 | ) | ||
| 421 | } | ||
| 422 | |||
| 400 | if result.certificateWarningLevel == .critical { | 423 | if result.certificateWarningLevel == .critical { |
| 401 | return MonitoringEvent( | 424 | return MonitoringEvent( |
| 402 | type: .certificateExpiring, | 425 | type: .certificateExpiring, |
| @@ -943,6 +966,22 @@ final class DomainMonitoringService { | |||
| 943 | ) | 966 | ) |
| 944 | } | 967 | } |
| 945 | 968 | ||
| 969 | /// First reported error on a snapshot, used to explain a fallback. Mirrors | ||
| 970 | /// the ordering `shouldFallbackToSnapshot` inspects. | ||
| 971 | private static func firstErrorMessage(in snapshot: LookupSnapshot) -> String { | ||
| 972 | [ | ||
| 973 | snapshot.dnsError, | ||
| 974 | snapshot.httpHeadersError, | ||
| 975 | snapshot.sslError, | ||
| 976 | snapshot.ownershipError, | ||
| 977 | snapshot.subdomainsError, | ||
| 978 | snapshot.redirectChainError, | ||
| 979 | snapshot.ipGeolocationError | ||
| 980 | ] | ||
| 981 | .compactMap { $0 } | ||
| 982 | .first ?? "The lookup could not reach the domain." | ||
| 983 | } | ||
| 984 | |||
| 946 | private static func shouldFallbackToSnapshot(_ snapshot: LookupSnapshot) -> Bool { | 985 | private static func shouldFallbackToSnapshot(_ snapshot: LookupSnapshot) -> Bool { |
| 947 | let candidateMessages = [ | 986 | let candidateMessages = [ |
| 948 | snapshot.dnsError, | 987 | snapshot.dnsError, |
DomainDig/Models.swift +7 −1
| @@ -1544,6 +1544,10 @@ struct MonitoringDomainResult: Codable, Identifiable, Equatable { | |||
| 1544 | let certificateWarningLevel: CertificateWarningLevel | 1544 | let certificateWarningLevel: CertificateWarningLevel |
| 1545 | let resultSource: LookupResultSource | 1545 | let resultSource: LookupResultSource |
| 1546 | let errorMessage: String? | 1546 | let errorMessage: String? |
| 1547 | /// Non-nil when the lookup failed and the previous snapshot was reused, so | ||
| 1548 | /// this run compared the old data against itself and cannot claim the | ||
| 1549 | /// domain is unchanged. Optional so already-persisted logs still decode. | ||
| 1550 | let unreachableReason: String? | ||
| 1547 | 1551 | ||
| 1548 | init( | 1552 | init( |
| 1549 | id: UUID = UUID(), | 1553 | id: UUID = UUID(), |
| @@ -1555,7 +1559,8 @@ struct MonitoringDomainResult: Codable, Identifiable, Equatable { | |||
| 1555 | alertSeverity: MonitoringAlertSeverity?, | 1559 | alertSeverity: MonitoringAlertSeverity?, |
| 1556 | certificateWarningLevel: CertificateWarningLevel, | 1560 | certificateWarningLevel: CertificateWarningLevel, |
| 1557 | resultSource: LookupResultSource, | 1561 | resultSource: LookupResultSource, |
| 1558 | errorMessage: String? = nil | 1562 | errorMessage: String? = nil, |
| 1563 | unreachableReason: String? = nil | ||
| 1559 | ) { | 1564 | ) { |
| 1560 | self.id = id | 1565 | self.id = id |
| 1561 | self.domain = domain | 1566 | self.domain = domain |
| @@ -1567,6 +1572,7 @@ struct MonitoringDomainResult: Codable, Identifiable, Equatable { | |||
| 1567 | self.certificateWarningLevel = certificateWarningLevel | 1572 | self.certificateWarningLevel = certificateWarningLevel |
| 1568 | self.resultSource = resultSource | 1573 | self.resultSource = resultSource |
| 1569 | self.errorMessage = errorMessage | 1574 | self.errorMessage = errorMessage |
| 1575 | self.unreachableReason = unreachableReason | ||
| 1570 | } | 1576 | } |
| 1571 | } | 1577 | } |
| 1572 | 1578 | ||