152 lines
5.2 KiB
Swift
152 lines
5.2 KiB
Swift
//
|
|
// Log.swift
|
|
// Earthquake Network
|
|
//
|
|
// Created by Andrea Busi on 27/02/25.
|
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import OSLog
|
|
|
|
|
|
/// Use this protocol to have a base TAG in a Swift class
|
|
public protocol Loggable {
|
|
static var TAG: String { get }
|
|
}
|
|
|
|
public extension Loggable {
|
|
static var TAG: String {
|
|
String(describing: Self.self)
|
|
}
|
|
}
|
|
|
|
extension UIViewController: Loggable { }
|
|
|
|
|
|
public class Log {
|
|
|
|
private static let dumpDateFormatter: DateFormatter = {
|
|
// create the default date formatter using ISO8601 date format
|
|
let formatter = DateFormatter()
|
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
|
|
return formatter
|
|
}()
|
|
|
|
private static let shared = Log()
|
|
|
|
// MARK: - Properties
|
|
|
|
private let maxNumberOfLogsInDump: Int
|
|
private let logsLifespanMillis: Int
|
|
/// Subsystem for OSLog
|
|
private let subsystem: String
|
|
/// Logging in everything in a single "APP" category
|
|
private let appCategory: String = "APP"
|
|
|
|
private lazy var logger: os.Logger = {
|
|
os.Logger(subsystem: subsystem, category: appCategory)
|
|
}()
|
|
|
|
// MARK: - Init
|
|
|
|
@objc
|
|
public init(
|
|
subsystem: String = Bundle.main.bundleIdentifier!,
|
|
maxNumberOfLogsInDump: Int = 5000,
|
|
logsLifespanMillis: Int = 3 * 24 * 3600 * 1000
|
|
) {
|
|
self.subsystem = subsystem
|
|
self.maxNumberOfLogsInDump = maxNumberOfLogsInDump
|
|
self.logsLifespanMillis = logsLifespanMillis
|
|
}
|
|
|
|
// MARK: - Internal
|
|
|
|
public static func error(tag: String?, _ message: String?, _ functionName: String = #function) {
|
|
shared.log(level: .fault, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
|
}
|
|
|
|
public static func warning(tag: String?, _ message: String?, _ functionName: String = #function) {
|
|
shared.log(level: .error, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
|
}
|
|
|
|
public static func info(tag: String?, _ message: String?, _ functionName: String = #function) {
|
|
shared.log(level: .info, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
|
}
|
|
|
|
public static func debug(tag: String?, _ message: String?, _ functionName: String = #function) {
|
|
shared.log(level: .debug, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
|
}
|
|
|
|
public static func verbose(tag: String?, _ message: String?, _ functionName: String = #function) {
|
|
shared.log(level: .debug, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
|
}
|
|
|
|
@available(iOS 15.0, *)
|
|
public func dumpLog() async -> String {
|
|
return (try? await getLogEntries()) ?? ""
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func log(level: OSLogType, tag: String, message: String, functionName: String) {
|
|
let formattedMessage = "[\(tag)] \(functionName): \(message)"
|
|
switch level {
|
|
case .fault: logger.fault("\(formattedMessage, privacy: .public)")
|
|
case .error: logger.error("\(formattedMessage, privacy: .public)")
|
|
case .default: logger.notice("\(formattedMessage, privacy: .public)")
|
|
case .info: logger.info("\(formattedMessage, privacy: .public)")
|
|
default: logger.debug("\(formattedMessage, privacy: .public)")
|
|
}
|
|
}
|
|
|
|
/// Retrieve log entries from a specified time.
|
|
/// - Returns: String of log entries, newlines separated
|
|
@available(iOS 15.0, *)
|
|
private func getLogEntries() async throws -> String {
|
|
let logTask = Task.init(priority: .utility) { () -> String in
|
|
let logs = try retrieveLogEntries()
|
|
let text = logs
|
|
.compactMap { "\(Self.dumpDateFormatter.string(from: $0.date)) [\($0.level)] \($0.composedMessage)" }
|
|
.joined(separator: "\n")
|
|
return text
|
|
}
|
|
return try await logTask.value
|
|
}
|
|
|
|
@available(iOS 15.0, *)
|
|
private func retrieveLogEntries() throws -> [OSLogEntryLog] {
|
|
// Open the log store.
|
|
let logStore = try OSLogStore(scope: .currentProcessIdentifier)
|
|
|
|
// Fetch log objects from the given time interval
|
|
let intervalPosition = logStore.position(date: Date().addingTimeInterval(TimeInterval(-logsLifespanMillis / 1000)))
|
|
let allEntries = try logStore.getEntries(at: intervalPosition)
|
|
|
|
// Filter the log to be relevant for our specific subsystem
|
|
// and remove other elements (signposts, etc).
|
|
return allEntries
|
|
.compactMap { $0 as? OSLogEntryLog }
|
|
.filter { $0.subsystem == subsystem }
|
|
.suffix(maxNumberOfLogsInDump)
|
|
}
|
|
}
|
|
|
|
@available(iOS 15.0, *)
|
|
extension OSLogEntryLog.Level: @retroactive CustomStringConvertible {
|
|
public var description: String {
|
|
switch self {
|
|
case .fault: return "FAULT"
|
|
case .error: return "ERROR"
|
|
case .notice: return "WARNING"
|
|
case .info: return "INFO"
|
|
case .debug: return "DEBUG"
|
|
case .undefined: return "UNDEFINED"
|
|
@unknown default:
|
|
return "UNKNOWN"
|
|
}
|
|
}
|
|
}
|