Can I use OSLogStore to access logs from earlier runs of my app?

I understand that in iOS 15 and macOS Monterey, I can easily access the log system to get log entries for my app like this:

let store = try OSLogStore(scope: .currentProcessIdentifier)

I can then query store for the log entries.

However, as it says right in the name, the store is only for current process, i.e current run of my app. When I try to get entries, I only get entries from the current run, even if I specify an earlier time like this:

guard let positionDate = Calendar.current.date(byAdding: .day, value: -7, to: Date()) else { return }
let position = store.position(date: positionDate)
let predicate = NSPredicate(format: "subsystem == 'com.exampl.emyAppSubsystem'")
let entries = try store.getEntries(with: [], at: position, matching: predicate)
for entry in entries {
   print("Entry: \(entry.date) \(entry.composedMessage)")
}

Is there any way to access log entries for earlier runs of my app, e.g from days or weeks ago? Either on macOS or iOS?

Replies

Is there any way to access log entries for earlier runs of my app, e.g from days or weeks ago? Either on macOS or iOS?

On macOS you can do this by opening the .system scope — or, on systems prior to macOS 12 beta, by calling OSLogStore.local() — and then running a query for your process name. IIRC this only works if you’re running as an admin user.

I don’t think there’s a solution for iOS but that would, IMO, make a good enhancement request.

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Out of interest did you ever raise an enhancement request? Our app too would benefit from being able to retrieve it's log entries from previous executions...

I encourage you to file your own ER for this. Even if Jaanus did file one, a few dups won’t hurt (-:

Please post your bug number, just for the record

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@esikmo please push this ticket: FB11505498

  • Thanks for filing FB11505498.

Add a Comment

Due to I can't find FB11505498, and I have same request for this, so I submitted a new request: https://feedbackassistant.apple.com/feedback/11896528 @esikme could you please to push it?

@esikmo Do you know when the app starts logging the logs from scratch, i.e. when the previous logs are reset? For example, if the app is moved to the background and later is reopened (not suspended in-between), are the logs available from the first launch (along with logs after the second launch in OSLogStore(scope: .currentProcessIdentifier)?

I think to implement a function that copies all the logs from OSLogStore when the app is closed (export to a file). It will allow to write a function to export logs from previous launches.

I put that exporting in applicationWillTerminate, but it's never called. I tested it by killing the app, but the files not appear meaning applicationWillTerminate is not called. Putting the function into applicationWillResignActive or applicationDidEnterBackground might result in having a few files that overlay in time.

Can you suggest how to implement this idea properly?

    func applicationWillTerminate(_ application: UIApplication) {
        // Export logs when the app is suspended
        if #available(iOS 15.0, *) {
            FileManager.exportLogsSinceLastLaunch()
        }
    }
...
// global vars
private var lastBgTaskIdLock = NSLock()
private var lastBgTaskId: UIBackgroundTaskIdentifier?

extension FileManager {
    public static func exportLogsSinceLastLaunch() {
        let logger = os.Logger(subsystem: Bundle.main.appId, category: "FileManager")
        logger.info("exportLogsSinceLastLaunch")
        let fileCoordinator = FileCoordinator()

        /// Register a task
        var cancel: (() -> Void)?
        let bgTask = UIApplication.shared.beginBackgroundTask(withName: "\(#function)") {
            cancel?()
        }
        let lock = lastBgTaskIdLock
        var bgTaskToCancel: UIBackgroundTaskIdentifier?
        do {
            lock.lock(); defer { lock.unlock() }
            if let id = lastBgTaskId, id != UIBackgroundTaskIdentifier.invalid {
                bgTaskToCancel = id
            }
            lastBgTaskId = bgTask
        }
        if let id = bgTaskToCancel {
            UIApplication.shared.endBackgroundTask(id)
        }
        /// Callback to end the task properly
        let endTaskCallback: () -> Void = {
            var needToEndTask = false
            do {
                lock.lock(); defer { lock.unlock() }
                if lastBgTaskId == bgTask {
                    needToEndTask = true
                    lastBgTaskId = UIBackgroundTaskIdentifier.invalid
                }
            }
            if needToEndTask {
                DispatchQueue.main.async {
                    UIApplication.shared.endBackgroundTask(bgTask)
                }
            }
        }

        let task = Task.detached { [fileCoordinator] in
            do {
                // Export log into temporary file
                guard let tempUrl = try fileCoordinator.storeAndGetCurrentLogTemporaryUrl() else { return }

                // Copy file to a persistent storage
                try Task.checkCancellation()
                let filename = tempUrl.lastPathComponent
                let url = try FileUtil(contentDirectoryName: "logs").mkdir().getLocalFileURL(filename)
                try FileManager.default.copyItem(at: tempUrl, to: url)

                // Complete the task
                try Task.checkCancellation()
            }
            catch {
                logger.error(error: error)
            }
            /// End the task
            endTaskCallback()
        }
        cancel = {
            task.cancel()
        }
    }
}

The meaning of .currentProcessIdentifier is exactly that: It’s tied to your process ID. If your app is suspended and resumed, it’ll have the same process ID. If the app is suspended, then terminated, and then relaunched, it’ll have a different process ID.

I put that exporting in applicationWillTerminate(_:), but it's never called.

This can be called but not often. When the user moves an iOS app to the background, it is first suspended. Typically you’ll then see one of two behaviours:

  • If the user brings it back to the front, the system will resume it.

  • If iOS decides it needs the memory, it’ll terminate the app. It won’t called applicationWillTerminate(_:) in this case because, if the system is short on memory, the last thing it should be doing is allocating memory to an app that’s about to go away.

The most common time to see applicationWillTerminate(_:) is when the user swipes up on it in the multitasking UI while the app is running.

I recommend that you review Managing your app’s life cycle. It covers this stuff in more detail.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"