概要
ネット上にOSLogStore周りの使い方をまとめた記事があまりなかったのでそれらの記事を参考にしながら、iPhoneで実機のログを収集するサンプルを作りました。
OSLogStoreとは?
Apple側で用意してくれているSwiftプロジェクト内で使用できるLogを取るためのライブラリ。
ユーザーのシステムログを取るのに使える。
公式ドキュメントはここ。
サンプル
イメージ
ボタンをタップした時に報告をするアラートが表示され、
アラートのOK
をタップしたらログが送信されるようなイメージ。
今回のソースでは送信時の処理にログファイルがlog.txt
としてiPhoneのアプリ内(ドキュメントディレクトリ)に保存される仕様となっているため、一旦ログの中身を見たい場合にはprint(messages)
を追加すること。
Logs exported to: file:///Users/UserName/Library/Developer/CoreSimulator/Devices/358AD377-A8B8-4EDA-92A1-0266704E7597/data/Containers/Data/Application/67A88D6C-ADE9-4636-8DE1-FEC69A137CF7/Documents/logs.txt
Using ~iphone resources
main bundle CFBundle 0x600003b082a0 </Users/UserName/Library/Developer/CoreSimulator/Devices/358AD377-A8B8-4EDA-92A1-0266704E7597/data/Containers/Bundle/Application/646B339D-2D47-47E4-A692-D8CD41E9D85F/OSLogStore.app> (executable, loaded) getting handle 0xfffffffffffffffb
[0x600003b10000] activating connection: mach=true listener=false peer=false name=com.apple.cfprefsd.daemon
Initializing connection
Removing all cached process handles
Sending handshake request attempt #1 to server
Creating connection to com.apple.runningboard
[0x600003b14000] activating connection: mach=true listener=false peer=false name=com.apple.runningboard
[0x10dd04080] activating connection: mach=false listener=false peer=false name=(anonymous)
Handshake succeeded
Identity resolved as app<kuehar.OSLogStore((null))>
didChangeInheritances: <RBSInheritanceChangeSet| gained:{(
<RBSInheritance| environment:(none) name:com.apple.frontboard.visibility origID:86005-86002-137>
)} lost:(null)>
No persisted cache on this platform.
Deactivation reason added: 10; deactivation reasons: 0 -> 1024; animating application lifecycle event: 0
assertion failed: 23A344 21A328: libxpc.dylib + 62124 [D42F682D-2192-3D1C-AA6B-B96242532F0B]: 0x7d
activating monitor for service com.apple.frontboard.open
activating monitor for service com.apple.frontboard.workspace-service
..............................(この後にもログが続く)
ソースファイル
import SwiftUI
import OSLog
struct ContentView: View {
@State private var messages:String = ""
@State private var isLogSendAlertDisplay = false
var body: some View {
Form {
Section(header: Text("報告")) {
Button(action: {
exportToLogFile()
isLogSendAlertDisplay = true
}, label: {
Text("アプリがクラッシュしたことを報告する")
})
.alert("通知", isPresented:$isLogSendAlertDisplay) {
Button("OK") {
// ログ送付処理をここに書く
isLogSendAlertDisplay = false
}
Button("Cancel",role:.cancel){}
} message: {
Text("クラッシュしたことを開発者に報告します。よろしいですか?")
}
}
}
}
/// OSLogStoreを使用し、ユーザーログを抽出する。
/// その後の処理で`logs.txt`を作成し、ドキュメントディレクトリに保存を行う。
func exportToLogFile() {
do {
let store = try OSLogStore(scope: .currentProcessIdentifier)
let position = store.position(date: Date().addingTimeInterval(-3600))
let enumerator = try store.__entriesEnumerator(options: [.reverse], position: position, predicate: nil)
var logString = ""
messages = ""
enumerator.forEach { element in
if let message = (element as? OSLogEntry)?.composedMessage {
logString += message + "\n"
messages += message + "\n"
}
}
// ドキュメントディレクトリのパスを取得
if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let logURL = documentDirectory.appendingPathComponent("logs.txt")
try logString.write(to: logURL, atomically: true, encoding: .utf8)
print("Logs exported to: \(logURL)")
}
} catch {
print("Error exporting logs: \(error)")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
exportToLogFile
の中でOSLogStoreを使用し、ユーザーログを抽出する。
その後の処理でlogs.txt
を作成し、ドキュメントディレクトリに保存を行う。
プロジェクトに応じて対応すべき内容
log.txt
の送付処理に関してはプロジェクトによってまちまちな部分なので、今回は記載していない。
この記事の目的はあくまでOSLogStoreを通してユーザーのiPhoneのシステムログを取得するところにあるので、その点に関しては各々で対応するものとしている。