summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--DomainDig/DomainMonitoringService.swift43
-rw-r--r--DomainDig/Models.swift8
2 files changed, 48 insertions, 3 deletions
diff --git a/DomainDig/DomainMonitoringService.swift b/DomainDig/DomainMonitoringService.swift
index 6fb5fc9..917ede0 100644
--- a/DomainDig/DomainMonitoringService.swift
+++ b/DomainDig/DomainMonitoringService.swift
@@ -291,6 +291,12 @@ final class DomainMonitoringService {
previousSnapshot: previousSnapshot
)
let snapshot = Self.resolvedSnapshotAfterFallback(inspectedSnapshot, previousSnapshot: previousSnapshot)
+ // When the fallback fires, `snapshot` *is* `previousSnapshot`, so
+ // every comparison below is old-against-old and would otherwise
+ // report "No meaningful changes" for a domain we never reached.
+ let unreachableReason = (previousSnapshot != nil && Self.shouldFallbackToSnapshot(inspectedSnapshot))
+ ? Self.firstErrorMessage(in: inspectedSnapshot)
+ : nil
let savedEntry: HistoryEntry?
if snapshot.statusMessage == nil {
savedEntry = persistSnapshot(
@@ -330,14 +336,16 @@ final class DomainMonitoringService {
historyEntryID: savedEntry?.id ?? snapshot.historyEntryID,
checkedAt: now,
didChange: alertDescriptor != nil,
- summaryMessage: snapshot.statusMessage
+ summaryMessage: unreachableReason.map { "Could not check — kept the previous result. \($0)" }
+ ?? snapshot.statusMessage
?? alertDescriptor?.message
?? savedEntry?.changeSummary?.message
?? "No meaningful changes",
alertSeverity: alertDescriptor?.severity,
certificateWarningLevel: DomainDiffService.certificateWarningLevel(for: snapshot),
resultSource: snapshot.resultSource,
- errorMessage: snapshot.statusMessage
+ errorMessage: snapshot.statusMessage,
+ unreachableReason: unreachableReason
)
results.append(result)
@@ -397,6 +405,21 @@ final class DomainMonitoringService {
)
}
+ if let unreachableReason = result.unreachableReason {
+ return MonitoringEvent(
+ type: .monitoringFailure,
+ severity: .warning,
+ domain: result.domain,
+ timestamp: result.checkedAt,
+ summary: result.summaryMessage,
+ details: [
+ "reason": unreachableReason,
+ "trigger": log.trigger.rawValue,
+ "resultSource": result.resultSource.rawValue
+ ]
+ )
+ }
+
if result.certificateWarningLevel == .critical {
return MonitoringEvent(
type: .certificateExpiring,
@@ -943,6 +966,22 @@ final class DomainMonitoringService {
)
}
+ /// First reported error on a snapshot, used to explain a fallback. Mirrors
+ /// the ordering `shouldFallbackToSnapshot` inspects.
+ private static func firstErrorMessage(in snapshot: LookupSnapshot) -> String {
+ [
+ snapshot.dnsError,
+ snapshot.httpHeadersError,
+ snapshot.sslError,
+ snapshot.ownershipError,
+ snapshot.subdomainsError,
+ snapshot.redirectChainError,
+ snapshot.ipGeolocationError
+ ]
+ .compactMap { $0 }
+ .first ?? "The lookup could not reach the domain."
+ }
+
private static func shouldFallbackToSnapshot(_ snapshot: LookupSnapshot) -> Bool {
let candidateMessages = [
snapshot.dnsError,
diff --git a/DomainDig/Models.swift b/DomainDig/Models.swift
index b86fc07..6989cba 100644
--- a/DomainDig/Models.swift
+++ b/DomainDig/Models.swift
@@ -1544,6 +1544,10 @@ struct MonitoringDomainResult: Codable, Identifiable, Equatable {
let certificateWarningLevel: CertificateWarningLevel
let resultSource: LookupResultSource
let errorMessage: String?
+ /// Non-nil when the lookup failed and the previous snapshot was reused, so
+ /// this run compared the old data against itself and cannot claim the
+ /// domain is unchanged. Optional so already-persisted logs still decode.
+ let unreachableReason: String?
init(
id: UUID = UUID(),
@@ -1555,7 +1559,8 @@ struct MonitoringDomainResult: Codable, Identifiable, Equatable {
alertSeverity: MonitoringAlertSeverity?,
certificateWarningLevel: CertificateWarningLevel,
resultSource: LookupResultSource,
- errorMessage: String? = nil
+ errorMessage: String? = nil,
+ unreachableReason: String? = nil
) {
self.id = id
self.domain = domain
@@ -1567,6 +1572,7 @@ struct MonitoringDomainResult: Codable, Identifiable, Equatable {
self.certificateWarningLevel = certificateWarningLevel
self.resultSource = resultSource
self.errorMessage = errorMessage
+ self.unreachableReason = unreachableReason
}
}