iOS Logging practices

Photo of Kamil Szczepański

Kamil Szczepański

Updated Aug 2, 2023 • 8 min read
agence-olloweb-520914-unsplash

Logging is the fastest way of validating if our application is doing what it was intended to do. With the growth of our application it’s easy to overflow our logs with too many nonessential messages.

It’s important to know how to tame the logging beast and structure our approach properly. Below I’m presenting few important tips about how to create log messages in your iOS application, that will make finding information for debugging quick and easy. I’ll talk about iOS specific tools, but the overall idea can be used in the mobile development in general.

Make good use of log levels

When creating log message most logging systems allow selection of a log level for it. Choosing an appropriate level helps to differentiate between important errors and less essential information.

The names of these log levels may vary between logging systems, but their usage is the same. When using OSLog on iOS we have access to the following levels:

  • Default - a default level. Used for logging information about things that might result in a failure.
  • Info - contains information that may be helpful, but not essential, during troubleshooting errors.
  • Debug - this level is intended only for use in a development environment and should contain all information useful during development and while troubleshooting specific problems.
  • Error - logs errors that occur locally on a process level and are recoverable.
  • Fault - logs errors that occur on a system level involving multiple processes that aren’t recoverable.

Provide data for easier filtering

When creating log messages we need to provide as much precise data as we can, so later we can easily filter and find specific logs. Beside selecting a log level we should also provide basic information such as the time and the log category, which should precisely describe the topic for the message. Below you see three logs, with only default OSLog value, error level and added category.

logging1

Control access to sensitive data

For debugging process we will also need values of some variables. While putting data into our logs we need to remember that it can contain some sensitive data. We should control the access to such data and allow to inspect those values only in debugging mode.

logging2

Use proper formatting

The way we present data in those messages is also important. Writing full sentences may be more readable for us humans, but it is harder to filter out specific values when we don’t use systematic wording.

BAD: user 1234 tapped on the save button on the sign-up view

BETTER: userID=1234 action=tap buttonId=save viewId=sign-up

Beside that we should use built-in data formatters and avoid logging whole data collections (if you need, do it on a debug level)

Do not block the main thread

When debugging a single problem we all are eager to put few print statements into our code, but we need to remember that it is a synchronous operation that blocks the main thread. The bigger our app grows the more logs we will be collecting, that’s why we should only use asynchronous logging to not slow it down.

On iOS we have few options to choose from

  • OSLog - Provided by Apple in 2016 is a well documented tool, which makes great use of the built-in Console.app. You can find tons of articles about it on the Internet.
  • Swift-log - Few weeks ago Apple presented their new unified logging system. Targeted for all Swift developers is meant to replace OSLog, but for now it’s not fully documented and available only through Swift Package Manager.
  • ResponseDetective - Here in Netguru we have created our own framework for intercepting any outgoing requests and incoming responses between your app and your server for debugging purposes.
  • There’s also lots of third-party wrappers providing easy to use logging systems with various additional features, e.g SwiftyBeaver with its cloud console.

Consider application type

Based on the kind of application we are developing we need to log different information that will be useful in improving and troubleshooting it. With mobile games we should focus on rendering time and overall performance, network heavy apps will focus on networking times and error responses and other apps on data calculation and processing duration.

Make good use of selected tool - intro to OSLog

At this point we know how the log messages should look like and what data should they contain. Now I’ll make simple introduction how to use OSLog in our code.


OSLog is an Apple’s product, so we do not need to install anything, we can just import it into our application:

import os.log

To create a simple log message you use following code lines:

os_log(“Simple log message.”)

os_log(“Error log message.”, log: .default, type: .error)

os_log(.default, log: .default, “Simple log with some %s.”, “data”)

Which will respectively output these three log messages in our Console.app:

logging3

To provide values for Category and Subsystem we need to create an OSLog object:

let log = OSLog(subsystem: “co.netguru.loggingdemo”, category: “Demo”)

Then we can use it while creating a log message:

os_log(“Error log message with subsystem and category.”, log: log, type: .error)

Before I’ve shown how to format message strings with data, now we will look at controlling access to those values. Some data types, e.g. String, are private by default. With others we need to specify that we want to hide it while not running in debug mode.

os_log(.default, log: log, "Log message with %{public}@ and %{private}@ data", "regular", "sensitive")

This is how it will look in Console.app, first in debugging mode and then with just connected device.

logging4

More OSLog features - signpost and pointOfInterest

I have talked about using OSLog to log messages in the Console.app, now I’ll show two features that allow created logs to be shown in Instruments.


If we have operations that take a long time to execute, like downloading data or rendering some complicated objects, we would like to see how much time they take on some kind of a graph. With help comes OSLog with it’s signpost.

To create signpost log we can use the same OSLog object as before, but we need to specify the moment it begins and ends:

let signpostLog = OSLog(subsystem: "co.netguru.loggingdemo", category: "Demo")

os_signpost(.begin, log: signpostLog, name: "Longer operation", "Longer operation”)

/* performing long operation*/

os_signpost(.end, log: signpostLog, name: "Longer operation", signpostID: signpostID)

When used with processing an array of objects it will result with the following graph of detailed information about a duration and a start time:

logging5

Second feature of OSLog helps us in situations when single operation can lead to CPU-heavy calculations, e.g. user opens a new complicated view. In such case we can create a pointOfInterest that will mark the specified moment on a CPU usage graph. To do it we need to change the category of our OSLog object:

let pointOfInterestLog = OSLog(subsystem: "co.netguru.loggingdemo", category: .pointsOfInterest)

os_signpost(.event, log: pointOfInterestLog, name: "Some Important Point in Application's Flow")
logging6


That’s just the tip of an iceberg, logging in Swift is a complicated and fascinating topic. For more detailed information check out Apple’s logging documentation. Have fun working on your applications and making them more stable and secure.


Photo by Agence Olloweb on Unsplash

Tags

Photo of Kamil Szczepański

More posts by this author

Kamil Szczepański

Kamil started his programming adventure quite early with a bit of game development in Delphi and...
Lost with AI?  Get the most important news weekly, straight to your inbox, curated by our CEO  Subscribe to AI'm Informed

Read more on our Blog

Check out the knowledge base collected and distilled by experienced professionals.

We're Netguru

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency.

Let's talk business