1 // Copyright 2022-present 650 Industries. All rights reserved. 2 3 import Foundation 4 import OSLog 5 6 import ExpoModulesCore 7 8 /** 9 Class to read expo-updates logs using OSLogReader 10 */ 11 internal final class UpdatesLogReader { 12 private let serialQueue = DispatchQueue(label: "dev.expo.updates.logging.reader") 13 private let logPersistence = PersistentFileLog(category: UpdatesLogger.EXPO_UPDATES_LOG_CATEGORY) 14 15 /** 16 Get expo-updates logs newer than the given date 17 Returns the log entries unpacked as dictionaries 18 Maximum of one day lookback is allowed 19 */ 20 func getLogEntries(newerThan: Date) throws -> [[String: Any]] { 21 let epoch = epochFromDateOrOneDayAgo(date: newerThan) 22 return logPersistence.readEntries() 23 .compactMap { entryString in 24 logStringToFilteredLogEntry(entryString: entryString, epoch: epoch)?.asDict() 25 } 26 } 27 28 /** 29 Get expo-updates logs newer than the given date 30 Returned strings are all in the JSON format of UpdatesLogEntry 31 Maximum of one day lookback is allowed 32 */ 33 func getLogEntries(newerThan: Date) -> [String] { 34 let epoch = epochFromDateOrOneDayAgo(date: newerThan) 35 return logPersistence.readEntries() 36 .compactMap { entryString in 37 logStringToFilteredLogEntry(entryString: entryString, epoch: epoch)?.asString() 38 } 39 } 40 41 /** 42 Purge all log entries written more than one day ago 43 */ 44 func purgeLogEntries(completion: @escaping (Error?) -> Void) { 45 purgeLogEntries( 46 olderThan: Date().addingTimeInterval(-UpdatesLogReader.MAXIMUM_LOOKBACK_INTERVAL), 47 completion: completion 48 ) 49 } 50 51 /** 52 Purge all log entries written prior to the given date 53 */ 54 func purgeLogEntries(olderThan: Date, completion: @escaping (Error?) -> Void) { 55 let epoch = epochFromDateOrOneDayAgo(date: olderThan) 56 logPersistence.purgeEntriesNotMatchingFilter(filter: { entryString in 57 self.logStringToFilteredLogEntry(entryString: entryString, epoch: epoch) != nil 58 }, {error in 59 completion(error) 60 }) 61 } 62 63 private func logStringToFilteredLogEntry(entryString: String, epoch: UInt) -> UpdatesLogEntry? { 64 if entryString.lengthOfBytes(using: .utf8) < 2 { 65 return nil 66 } 67 let suffixFrom = entryString.index(entryString.startIndex, offsetBy: 2) 68 let entryStringSuffix = String(entryString.suffix(from: suffixFrom)) 69 let entry = UpdatesLogEntry.create(from: entryStringSuffix) 70 return entry?.timestamp ?? 0 >= epoch ? entry : nil 71 } 72 73 private static let MAXIMUM_LOOKBACK_INTERVAL: TimeInterval = 86_400 // 1 day 74 75 private func epochFromDateOrOneDayAgo(date: Date) -> UInt { 76 // Returns the epoch (milliseconds since 1/1/1970) 77 // If date is earlier than one day ago, then the epoch for one day ago is returned 78 // instead 79 let earliestDate = Date().addingTimeInterval(-UpdatesLogReader.MAXIMUM_LOOKBACK_INTERVAL) 80 let dateToUse = date.timeIntervalSince1970 < earliestDate.timeIntervalSince1970 ? 81 earliestDate : 82 date 83 return UInt(dateToUse.timeIntervalSince1970) * 1000 84 } 85 } 86