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
This finally worked for me to query only certain levels of messages. If one replaces the code that builds predicate
with this one
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):
You can use an NSCompoundPredicate to combine the predicates: