skip to Main Content

For some fields other than messageType "IN" condition works fine.
Also .reverse option does not seem to make any difference at least on iOS.
Here is quick sample to check:

public extension Logger {
    static let appSubsystem = Bundle.main.bundleIdentifier!
    static func fetch(since date: Date) async throws -> [String] {
        let store = try OSLogStore(scope: .currentProcessIdentifier)
        let position = store.position(date: date)
        
        // This does NOT work.
        // let predicate = NSPredicate(format: "(subsystem == %@) && (messageType IN %@)", Logger.appSubsystem, ["error", "fault"])
        // This, however, works.
        let predicate = NSPredicate(format: "(subsystem IN %@) && (messageType == %@)", [Logger.appSubsystem], "error")
        
        let entries = try store.getEntries(/* with: [.reverse], */ at: position,
                                           matching: predicate)
        var logs: [String] = []
        for entry in entries {
            try Task.checkCancellation()
            if let log = entry as? OSLogEntryLog {
                logs.append("""
          (entry.date):(log.subsystem):
          (log.category):(log.level): 
          (entry.composedMessage)n
          """)
            } else {
                logs.append("(entry.date): (entry.composedMessage)n")
            }
        }
        
        if logs.isEmpty { logs = ["Nothing found"] }
        return logs
    }
}

// This extension  is for sample purposes only
extension OSLogEntryLog.Level: @retroactive CustomStringConvertible {
    public var description: String {
        switch self {
        case .undefined: "undefined"
        case .debug: "debug"
        case .info: "info"
        case .notice: "notice"
        case .error: "error"
        case .fault: "fault"
        @unknown default: "default"
        }
    }
}

extension OSLogEntryLog.Level: @retroactive Identifiable {
    public var id: Int {
        rawValue
    }
}

Here is small SwiftUI screen to assist in reproducing the issue

import SwiftUI
import OSLog

struct LogView: View {
    @State private var text = "Loading..."
    @State private var task: Task<(), Error>?
    @State private var isLoading = false
    @State private var selectedLogLevel: OSLogEntryLog.Level = .undefined
    private let logLevels: [OSLogEntryLog.Level] = [.undefined, .debug, .info, .notice, .error, .fault]
    let logger = Logger(subsystem: Logger.appSubsystem, category: "main")
    
    var body: some View {
        VStack {
            HStack {
                Button(isLoading ? "Cancel" : "Refresh") {
                    if isLoading {
                        task?.cancel()
                    } else {
                        task = Task {
                            text = await fetchLogs()
                            isLoading = false
                        }
                    }
                }
                ProgressView()
                    .opacity(isLoading ? 1 : 0)
                Picker(selection: $selectedLogLevel, label: Text("Choose Log Level")) {
                    ForEach(logLevels) { logLevel in
                        Text(logLevel.description)
                            .tag(logLevel)
                    }
                }
                Button("Log") {
                    logMessage(selectedLogLevel: selectedLogLevel)
                }
            }
            
            ScrollView {
                Text(text)
                    .textSelection(.enabled)
                    .fontDesign(.monospaced)
                    .padding()
            }
        }
        .onAppear {
            isLoading = true
            task = Task {
                text = await fetchLogs()
                isLoading = false
            }
        }
    }
}

private extension LogView {
    @MainActor
    func fetchLogs() async -> String {
        let calendar = Calendar.current
        guard let hourAgo = calendar.date(byAdding: .hour,
                                          value: -1, to: Date.now) else {
            return "Invalid calendar"
        }
        
        do {
            let logs = try await Logger.fetch(since: hourAgo)
            return logs.joined()
        } catch {
            return error.localizedDescription
        }
    }
    
    func logMessage(selectedLogLevel: OSLogEntryLog.Level) {
        switch(selectedLogLevel) {
        case .undefined:
            logger.log("Default log message")
        case .debug:
            logger.debug("Debug log message")
        case .info:
            logger.info("Info log message")
            //                case .warning:
            //                    logger.warning("Warning log message")
        case .notice:
            logger.notice("Notice log message")
        case .error:
            logger.error("Error log message")
        case .fault:
            logger.fault("Fault log message")
            
        @unknown default:
            logger.log("Default log message")
        }
    }
}

2

Answers


  1. Chosen as BEST ANSWER

    This finally worked for me to query only certain levels of messages. If one replaces the code that builds predicate

    let predicate = NSPredicate(format:...
    

    with this one

                let subsystemPredicate = NSPredicate(format: "subsystem == %@", appSubsystem)            
                var messageTypePredicate = NSCompoundPredicate(orPredicateWithSubpredicates:logLevels.map{ NSPredicate(format: "messageType == %@", $0.description)})
                messageTypePredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [messageTypePredicate, NSPredicate(format: "messageType == nil")])
                
                let predicate = NSCompoundPredicate(andPredicateWithSubpredicates:  [messageTypePredicate, subsystemPredicate])
    

    the provided minimal reproducible sample should work as expected.

    The other way to make in work is to explicitly build a string fro a predicate that would "OR" enumerate all the messageTypes, like this (but its ugly):

     let predicate = NSPredicate(format: "(subsystem == %@) && ((messageType == error) OR (messageType == fault))")
    

  2. You can use an NSCompoundPredicate to combine the predicates:

    let subsystemPredicate = NSPredicate(format: "subsystem IN %@", [Logger.appSubsystem])
    let messageTypePredicate = NSPredicate(format: "messageType == %@", "error")
    
    let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
        subsystemPredicate, messageTypePredicate
    ])
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search