TeraDiagnosticPolicy.swift (2879B)
1 import Foundation 2 import RadrootsKit 3 4 /// An app-owned closed schema, applied before all sinks and again at the 5 /// standalone buffer boundary. Pattern redaction alone cannot admit content. 6 enum TeraDiagnosticPolicy { 7 static let eventNames: Set<String> = [ 8 "ios.lifecycle.start_requested", "ios.lifecycle.resume_coalesced", 9 "ios.lifecycle.active", "ios.lifecycle.background", 10 "ios.lifecycle.protected_data_available", "ios.lifecycle.protected_data_unavailable", 11 "ios.lifecycle.operation_failed", "ios.lifecycle.operation_completed", 12 "ios.lifecycle.shutdown_requested", "ios.diagnostics.redacted", 13 ] 14 static let phases: Set<String> = [ 15 "starting", "identity_required", "identity_locked", "protected_data_unavailable", 16 "recovery_required", "corrupt_identity", "configuration_reconfiguration_required", 17 "running", "stopped", "failed", 18 ] 19 private static let operations: Set<String> = [ 20 "start", "stop", "identity_create", "identity_import", "identity_lock", "identity_unlock", 21 "identity_recover", "configuration_reconfigure", "settings_reconfigure", "identity_key_removal", 22 ] 23 24 static func sanitized(_ event: RadrootsTelemetryEvent) -> RadrootsTelemetryEvent? { 25 let known = eventNames.contains(event.name) 26 let name = known ? event.name : "ios.diagnostics.redacted" 27 let fields = known ? event.fields.prefix(8).compactMap(sanitizedField) : [] 28 return try? RadrootsTelemetryEvent( 29 name: name, category: "ios_lifecycle", level: event.level, 30 fields: fields, occurredAt: event.occurredAt 31 ) 32 } 33 34 private static func sanitizedField(_ field: RadrootsTelemetryField) -> RadrootsTelemetryField? { 35 if case let .string(value) = field.value { 36 switch field.key { 37 case "operation" where operations.contains(value): return field 38 case "phase" where phases.contains(value): return field 39 case "code" where value == "bootstrap_failed": return field 40 default: return nil 41 } 42 } 43 if case let .integer(value) = field.value, 44 ["count", "attempts", "schema", "latency_ms"].contains(field.key), 45 (0 ... 1_000_000).contains(value) 46 { 47 return field 48 } 49 return nil 50 } 51 52 static func code(_ value: String, allowed: Set<String>) -> String { 53 allowed.contains(value) ? value : "unavailable" 54 } 55 56 static func version(_ value: String) -> String { 57 code(value, allowed: ["0.1.0-alpha"]) 58 } 59 60 static func build(_ value: String) -> String { 61 guard !value.isEmpty, value.utf8.count <= 10, 62 value.utf8.allSatisfy({ (48 ... 57).contains($0) }) 63 else { return "unavailable" } 64 return value 65 } 66 } 67 68 struct TeraSanitizedTelemetry: RadrootsTelemetry { 69 let sink: any RadrootsTelemetry 70 71 func record(_ event: RadrootsTelemetryEvent) async { 72 guard let event = TeraDiagnosticPolicy.sanitized(event) else { return } 73 await sink.record(event) 74 } 75 }