Diagnosing on-device issues for iOS App Development
Enhancing iOS app diagnostics with advanced on-device logging and privacy-centric data sharing, improving error handling and user experience.
Occasionally, users of your iOS app encounter issues unique to their devices. Despite comprehensive error handling and backup workflows in your app, specific errors can’t be resolved smoothly without revealing technical details, which are irrelevant and confusing for users. Presenting such internal errors with their technicalities undermines the user experience and gives an impression of a low-quality app. Typically, these issues are recorded on the device and sent to a service for developers to analyse and fix. However, this process can be hindered if the network is unavailable or in cases where the network itself is the root of the problem.
Apple’s recent updates have laid the groundwork for sophisticated on-device logging capabilities, a feature that debuted with iOS & iPadOS 15. This enhancement is a significant stride in ensuring user privacy and app security. The ongoing advancements and improvements to OSLog have rendered traditional C-style print statements obsolete, favouring a system rich in metadata support. This system seamlessly integrates with the first-class Swift-type system and development tools. Most notably, log entries can now be queried and retrieved, enabling the creation of versatile log viewers or extracting logs to files. This comprehensive solution directly empowers sensitive diagnostic data collection on the device. It facilitates a seamless sharing process with developers and support teams, marking a significant leap in diagnostic and support capabilities.
This feature can be rolled out incrementally, applying a sophisticated logging setup as needed. Importantly, each step is optional, yet each independently offers a distinct advantage over traditional logging methods. Nevertheless, we present a comprehensive solution designed to simplify diagnosing local issues significantly.
Commencing with identifying sections of the code that are already subject to logging, as well as those that could gain from generating more detailed information for enhanced visibility into the internal workings of the code. This includes areas where error handling is insufficient for accurately pinpointing issues or where errors cannot be gracefully managed through user intervention or fallback mechanisms. It’s wise to strategically plan the code areas in advance, as good logging should be introduced gradually. As the volume of logging statements increases, revisiting all of them to restructure their design and relationships can become challenging.
Organise your log messages into categories based on the aspects likely to be scrutinised during an issue investigation. Effective categorisation examples include networking, storage access, and third-party SDK usage. This approach is invaluable for filtering through potentially extensive logs, depending on the recorded activity volume. It’s crucial to ensure that each category is sufficiently comprehensive to encompass all significant, relevant events while remaining concise enough to offer a clear summary of the activities.
Carefully construct your messages utilising the new OSLog API, aligning them with specific categories and subsystems. Employ your primary bundle identifier as a subsystem for easy retrieval of logs generated by your app. For frameworks and packages, you can craft the identifier using their name or the reverse domain convention typically used for apps. It’s important to note that module bundle identifiers are not optimal in this scenario, as they usually refer to the resource bundle in the case of a package.
Select the most suitable log type for each message, tailoring them to their respective purpose. We recommend utilising the following:
- debug (for detailed information during development)
- info (for critical events, essential context)
- notice (for usual errors and unexpected behaviour)
- error (for critical yet recoverable errors)
- fault (for critical, unrecoverable errors)
It’s important to note that debug logs are only stored in memory, making them ineffective for troubleshooting on users’ devices. While it’s possible to amend this default setting, we advise against it except in special circumstances where storage limitations are not a concern.
The most vital aspect of this process is the creation of logs that are rich in useful information. The effectiveness of these logs is critical, as they will determine whether you have sufficient data available when it comes time to diagnose an issue.
- Ensure the message context is well defined by the logger type so you don’t repeat that information in your messages. OSLog records source location so you can focus on what happened in your messages instead of where it happened.
- Don’t log everything. You have to be mindful of the space on the user’s device and the log size that will be delivered for your inspection. You risk your log to be truncated or incomplete. The users might also be more hesitant to share large files.
- Do log all the error details provided by error objects. There is no point in overwriting system-provided information.
- Use the description property to reveal essential information on your objects and structures.
- Use custom values in the contents of a message with convenient formatting and privacy options (for sensitive information).
- Keep the messages short, stating the event that occurred in a straightforward, meaningful and explicit way.
After our log messages are securely stored, it’s essential to have a method for retrieving them upon request. Apple offers a solution with the OSLogStore API, which allows for querying and extracting information from OSLog. Utilising this tool, you can filter and select the relevant messages, compiling them into a text file that the user can easily share.
Focused Log Retrieval: Extract log entries specific to your app process, including logs from your app and Apple’s frameworks and subsystems. While you might consider narrowing these entries to those logged against your app’s subsystem, be mindful that this could result in missing valuable context, mainly if the issue is linked to system frameworks.
Time-Bound Queries: Implement time constraints on your log queries to manage the volume of entries. A common approach is to collect logs from the past few days or since the last device reboot. Ensure the time range is substantial enough to capture potential distant causes of issues.
Comprehensive Log Types: Typically, you’ll want to include all types of logs stored on the disk for a thorough analysis unless you’ve set up a specific error log for improved app maintenance. Both informative events and error details are critical for your investigation.
Essential Log Line Details: When formatting logs for extraction, ensure that critical details such as timestamps, categories, and messages are included. If you’re extracting data from multiple modules, including the subsystem is beneficial. Adding the log level (type) can be helpful for more refined filtering.
Fallback for Log Export: Consider a fallback mechanism that compiles the log export contents in memory. Since logging serves as an error-handling strategy, storage errors might impede file saving, and this fallback can be a safeguard.
User Guidance for Data Provision: Advise users on the importance of providing logs that consider the collection time frame and extraction methods. This guidance ensures they supply you with the most relevant and valuable data for your analysis.
The final step in the implementation process is establishing a method for users to send the extracted logs to developers. On iOS (and iPadOS), the most seamless approach offers users various sharing options on their devices. This can include:
- sending it as a mail attachment to the support email address
- sending it included with a message on some messaging app
- sending it using AirDrop to some nearby developer device
- saving it as a note attachment (for later reference)
- saving it on the device for record, later decision (or connectivity)
These sharing options, along with others, are accessible via the standard activity sheet provided by system frameworks. On the iPhone, this is presented as a share sheet, while on the iPad, it appears as a popover. This distinction necessitates additional configuration for the popover on iPadOS.
An alternative method is automatic log uploads to a server. However, we advise caution with this approach due to potential network connection failures, the conservation of user data plans, and the possible size of the log file. It’s imperative to always seek user consent for such background activities. Moreover, if you opt for server uploads, remember to update the privacy nutrition label on the App Store to reflect this functionality.
The new system framework capabilities have simplified the sharing of technical logs with developers. This solution not only upholds best practices in security and privacy but also empowers users to control the sharing of potentially sensitive information. Thanks to lightweight data structures, it integrates smoothly with existing code without compromising performance. This flexibility supports a range of use cases, from routine error reporting to comprehensive diagnostics and activity monitoring.
Additionally, Xcode’s Instruments can use the collected data to analyse the correlation between various metrics and app code, assessing the impact of different usage patterns on app performance and system integration. This enhancement also enriches the Xcode debugging experience. Console messages are now more easily filtered and presented clearly during debugging sessions, with direct links to the code that generated them.
This approach modernises and replaces previous methods involving device console logs and the need for physical access to the device, offering significant benefits to both users and developers.
If you need a partner in software development, we're here to help you.
We will respond to your enquiry immediately.