After reading the discussion in this post, I wanted to make a simple prototype to see whether it is possible to get the logs from the phone remotely. To accomplish this, I modified Steipete's code a little: I removed some code I didn't need and added a button to trigger the sending of the logs, named "Send logs to the developers".
Then, I created a codable struct called SendableLog
that converted the OSLogEntryLog
, making it possible to convert it to JSON. After getting the logs using getEntries()
and mapping them to this new type, I converted the logs to JSON and sent an HTTP POST request to an endpoint (as suggested by @DanielKaplan) on a simple Python server I was running on my MacBook.
The Swift code (iOS 15 application):
//
// ContentView.swift
// OSLogStoreTesting
//
// Created by bbruns on 23/12/2021.
// Based on Peter Steinberger (23.08.20): https://github.com/steipete/OSLogTest/blob/master/LoggingTest/ContentView.swift.
//
import SwiftUI
import OSLog
import Combine
let subsystem = "com.bbruns.OSLogStoreTesting"
func getLogEntries() throws -> [OSLogEntryLog] {
let logStore = try OSLogStore(scope: .currentProcessIdentifier)
let oneHourAgo = logStore.position(date: Date().addingTimeInterval(-3600))
let allEntries = try logStore.getEntries(at: oneHourAgo)
return allEntries
.compactMap { $0 as? OSLogEntryLog }
.filter { $0.subsystem == subsystem }
}
struct SendableLog: Codable {
let level: Int
let date, subsystem, category, composedMessage: String
}
func sendLogs() {
let logs = try! getLogEntries()
let sendLogs: [SendableLog] = logs.map({ SendableLog(level: $0.level.rawValue,
date: "\($0.date)",
subsystem: $0.subsystem,
category: $0.category,
composedMessage: $0.composedMessage) })
// Convert object to JSON
let jsonData = try? JSONEncoder().encode(sendLogs)
// Send to my API
let url = URL(string: "http://x.x.x.x:8000")! // IP address and port of Python server
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
let session = URLSession.shared
let task = session.dataTask(with: request) { (data, response, error) in
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
}
}
task.resume()
}
struct ContentView: View {
let logger = Logger(subsystem: subsystem, category: "main")
var logLevels = ["Default", "Info", "Debug", "Error", "Fault"]
@State private var selectedLogLevel = 0
init() {
logger.log("SwiftUI is initializing the main ContentView")
}
var body: some View {
return VStack {
Text("This is a sample project to test the new logging features of iOS 15.")
.padding()
Picker(selection: $selectedLogLevel, label: Text("Choose Log Level")) {
ForEach(0 ..< logLevels.count) {
Text(self.logLevels[$0])
}
}.frame(width: 400, height: 150, alignment: .center)
Button(action: {
switch(selectedLogLevel) {
case 0:
logger.log("Default log message")
case 1:
logger.info("Info log message")
case 2:
logger.debug("Debug log message")
case 3:
logger.error("Error log message")
default: // 4
logger.fault("Fault log message")
}
}) {
Text("Log with Log Level \(logLevels[selectedLogLevel])")
}.padding()
Button(action: sendLogs) {
Text("Send logs to developers")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I have this simple Python HTTP server listening to incoming POST requests, the IP address was set to the local IP address of my MacBook. This matches the IP address in the Swift code above.
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
hostName = "x.x.x.x" # IP address of server
serverPort = 8000
class MyServer(BaseHTTPRequestHandler):
def _set_headers(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_HEAD(self):
self._set_headers()
def do_POST(self):
self._set_headers()
print("Received POST")
self.data_string = self.rfile.read(int(self.headers['Content-Length']))
self.send_response(200)
self.end_headers()
data = json.loads(self.data_string)
print(f"JSON received: \n\n {data}")
if __name__ == "__main__":
webServer = HTTPServer((hostName, serverPort), MyServer)
print("Server started http://%s:%s" % (hostName, serverPort))
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
print("Server stopped.")
When I run the app and tap the Send logs to developers button, I see the following message in my terminal:
x.x.x.x - - [23/Dec/2021 13:56:47] "POST / HTTP/1.1" 200 -
JSON received:
[{'subsystem': 'com.bbruns.OSLogStoreTesting', 'level': 3, 'composedMessage': 'SwiftUI is initializing the main ContentView', 'category': 'main', 'date': '2021-12-23 12:56:43 +0000'}]
The logs are successfully retrieved from the phone and then sent to the server.
Caveat
When I (fully) close the app and reopen it, the previous logs are gone!
When creating the log store (let logStore = try OSLogStore(scope: .currentProcessIdentifier)
) the scope is set to .currentProcessIdentifier
, which is the only available scope on iOS. This thread makes me believe The .system
scope would include previous logs as well, but the system scope is not available on iOS.