Integrating with OSLog #198
Replies: 2 comments 5 replies
-
SwiftLog supports OSLog as one of its logging backends. If you are already using SwiftLog, it should be easy to add OSLog to the list of backends. In case someone who is not using SwiftLog yet stumbles upon this thread, there is an alternative way to implement this without too much code change using |
Beta Was this translation helpful? Give feedback.
-
@kean The good news is, I believe if this is something you'd rather keep external to Pulse that should be achievable with a single API addition! The bad news is that there are some limitations If you want Now, the limitations:
import Foundation
import OSLog
@available(iOS 15.0, macOS 10.15, tvOS 15.0, watchOS 8.0, *)
class OSLogStoreSynchronizer {
let osLogStore: OSLogStore
let pulseStore: LoggerStore
var subsystemAllowList: [String] = [Bundle.main.bundleIdentifier].compactMap({ $0 })
var mapLogLevel: (OSLogEntryLog.Level) -> LoggerStore.Level
var onRead: ((Error?) async -> Void)? = nil
var timer: DispatchSourceTimer?
var lastLogStoreError: Error?
var predicate: NSPredicate?
var lastPosition: OSLogPosition?
let queue = DispatchQueue(label: "com.github.kean.pulse.OSLogStoreSynchronizer")
init(
pulseStore: LoggerStore,
subsystemAllowList: [String] = [Bundle.main.bundleIdentifier].compactMap({ $0 }),
mapLogLevel: @escaping (OSLogEntryLog.Level) -> LoggerStore.Level = defaultOSLogLevelMapping,
onRead: ((Error?) async -> Void)? = nil
) throws {
self.osLogStore = try OSLogStore(scope: .currentProcessIdentifier)
self.pulseStore = pulseStore
self.mapLogLevel = mapLogLevel
self.subsystemAllowList = subsystemAllowList
self.onRead = onRead
}
deinit {
timer?.cancel()
}
func readOSLogEntries(into pulseStore: LoggerStore) throws {
var lastOSLogEntry: OSLogEntryLog?
let entries = try osLogStore.getEntries(with: [], at: lastPosition, matching: predicate)
for entry in entries {
if let payload = entry as? OSLogEntryWithPayload,
!subsystemAllowList.contains(payload.subsystem) && !subsystemAllowList.isEmpty {
continue
}
switch entry {
case let entry as OSLogEntryLog:
pulseStore.storeMessage(
createdAt: entry.date,
label: entry.category,
level: mapLogLevel(entry.level),
message: entry.composedMessage,
metadata: ["thread_id": .stringConvertible(entry.threadIdentifier)],
file: "<unknown>",
function: "<unknown>",
line: 0
)
lastOSLogEntry = entry
default:
continue
}
}
if let lastOSLogEntry {
/// NOTE: lastPosition doesn't seem to filter the entries retrieved, we provide it anyway, but rely upon `predicate`
self.predicate = NSPredicate(format: "date > %@", lastOSLogEntry.date as NSDate)
self.lastPosition = osLogStore.position(date: lastOSLogEntry.date)
}
print("Did read")
}
func startSyncing(every interval: TimeInterval = 30) {
let timer = DispatchSource.makeTimerSource(queue: queue)
self.timer = timer
timer.schedule(wallDeadline: .now(), repeating: interval)
timer.setEventHandler { [weak self] in
guard let self else { return }
do {
try readOSLogEntries(into: pulseStore)
if let onRead {
Task {
await onRead(nil)
}
}
} catch {
lastLogStoreError = error
if let onRead {
Task {
await onRead(error)
}
}
}
}
timer.resume()
}
func stop() {
timer?.cancel()
}
static func defaultOSLogLevelMapping(_ level: OSLogEntryLog.Level) -> LoggerStore.Level {
switch level {
case .undefined: return .trace
case .debug: return .debug
case .info: return .info
case .notice: return .notice
case .error: return .error
case .fault: return .critical
@unknown default: return .info
}
}
} |
Beta Was this translation helpful? Give feedback.
-
I’m currently using SwiftLog in my iOS app mainly because of how easy it is to connect to Pulse, however with the new Xcode 15 structured logging support in the console I’m wondering if should switch to use OSLog instead.
Is there an easy way of using OSLog as my main logging API but still connecting this up to Pulse?
Beta Was this translation helpful? Give feedback.
All reactions