OSLogStore reads back the log entries your app wrote with Logger. Pair it with a share sheet, and users can send their logs to you from a settings screen, without a cable or the Console app.
Fetch Entries With OSLogStore
Open the store scoped to the current process, pick a start position, then fetch and filter. Filter on your own subsystem, or you also get system noise from the process.
import OSLog
@MainActor final class LogStore: ObservableObject {
@Published private(set) var entries: [String] = []
func export() {
do {
let store = try OSLogStore(scope: .currentProcessIdentifier)
let position = store.position(timeIntervalSinceLatestBoot: 1)
entries = try store
.getEntries(at: position)
.compactMap { $0 as? OSLogEntryLog }
.filter { $0.subsystem == Bundle.main.bundleIdentifier! }
.map { "[\($0.date.formatted())] [\($0.category)] \($0.composedMessage)" }
} catch {
// log the failure with your own Logger
}
}
}
getEntries(at:) returns generic entries, so cast each one to OSLogEntryLog to reach subsystem, category, and composedMessage.
Choose the Time Window With position
The position function has two useful overloads. timeIntervalSinceLatestBoot: starts at the last boot. position(date:) starts at any date, for example the last 24 hours.
let store = try OSLogStore(scope: .currentProcessIdentifier)
let date = Date.now.addingTimeInterval(-24 * 3600)
let position = store.position(date: date)
Share the Logs From Settings
Join the entries into one string and hand it to a wrapped UIActivityViewController. The user picks Mail, Messages, or Files themselves.
struct SettingsView: View {
@ObservedObject var logs: LogStore
@State private var exportShown = false
var body: some View {
Form {
Section(header: Text("debug")) {
Button("exportLogs") {
logs.export()
exportShown = true
}
.sheet(isPresented: $exportShown) {
ShareView(items: [logs.entries.joined(separator: "\n")])
}
}
}
}
}
struct ShareView: UIViewControllerRepresentable {
let items: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
UIActivityViewController(activityItems: items, applicationActivities: nil)
}
func updateUIViewController(_ vc: UIActivityViewController, context: Context) {}
}
Only persisted levels come back: notice, warning, and critical survive, while trace messages are never stored, so they cannot be exported.