This module provides a compatibility API corresponding to Apple's OSLogStore API, allowing app developers to read the OSLog messages from their own app.
On iOS, SkipOSLogStore simply re-exports OSLog. On Android, SkipOSLogStore executes logcat directly on the device, and parses the logs using protobuf-javalite.
import SkipOSLogStore
func loadEntries() -> [OSLogEntryLog] throws {
let store = try OSLogStore(scope: .currentProcessIdentifier)
return try Array(store.getEntries().compactMap { $0 as? OSLogEntryLog })
}
for entry in loadEntries() {
print(entry.date)
print(entry.level) // OSLogEntryLog.Level
print(entry.subsystem)
print(entry.category)
print(entry.composedMessage)
#if os(Android)
print(entry.androidLevel) // AndroidLogLevel
#endif
}This returns log entries from the current process. (That's the only supported way to use it, on iOS with Apple's implementation and on Android with SkipOSLogStore.)
It can be quite slow, especially on iOS, so it's best to run it off the main thread, which can be a bit tricky, because OSLogEntryLog is not declared Sendable.
import SkipOSLogStore
extension OSLogEntryLog: @retroactive @unchecked Sendable {}
extension OSLogEntryLog.Level: @retroactive @unchecked Sendable {}
func loadEntries() -> [OSLogEntryLog] async throws {
try await Task.detached(priority: .userInitiated) { @concurrent
let store = try OSLogStore(scope: .currentProcessIdentifier)
return try Array(store.getEntries().compactMap { $0 as? OSLogEntryLog })
}.value
}For your convenience, SkipOSLogStore provides:
public struct SkipOSLogStore {
public static func getEntries(
reversed: Bool = true,
after: Date? = nil,
minimumLevel: OSLogEntryLog.Level? = .notice,
inSubsystem: String? = nil,
inCategory: String? = nil
) async throws -> [SkipOSLogStoreEntry] { /* ... */ }
}
public struct SkipOSLogStoreEntry: Sendable {
private let entry: OSLogEntryLog
public var date: Date { entry.date }
public var level: OSLogEntryLog.Level { entry.level }
public var category: String { entry.category }
public var subsystem: String { entry.subsystem }
public var composedMessage: String { entry.composedMessage }
#if os(Android)
public var androidLevel: AndroidLogLevel? { entry.androidLevel }
#endif
init(entry: OSLogEntryLog) {
self.entry = entry
}
}Use it like this:
try await SkipOSLogStore.getEntries(
reversed: true,
after: Date.now.addingTimeInterval(TimeInterval(-5 * 60)),
minimumLevel: .error,
inCategory: "MyCategory"
)The OSLogStore getEntries(with:at:matching:) function is supposed to support a bunch of sorting/filtering features:
- Reverse chronological sorting
- Returning log entries after a certain date
- Filtering with an
NSPredicate
… but none of that works, either in Apple's implementation on iOS or in SkipOSLogStore. getEntries() always just returns all of the log entries for the current process; we have to sort/filter them in the client, which is what SkipOSLogStore.getEntries(reversed:after:minimumLevel:inCategory:) does for you.
This project is a Swift Package Manager module that uses the Skip plugin to build the package for both iOS and Android.
The module can be tested using the standard swift test command
or by running the test target for the macOS destination in Xcode,
which will run the Swift tests as well as the transpiled
Kotlin JUnit tests in the Robolectric Android simulation environment.
Parity testing can be performed with skip test,
which will output a table of the test results for both platforms.
This software is licensed under the Mozilla Public License 2.0.